├── resources ├── images │ ├── lan8720_modified_board.png │ ├── lan8720_modified_schematic.png │ ├── logo.svg │ └── stm32_w5500.svg └── docs │ ├── TROUBLESHOOTING.md │ └── ADVANCED.md ├── library.properties ├── library.json ├── .github ├── FUNDING.yml ├── stale.yml ├── workflows │ ├── cpp_lint.yml │ └── jekyll-gh-pages.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── src ├── imap │ ├── MailboxInfo.h │ ├── IMAPResponse.h │ └── IMAPBase.h ├── core │ ├── ReadyError.h │ ├── ReadyTimer.h │ ├── ReadyClient.h │ ├── Utils.h │ └── QBDecoder.h ├── ReadyMail.h └── smtp │ └── SMTPResponse.h ├── examples ├── Reading │ ├── OTA │ │ ├── Networks.h │ │ └── OTA.ino │ ├── Append │ │ └── Networks.h │ ├── Command │ │ ├── Networks.h │ │ └── Command.ino │ ├── Download │ │ ├── Networks.h │ │ └── Download.ino │ ├── Fetch │ │ ├── Networks.h │ │ └── Fetch.ino │ ├── Idling │ │ ├── Networks.h │ │ └── Idling.ino │ ├── Search │ │ ├── Networks.h │ │ └── Search.ino │ └── FetchAsync │ │ ├── Networks.h │ │ └── FetchAsync.ino ├── Network │ ├── AutoPort │ │ ├── Networks.h │ │ └── AutoPort.ino │ ├── AutoClient │ │ ├── Networks.h │ │ └── AutoClient.ino │ ├── EthernetClient │ │ └── EthernetClient.ino │ └── GSMClient │ │ └── GSMClient.ino └── Sending │ ├── Command │ ├── Networks.h │ └── Command.ino │ ├── Attachment │ ├── Networks.h │ └── Attachment.ino │ ├── InlineImage │ ├── Networks.h │ └── InlineImage.ino │ ├── RFC822Message │ └── Networks.h │ ├── SendAsync │ ├── Networks.h │ └── SendAsync.ino │ ├── SimpleText │ ├── Networks.h │ └── SimpleText.ino │ ├── StaticText │ ├── Networks.h │ └── StaticText.ino │ └── ESP32Camera │ ├── ESP32Camera.ino │ └── camera_pins.h ├── LICENSE ├── keywords.txt └── README.md /resources/images/lan8720_modified_board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobizt/ReadyMail/HEAD/resources/images/lan8720_modified_board.png -------------------------------------------------------------------------------- /resources/images/lan8720_modified_schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobizt/ReadyMail/HEAD/resources/images/lan8720_modified_schematic.png -------------------------------------------------------------------------------- /resources/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ReadyMail 5 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ReadyMail 2 | 3 | version=0.3.6 4 | 5 | author=Mobizt 6 | 7 | maintainer=Mobizt 8 | 9 | sentence=The fast and lightweight async Email client library for Arduino. 10 | 11 | paragraph=This library supports sending and fetching the Email. The nested RFC822 message attachments sending and fetching are also supported. 12 | 13 | category=Communication 14 | 15 | url=https://github.com/mobizt/ReadyMail 16 | 17 | architectures=esp8266,esp32,sam,samd,stm32,STM32F1,STM32F4,teensy,mbed_nano,mbed_rp2040,rp2040, renesas_uno 18 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReadyMail", 3 | "version": "0.3.6", 4 | "keywords": "communication, email, imap, smtp, esp32, esp8266, samd, arduino", 5 | "description": "The fast and lightweight async Email client library for Arduino.", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/mobizt/ReadyMail.git" 9 | }, 10 | "authors": [{ 11 | "name": "Mobizt", 12 | "email": "suwatchai@outlook.com" 13 | }], 14 | "frameworks": "arduino", 15 | "platforms": "espressif32, espressif8266, atmelsam, ststm32, teensy, rp2040, renesas-ra" 16 | } 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: mobizt 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: 13 | - https://www.buymeacoffee.com/Mobizt 14 | -------------------------------------------------------------------------------- /src/imap/MailboxInfo.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 Suwatchai K. 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef IMAP_MAILBOX_INFO_H 8 | #define IMAP_MAILBOX_INFO_H 9 | #if defined(ENABLE_IMAP) 10 | #include 11 | #include "Common.h" 12 | 13 | namespace ReadyMailIMAP 14 | { 15 | typedef struct mailbox_info 16 | { 17 | uint32_t msgCount = 0, RecentCount = 0, UIDValidity = 0, nextUID = 0, UnseenIndex = 0, highestModseq = 0; 18 | bool noModseq = false; 19 | std::vector flags, permanentFlags; 20 | String name; 21 | } MailboxInfo; 22 | } 23 | 24 | #endif 25 | #endif 26 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 2 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 1 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github/workflows/cpp_lint.yml: -------------------------------------------------------------------------------- 1 | name: cpplint 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - '.github/workflows/compile_*.yml' 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: cpplint 13 | uses: reviewdog/action-cpplint@master 14 | with: 15 | github_token: ${{ secrets.GITHUB_TOKEN }} 16 | reporter: github-pr-check 17 | flags: --linelength=100 18 | filter: "-whitespace/tab\ 19 | ,-readability/braces\ 20 | ,-whitespace/braces\ 21 | ,-whitespace/comments\ 22 | ,-whitespace/indent\ 23 | ,-whitespace/newline\ 24 | ,-whitespace/operators\ 25 | ,-whitespace/parens\ 26 | " -------------------------------------------------------------------------------- /examples/Reading/OTA/Networks.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) 2 | #include 3 | #elif defined(ESP8266) 4 | #include 5 | #elif __has_include() || defined(ARDUINO_NANO_RP2040_CONNECT) 6 | #include 7 | #elif __has_include() 8 | #include 9 | #elif __has_include() || defined(ARDUINO_UNOWIFIR4) 10 | #include 11 | #elif __has_include() || defined(ARDUINO_PORTENTA_C33) 12 | #include 13 | #elif __has_include() 14 | #include 15 | #endif 16 | 17 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ESP8266) 18 | #include 19 | #else 20 | #include 21 | #endif -------------------------------------------------------------------------------- /examples/Network/AutoPort/Networks.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) 2 | #include 3 | #elif defined(ESP8266) 4 | #include 5 | #elif __has_include() || defined(ARDUINO_NANO_RP2040_CONNECT) 6 | #include 7 | #elif __has_include() 8 | #include 9 | #elif __has_include() || defined(ARDUINO_UNOWIFIR4) 10 | #include 11 | #elif __has_include() || defined(ARDUINO_PORTENTA_C33) 12 | #include 13 | #elif __has_include() 14 | #include 15 | #endif 16 | 17 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ESP8266) 18 | #include 19 | #else 20 | #include 21 | #endif -------------------------------------------------------------------------------- /examples/Reading/Append/Networks.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) 2 | #include 3 | #elif defined(ESP8266) 4 | #include 5 | #elif __has_include() || defined(ARDUINO_NANO_RP2040_CONNECT) 6 | #include 7 | #elif __has_include() 8 | #include 9 | #elif __has_include() || defined(ARDUINO_UNOWIFIR4) 10 | #include 11 | #elif __has_include() || defined(ARDUINO_PORTENTA_C33) 12 | #include 13 | #elif __has_include() 14 | #include 15 | #endif 16 | 17 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ESP8266) 18 | #include 19 | #else 20 | #include 21 | #endif -------------------------------------------------------------------------------- /examples/Reading/Command/Networks.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) 2 | #include 3 | #elif defined(ESP8266) 4 | #include 5 | #elif __has_include() || defined(ARDUINO_NANO_RP2040_CONNECT) 6 | #include 7 | #elif __has_include() 8 | #include 9 | #elif __has_include() || defined(ARDUINO_UNOWIFIR4) 10 | #include 11 | #elif __has_include() || defined(ARDUINO_PORTENTA_C33) 12 | #include 13 | #elif __has_include() 14 | #include 15 | #endif 16 | 17 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ESP8266) 18 | #include 19 | #else 20 | #include 21 | #endif -------------------------------------------------------------------------------- /examples/Reading/Download/Networks.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) 2 | #include 3 | #elif defined(ESP8266) 4 | #include 5 | #elif __has_include() || defined(ARDUINO_NANO_RP2040_CONNECT) 6 | #include 7 | #elif __has_include() 8 | #include 9 | #elif __has_include() || defined(ARDUINO_UNOWIFIR4) 10 | #include 11 | #elif __has_include() || defined(ARDUINO_PORTENTA_C33) 12 | #include 13 | #elif __has_include() 14 | #include 15 | #endif 16 | 17 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ESP8266) 18 | #include 19 | #else 20 | #include 21 | #endif -------------------------------------------------------------------------------- /examples/Reading/Fetch/Networks.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) 2 | #include 3 | #elif defined(ESP8266) 4 | #include 5 | #elif __has_include() || defined(ARDUINO_NANO_RP2040_CONNECT) 6 | #include 7 | #elif __has_include() 8 | #include 9 | #elif __has_include() || defined(ARDUINO_UNOWIFIR4) 10 | #include 11 | #elif __has_include() || defined(ARDUINO_PORTENTA_C33) 12 | #include 13 | #elif __has_include() 14 | #include 15 | #endif 16 | 17 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ESP8266) 18 | #include 19 | #else 20 | #include 21 | #endif -------------------------------------------------------------------------------- /examples/Reading/Idling/Networks.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) 2 | #include 3 | #elif defined(ESP8266) 4 | #include 5 | #elif __has_include() || defined(ARDUINO_NANO_RP2040_CONNECT) 6 | #include 7 | #elif __has_include() 8 | #include 9 | #elif __has_include() || defined(ARDUINO_UNOWIFIR4) 10 | #include 11 | #elif __has_include() || defined(ARDUINO_PORTENTA_C33) 12 | #include 13 | #elif __has_include() 14 | #include 15 | #endif 16 | 17 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ESP8266) 18 | #include 19 | #else 20 | #include 21 | #endif -------------------------------------------------------------------------------- /examples/Reading/Search/Networks.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) 2 | #include 3 | #elif defined(ESP8266) 4 | #include 5 | #elif __has_include() || defined(ARDUINO_NANO_RP2040_CONNECT) 6 | #include 7 | #elif __has_include() 8 | #include 9 | #elif __has_include() || defined(ARDUINO_UNOWIFIR4) 10 | #include 11 | #elif __has_include() || defined(ARDUINO_PORTENTA_C33) 12 | #include 13 | #elif __has_include() 14 | #include 15 | #endif 16 | 17 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ESP8266) 18 | #include 19 | #else 20 | #include 21 | #endif -------------------------------------------------------------------------------- /examples/Sending/Command/Networks.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) 2 | #include 3 | #elif defined(ESP8266) 4 | #include 5 | #elif __has_include() || defined(ARDUINO_NANO_RP2040_CONNECT) 6 | #include 7 | #elif __has_include() 8 | #include 9 | #elif __has_include() || defined(ARDUINO_UNOWIFIR4) 10 | #include 11 | #elif __has_include() || defined(ARDUINO_PORTENTA_C33) 12 | #include 13 | #elif __has_include() 14 | #include 15 | #endif 16 | 17 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ESP8266) 18 | #include 19 | #else 20 | #include 21 | #endif -------------------------------------------------------------------------------- /examples/Network/AutoClient/Networks.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) 2 | #include 3 | #elif defined(ESP8266) 4 | #include 5 | #elif __has_include() || defined(ARDUINO_NANO_RP2040_CONNECT) 6 | #include 7 | #elif __has_include() 8 | #include 9 | #elif __has_include() || defined(ARDUINO_UNOWIFIR4) 10 | #include 11 | #elif __has_include() || defined(ARDUINO_PORTENTA_C33) 12 | #include 13 | #elif __has_include() 14 | #include 15 | #endif 16 | 17 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ESP8266) 18 | #include 19 | #else 20 | #include 21 | #endif -------------------------------------------------------------------------------- /examples/Reading/FetchAsync/Networks.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) 2 | #include 3 | #elif defined(ESP8266) 4 | #include 5 | #elif __has_include() || defined(ARDUINO_NANO_RP2040_CONNECT) 6 | #include 7 | #elif __has_include() 8 | #include 9 | #elif __has_include() || defined(ARDUINO_UNOWIFIR4) 10 | #include 11 | #elif __has_include() || defined(ARDUINO_PORTENTA_C33) 12 | #include 13 | #elif __has_include() 14 | #include 15 | #endif 16 | 17 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ESP8266) 18 | #include 19 | #else 20 | #include 21 | #endif -------------------------------------------------------------------------------- /examples/Sending/Attachment/Networks.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) 2 | #include 3 | #elif defined(ESP8266) 4 | #include 5 | #elif __has_include() || defined(ARDUINO_NANO_RP2040_CONNECT) 6 | #include 7 | #elif __has_include() 8 | #include 9 | #elif __has_include() || defined(ARDUINO_UNOWIFIR4) 10 | #include 11 | #elif __has_include() || defined(ARDUINO_PORTENTA_C33) 12 | #include 13 | #elif __has_include() 14 | #include 15 | #endif 16 | 17 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ESP8266) 18 | #include 19 | #else 20 | #include 21 | #endif -------------------------------------------------------------------------------- /examples/Sending/InlineImage/Networks.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) 2 | #include 3 | #elif defined(ESP8266) 4 | #include 5 | #elif __has_include() || defined(ARDUINO_NANO_RP2040_CONNECT) 6 | #include 7 | #elif __has_include() 8 | #include 9 | #elif __has_include() || defined(ARDUINO_UNOWIFIR4) 10 | #include 11 | #elif __has_include() || defined(ARDUINO_PORTENTA_C33) 12 | #include 13 | #elif __has_include() 14 | #include 15 | #endif 16 | 17 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ESP8266) 18 | #include 19 | #else 20 | #include 21 | #endif -------------------------------------------------------------------------------- /examples/Sending/RFC822Message/Networks.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) 2 | #include 3 | #elif defined(ESP8266) 4 | #include 5 | #elif __has_include() || defined(ARDUINO_NANO_RP2040_CONNECT) 6 | #include 7 | #elif __has_include() 8 | #include 9 | #elif __has_include() || defined(ARDUINO_UNOWIFIR4) 10 | #include 11 | #elif __has_include() || defined(ARDUINO_PORTENTA_C33) 12 | #include 13 | #elif __has_include() 14 | #include 15 | #endif 16 | 17 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ESP8266) 18 | #include 19 | #else 20 | #include 21 | #endif -------------------------------------------------------------------------------- /examples/Sending/SendAsync/Networks.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) 2 | #include 3 | #elif defined(ESP8266) 4 | #include 5 | #elif __has_include() || defined(ARDUINO_NANO_RP2040_CONNECT) 6 | #include 7 | #elif __has_include() 8 | #include 9 | #elif __has_include() || defined(ARDUINO_UNOWIFIR4) 10 | #include 11 | #elif __has_include() || defined(ARDUINO_PORTENTA_C33) 12 | #include 13 | #elif __has_include() 14 | #include 15 | #endif 16 | 17 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ESP8266) 18 | #include 19 | #else 20 | #include 21 | #endif -------------------------------------------------------------------------------- /examples/Sending/SimpleText/Networks.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) 2 | #include 3 | #elif defined(ESP8266) 4 | #include 5 | #elif __has_include() || defined(ARDUINO_NANO_RP2040_CONNECT) 6 | #include 7 | #elif __has_include() 8 | #include 9 | #elif __has_include() || defined(ARDUINO_UNOWIFIR4) 10 | #include 11 | #elif __has_include() || defined(ARDUINO_PORTENTA_C33) 12 | #include 13 | #elif __has_include() 14 | #include 15 | #endif 16 | 17 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ESP8266) 18 | #include 19 | #else 20 | #include 21 | #endif -------------------------------------------------------------------------------- /examples/Sending/StaticText/Networks.h: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ARDUINO_GIGA) || defined(ARDUINO_OPTA) 2 | #include 3 | #elif defined(ESP8266) 4 | #include 5 | #elif __has_include() || defined(ARDUINO_NANO_RP2040_CONNECT) 6 | #include 7 | #elif __has_include() 8 | #include 9 | #elif __has_include() || defined(ARDUINO_UNOWIFIR4) 10 | #include 11 | #elif __has_include() || defined(ARDUINO_PORTENTA_C33) 12 | #include 13 | #elif __has_include() 14 | #include 15 | #endif 16 | 17 | #if defined(ESP32) || defined(ARDUINO_RASPBERRY_PI_PICO_W) || defined(ESP8266) 18 | #include 19 | #else 20 | #include 21 | #endif -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: ENHANCEMENT 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Just ask something** 11 | If you have questions, use [Discussions](https://github.com/mobizt/FirebaseClient/discussions) instead. 12 | 13 | **Is your feature request related to a problem? Please describe.** 14 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 15 | 16 | **Describe the solution you'd like** 17 | A clear and concise description of what you want to happen. 18 | 19 | **Describe alternatives you've considered** 20 | A clear and concise description of any alternative solutions or features you've considered. 21 | 22 | **Additional context** 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please make sure, you have read the library [documentation](https://github.com/mobizt/ReadyMail) thoroughly before open the issue. 11 | 12 | **Just ask something** 13 | If you have questions, use [Discussions](https://github.com/mobizt/ReadyMail/discussions) instead. 14 | 15 | **Describe the bug** 16 | A clear and concise description of what the bug is. 17 | 18 | **To Reproduce** 19 | Create minimal, clean and clear but complete code that can be reproduced the issue. 20 | The third party libraries should be removed. 21 | And post the code here. 22 | 23 | 24 | **Expected behavior** 25 | A clear and concise description of what you expected to happen. 26 | 27 | **Screenshots** 28 | If applicable, add screenshots to help explain your problem. 29 | 30 | **IDE and its version:** 31 | - Arduino, PlatformIO 32 | - Version [e.g. 3.3.4] 33 | 34 | **ESP8266 Arduino Core SDK version** 35 | - Version [e.g. 2.3.6] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Suwatchai K. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/jekyll-gh-pages.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a Jekyll site to GitHub Pages 2 | name: Deploy Jekyll with GitHub Pages dependencies preinstalled 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Build job 26 | build: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v4 31 | - name: Setup Pages 32 | uses: actions/configure-pages@v5 33 | - name: Build with Jekyll 34 | uses: actions/jekyll-build-pages@v1 35 | with: 36 | source: ./ 37 | destination: ./_site 38 | - name: Upload artifact 39 | uses: actions/upload-pages-artifact@v3 40 | 41 | # Deployment job 42 | deploy: 43 | environment: 44 | name: github-pages 45 | url: ${{ steps.deployment.outputs.page_url }} 46 | runs-on: ubuntu-latest 47 | needs: build 48 | steps: 49 | - name: Deploy to GitHub Pages 50 | id: deployment 51 | uses: actions/deploy-pages@v4 52 | -------------------------------------------------------------------------------- /src/core/ReadyError.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 Suwatchai K. 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef READY_ERROR_H 8 | #define READY_ERROR_H 9 | #include 10 | 11 | #if defined(ENABLE_IMAP) || defined(ENABLE_SMTP) 12 | #if defined(ENABLE_DEBUG) || defined(ENABLE_CORE_DEBUG) 13 | static String rd_err(int code) 14 | { 15 | String msg; 16 | switch (code) 17 | { 18 | case TCP_CLIENT_ERROR_INITIALIZE: 19 | msg = "Client is not yet initialized"; 20 | break; 21 | case TCP_CLIENT_ERROR_CONNECTION: 22 | msg = "Connection failed"; 23 | break; 24 | case TCP_CLIENT_ERROR_NOT_CONNECTED: 25 | msg = "Server was not yet connected"; 26 | break; 27 | case TCP_CLIENT_ERROR_CONNECTION_TIMEOUT: 28 | msg = "Connection timed out, see http://bit.ly/437GkRA"; 29 | break; 30 | case TCP_CLIENT_ERROR_STARTTLS: 31 | msg = "SRART TLS failed"; 32 | break; 33 | case TCP_CLIENT_ERROR_TLS_HANDSHAKE: 34 | msg = "TLS handshake failed"; 35 | break; 36 | case TCP_CLIENT_ERROR_SEND_DATA: 37 | msg = "Send data failed"; 38 | break; 39 | case TCP_CLIENT_ERROR_READ_DATA: 40 | msg = "Read data failed"; 41 | break; 42 | case AUTH_ERROR_UNAUTHENTICATE: 43 | msg = "Unauthented"; 44 | break; 45 | case AUTH_ERROR_AUTHENTICATION: 46 | msg = "Authentication error"; 47 | break; 48 | case AUTH_ERROR_OAUTH2_NOT_SUPPORTED: 49 | msg = "OAuth2.0 authentication was not supported"; 50 | break; 51 | default: 52 | break; 53 | } 54 | return msg; 55 | } 56 | #endif 57 | #endif 58 | #endif -------------------------------------------------------------------------------- /resources/images/stm32_w5500.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | STM32F103C8T6 11 | PA4 (CS) 12 | PA5 (SCLK) 13 | PA6 (MISO) 14 | PA7 (MOSI) 15 | PA0 (INT) 16 | PB13 (RST) 17 | 3.3V 18 | GND 19 | 20 | 21 | 22 | W5500 Module 23 | SCS 24 | SCLK 25 | MISO 26 | MOSI 27 | INT 28 | RST 29 | VCC 30 | GND 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/core/ReadyTimer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 Suwatchai K. 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef READY_TIMER_H 8 | #define READY_TIMER_H 9 | 10 | #include 11 | 12 | #if defined(ENABLE_IMAP) || defined(ENABLE_SMTP) 13 | 14 | class ReadyTimer 15 | { 16 | private: 17 | unsigned long ts = 0, end = 0, period = 0, now = 0, ms = 0; 18 | bool enable = false; 19 | uint8_t feed_count = 0; 20 | 21 | public: 22 | explicit ReadyTimer(unsigned long sec = 60) { setInterval(sec); } 23 | 24 | ~ReadyTimer() {} 25 | 26 | void reset() { end = ts + period; } 27 | 28 | void start() 29 | { 30 | enable = true; 31 | loop(); 32 | reset(); 33 | } 34 | 35 | void stop() { enable = false; } 36 | 37 | void setInterval(unsigned long sec) 38 | { 39 | loop(); 40 | period = sec; 41 | reset(); 42 | } 43 | 44 | void feed(unsigned long sec) 45 | { 46 | feed_count++; 47 | if (sec == 0 || feed_count == 0) 48 | feed_count = 1; 49 | stop(); 50 | setInterval(sec); 51 | start(); 52 | } 53 | 54 | void loop() 55 | { 56 | if (enable && (unsigned long)(millis() - now) > 100) 57 | { 58 | now = millis(); 59 | if (now / 1000 >= ts) 60 | { 61 | ts = now / 1000; 62 | ms = now; 63 | } 64 | else // millis overflow handling 65 | ts += (unsigned long)(now - ms) / 1000; 66 | } 67 | } 68 | 69 | unsigned long remaining() { return ready() ? 0 : end - ts; } 70 | 71 | uint8_t feedCount() const { return feed_count; } 72 | 73 | bool isRunning() const { return enable; }; 74 | 75 | bool ready() 76 | { 77 | loop(); 78 | return ts >= end; 79 | } 80 | }; 81 | #endif 82 | #endif -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ###################################### 2 | # Syntax Coloring Map Agaligo 3 | ###################################### 4 | 5 | ####################################### 6 | # Classes and Structured Type (KEYWORD1) 7 | ####################################### 8 | 9 | Agaligo KEYWORD1 10 | IMAPClient KEYWORD1 11 | SMTPClient KEYWORD1 12 | SMTPMessage KEYWORD1 13 | MailboxInfo KEYWORD1 14 | Attachment KEYWORD1 15 | 16 | ############################################### 17 | # Methods and Functions (KEYWORD2) 18 | ############################################### 19 | 20 | connect KEYWORD2 21 | isConnected KEYWORD2 22 | authenticate KEYWORD2 23 | fetch KEYWORD2 24 | fetchUID KEYWORD2 25 | getMailbox KEYWORD2 26 | send KEYWORD2 27 | list KEYWORD2 28 | select KEYWORD2 29 | append KEYWORD2 30 | logout KEYWORD2 31 | stop KEYWORD2 32 | close KEYWORD2 33 | loop KEYWORD2 34 | available KEYWORD2 35 | isAuthenticated KEYWORD2 36 | currentState KEYWORD2 37 | idleStatus KEYWORD2 38 | currentMessage KEYWORD2 39 | sendCommand KEYWORD2 40 | sendData KEYWORD2 41 | setStartTLS KEYWORD2 42 | commandResponse KEYWORD2 43 | addAttachment KEYWORD2 44 | addInlineImage KEYWORD2 45 | isComplete KEYWORD2 46 | getCmdResponse KEYWORD2 47 | getDateTimeString KEYWORD2 48 | base64Encode KEYWORD2 49 | plainSASLEncode KEYWORD2 50 | fileChunk KEYWORD2 51 | fileInfo KEYWORD2 52 | fileProgress KEYWORD2 53 | fileCount KEYWORD2 54 | headerCount KEYWORD2 55 | getHeader KEYWORD2 56 | messageIndex KEYWORD2 57 | messageNum KEYWORD2 58 | messageAvailable KEYWORD2 59 | messageFound KEYWORD2 60 | event KEYWORD2 61 | 62 | ####################################### 63 | # Struct and Enum (KEYWORD3) 64 | ####################################### 65 | 66 | imap_state KEYWORD3 67 | smtp_state KEYWORD3 68 | SMTPStatus KEYWORD3 69 | IMAPStatus KEYWORD3 70 | readymail_file_operating_mode KEYWORD3 71 | TLSHandshakeCallback KEYWORD3 72 | FileCallback KEYWORD3 73 | SMTPResponseCallback KEYWORD3 74 | SMTPCustomComandCallback KEYWORD3 75 | SMTPCommandResponse KEYWORD3 76 | IMAPCallbackData KEYWORD3 77 | IMAPDataCallback KEYWORD3 78 | IMAPCommandResponse KEYWORD3 79 | IMAPCustomComandCallback KEYWORD3 80 | IMAPResponseCallback KEYWORD3 -------------------------------------------------------------------------------- /src/core/ReadyClient.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 Suwatchai K. 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #pragma once 8 | 9 | #ifndef READY_CLIENT_H 10 | #define READY_CLIENT_H 11 | 12 | #include 13 | #if defined(ENABLE_READYCLIENT) && defined(READYCLIENT_SSL_CLIENT) 14 | 15 | class ReadyClient 16 | { 17 | private: 18 | std::vector ports; 19 | READYCLIENT_SSL_CLIENT *ssl_client = nullptr; 20 | 21 | public: 22 | /** ReadyClient class constructor. 23 | * 24 | * @param ssl_client The SSL client to use with ReadyClient. Currently supports ESP_SSLClient and ESP32 v3.x NeteorkClientSecure 25 | */ 26 | ReadyClient(READYCLIENT_SSL_CLIENT &ssl_client) { this->ssl_client = &ssl_client; } 27 | 28 | /** Add port and protocol. 29 | * 30 | * @param port The server port. 31 | * @param proto The readymail_protocol enums e.g. readymail_protocol_plain_text, readymail_protocol_ssl and readymail_protocol_tls. 32 | * The readymail_protocol_tls is the STARTTLS protocols 33 | */ 34 | void addPort(uint16_t port, readymail_protocol proto) 35 | { 36 | readymail_port_function port_func; 37 | port_func.port = port; 38 | port_func.protocol = proto; 39 | ports.push_back(port_func); 40 | } 41 | /** Clear all ports. 42 | * 43 | */ 44 | void clearPorts() { ports.clear(); } 45 | ~ReadyClient() {} 46 | 47 | // Internal used functions. 48 | READYCLIENT_SSL_CLIENT &getClient() { return *ssl_client; } 49 | 50 | // Internal used functions. 51 | void configPort(uint16_t port, bool &ssl, bool &startTLS) 52 | { 53 | for (size_t i = 0; i < ports.size(); i++) 54 | { 55 | if (port == ports[i].port) 56 | { 57 | ssl = !ports[i].protocol == readymail_protocol_plain_text ? true : false; 58 | #if defined(READYCLIENT_TYPE_1) 59 | ssl_client->enableSSL(ports[i].protocol == readymail_protocol_ssl); 60 | #elif defined(READYCLIENT_TYPE_2) 61 | if (ports[i].protocol == readymail_protocol_plain_text || ports[i].protocol == readymail_protocol_tls) 62 | ssl_client->setPlainStart(); 63 | #endif 64 | if (ports[i].protocol == readymail_protocol_tls) 65 | startTLS = true; 66 | else 67 | startTLS = false; 68 | } 69 | } 70 | } 71 | 72 | // Internal used functions. 73 | bool connectSSL() 74 | { 75 | #if defined(READYCLIENT_TYPE_1) 76 | return ssl_client->connectSSL(); 77 | #elif defined(READYCLIENT_TYPE_2) 78 | return ssl_client->startTLS(); 79 | #endif 80 | return false; 81 | } 82 | }; 83 | 84 | #endif 85 | #endif -------------------------------------------------------------------------------- /resources/docs/TROUBLESHOOTING.md: -------------------------------------------------------------------------------- 1 | # 🧯 TROUBLESHOOTING — ReadyMail 2 | 3 | This document outlines known issues across supported platforms and provides solutions or workarounds to help developers resolve common problems when using ReadyMail. 4 | 5 | --- 6 | 7 | ## 📚 Table of Contents 8 | 9 | - [ESP8266 Issues](#-esp8266-issues) 10 | - [ESP32 Issues](#-esp32-issues) 11 | - [ESP_SSLClient Issues](#-esp_sslclient-issues) 12 | - [IMAP Response Parsing](#-imap-response-parsing) 13 | 14 | --- 15 | 16 | ## ⚠️ ESP8266 Issues 17 | 18 | ### 🔧 Buffer Configuration 19 | 20 | ESP8266's `WiFiClientSecure` requires buffer tuning to avoid crashes during SSL connections. 21 | 22 | ```cpp 23 | ssl_client.setBufferSizes(1024, 1024); 24 | ``` 25 | 26 | This reduces memory usage and enables SSL fragmentation. 27 | 28 | ### 🧠 Heap Configuration 29 | 30 | Ensure proper MMU settings: 31 | 32 | - Arduino IDE: 33 | `Tools > MMU: 16KB cache + 48 KB IRAM and 2nd Heap (shared)` 34 | - PlatformIO: 35 | ```ini 36 | build_flags = -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED 37 | ``` 38 | 39 | ### ⚡ Power Supply 40 | 41 | Use a stable, low-noise power source with short, low-impedance cables to prevent brownouts during SSL handshake. 42 | 43 | --- 44 | 45 | ## ⚠️ ESP32 Issues 46 | 47 | ### 🐞 Plain Mode Hang (v3.x) 48 | 49 | Calling `setPlainStart()` on `WiFiClientSecure` in ESP32 v3.x may cause `connected()` to hang for 30 seconds due to lwIP select bug. 50 | 51 | **Workaround:** Avoid using plain mode or wait for upstream fix. 52 | 53 | ### 🔥 SSL/Plain Conflict 54 | 55 | If `setPlainStart()` is called while still connected in SSL mode, data corruption or unexpected errors may occur. 56 | 57 | **Recommendation:** Use SSL mode only unless protocol upgrade is explicitly required. 58 | 59 | --- 60 | 61 | ## ⚠️ ESP_SSLClient Issues 62 | 63 | ### 🧠 Memory Allocation Failures 64 | 65 | On devices like Renesas (UNO R4 WiFi) or SAMD (MKR WiFi 1010), TLS handshake may fail due to limited RAM. 66 | 67 | **Solution:** 68 | 69 | ```cpp 70 | ssl_client.setBufferSizes(1024, 1024); 71 | ``` 72 | 73 | Also ensure: 74 | 75 | ```cpp 76 | ssl_client.setClient(&basic_client); 77 | ssl_client.setInsecure(); 78 | ``` 79 | 80 | ### 🐢 Compilation Slowness 81 | 82 | Due to BearSSL's large C codebase, compilation may be slow in Arduino IDE. Consider disabling antivirus interference or using PlatformIO. 83 | 84 | --- 85 | 86 | ## ⚠️ IMAP Response Parsing 87 | 88 | ### 🧩 Chunked Header Limitations 89 | 90 | Some IMAP servers report incorrect decoded sizes for `text/plain` and `text/html`, causing `fileSize = 0`. 91 | 92 | **Impact:** Progress tracking and chunk indexing may be unreliable. 93 | 94 | **Recommendation:** Use `fileChunk().isComplete` to detect final chunk and avoid relying on `fileSize`. 95 | 96 | ### 🧠 Memory Constraints 97 | 98 | Parsing large multi-line IMAP responses may fail on low-RAM devices. Consider using `WiFiSSLClient` or limiting fetch size. 99 | 100 | --- 101 | 102 | ## 📄 License 103 | 104 | MIT License © 2025 Suwatchai K (Mobizt). 105 | See [LICENSE](LICENSE) for details. 106 | -------------------------------------------------------------------------------- /examples/Network/EthernetClient/EthernetClient.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The example to use WIZnet W5500 SPI Ethernet module and ESP32 3 | * to connect to SMTP server. 4 | * 5 | * The Ethernet libraries are used in this example. 6 | * 7 | * For proper network/SSL client and port selection, please see http://bit.ly/46Xu9Yk 8 | */ 9 | #include 10 | #include 11 | 12 | #define ENABLE_SMTP // Allows SMTP class and data 13 | #define ENABLE_DEBUG // Allows debugging 14 | #define READYMAIL_DEBUG_PORT Serial 15 | #include 16 | #include 17 | 18 | #include 19 | #define WIZNET_RESET_PIN 26 // Connect W5500 Reset pin to GPIO 26 of ESP32 (-1 for no reset pin assigned) 20 | #define WIZNET_CS_PIN 5 // Connect W5500 CS pin to GPIO 5 of ESP32 21 | #define WIZNET_MISO_PIN 19 // Connect W5500 MISO pin to GPIO 19 of ESP32 22 | #define WIZNET_MOSI_PIN 23 // Connect W5500 MOSI pin to GPIO 23 of ESP32 23 | #define WIZNET_SCLK_PIN 18 // Connect W5500 SCLK pin to GPIO 18 of ESP32 24 | 25 | uint8_t Eth_MAC[] = {0x02, 0xF0, 0x0D, 0xBE, 0xEF, 0x01}; 26 | 27 | EthernetClient basic_client; 28 | 29 | #define SMTP_HOST "_______" 30 | #define SMTP_PORT 465 31 | 32 | // https://github.com/mobizt/ESP_SSLClient 33 | ESP_SSLClient ssl_client; 34 | SMTPClient smtp(ssl_client); 35 | 36 | // For more information, see http://bit.ly/474niML 37 | void smtpCb(SMTPStatus status) 38 | { 39 | if (status.progress.available) 40 | ReadyMail.printf("ReadyMail[smtp][%d] Uploading file %s, %d %% completed\n", status.state, 41 | status.progress.filename.c_str(), status.progress.value); 42 | else 43 | ReadyMail.printf("ReadyMail[smtp][%d]%s\n", status.state, status.text.c_str()); 44 | } 45 | 46 | bool initEthernet() 47 | { 48 | ReadyMail.printf("Resetting Ethernet Board...\n"); 49 | 50 | #if defined(WIZNET_CS_PIN) 51 | Ethernet.init(WIZNET_CS_PIN); 52 | #endif 53 | 54 | #if defined(WIZNET_RESET_PIN) 55 | pinMode(WIZNET_RESET_PIN, OUTPUT); 56 | digitalWrite(WIZNET_RESET_PIN, HIGH); 57 | delay(200); 58 | digitalWrite(WIZNET_RESET_PIN, LOW); 59 | delay(50); 60 | digitalWrite(WIZNET_RESET_PIN, HIGH); 61 | #endif 62 | 63 | ReadyMail.printf("Starting Ethernet connection...\n"); 64 | Ethernet.begin(Eth_MAC); 65 | 66 | unsigned long ms = millis(); 67 | while (Ethernet.linkStatus() != LinkON && millis() - ms < 3000) 68 | { 69 | delay(100); 70 | } 71 | 72 | if (Ethernet.linkStatus() == LinkON) 73 | ReadyMail.printf("Connected with IP: %s\n", Ethernet.localIP().toString().c_str()); 74 | else 75 | ReadyMail.printf("Can't connect to network\n"); 76 | 77 | return Ethernet.linkStatus() == LinkON; 78 | } 79 | 80 | void setup() 81 | { 82 | Serial.begin(115200); 83 | Serial.println(); 84 | 85 | if (!initEthernet()) 86 | return; 87 | 88 | ssl_client.setClient(&basic_client); 89 | 90 | // If server SSL certificate verification was ignored for this SSL Client. 91 | // To verify root CA or server SSL cerificate, 92 | // please consult SSL Client documentation. 93 | ssl_client.setInsecure(); 94 | ssl_client.setBufferSizes(2048, 1024); 95 | ssl_client.setDebugLevel(1); 96 | 97 | Serial.println("ReadyMail, version " + String(READYMAIL_VERSION)); 98 | 99 | // In case ESP8266 crashes, please see https://bit.ly/48r4wSe 100 | 101 | smtp.connect(SMTP_HOST, SMTP_PORT, smtpCb); 102 | if (!smtp.isConnected()) 103 | return; 104 | } 105 | 106 | void loop() 107 | { 108 | } -------------------------------------------------------------------------------- /examples/Network/AutoClient/AutoClient.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The example to send simple text message using ReadyClient class which port can be changed at run time. 3 | * For proper network/SSL client and port selection, please see http://bit.ly/46Xu9Yk 4 | */ 5 | #include 6 | #include "Networks.h" 7 | #include 8 | 9 | #define ENABLE_SMTP 10 | #define ENABLE_DEBUG 11 | #define READYMAIL_DEBUG_PORT Serial 12 | #define USE_ESP_SSLCLIENT 13 | 14 | #if defined(USE_ESP_SSLCLIENT) 15 | #include 16 | #else 17 | #include 18 | #endif 19 | 20 | #if defined(USE_ESP_SSLCLIENT) 21 | #define READYCLIENT_SSL_CLIENT ESP_SSLClient 22 | #define READYCLIENT_TYPE_1 // TYPE 1 when using ESP_SSLClient 23 | #else 24 | #define READYCLIENT_SSL_CLIENT NetworkClientSecure 25 | #define READYCLIENT_TYPE_2 // TYPE 2 when using NetworkClientSecure 26 | #endif 27 | 28 | #include 29 | 30 | #define SMTP_HOST "_______" 31 | #define AUTHOR_EMAIL "_______" 32 | #define AUTHOR_PASSWORD "_______" 33 | #define RECIPIENT_EMAIL "_______" 34 | 35 | #define WIFI_SSID "_______" 36 | #define WIFI_PASSWORD "_______" 37 | 38 | #if defined(USE_ESP_SSLCLIENT) 39 | WiFiClient basic_client; 40 | ESP_SSLClient ssl_client; 41 | #else 42 | NetworkClientSecure ssl_client; 43 | #endif 44 | 45 | ReadyClient rClient(ssl_client); 46 | SMTPClient smtp(rClient); 47 | 48 | // For more information, see http://bit.ly/474niML 49 | void smtpCb(SMTPStatus status) 50 | { 51 | if (status.progress.available) 52 | ReadyMail.printf("ReadyMail[smtp][%d] Uploading file %s, %d %% completed\n", status.state, 53 | status.progress.filename.c_str(), status.progress.value); 54 | else 55 | ReadyMail.printf("ReadyMail[smtp][%d]%s\n", status.state, status.text.c_str()); 56 | } 57 | 58 | void sendEmail(int port) 59 | { 60 | 61 | smtp.connect(SMTP_HOST, port, smtpCb); 62 | if (!smtp.isConnected()) 63 | return; 64 | 65 | smtp.authenticate(AUTHOR_EMAIL, AUTHOR_PASSWORD, readymail_auth_password); 66 | if (!smtp.isAuthenticated()) 67 | return; 68 | 69 | SMTPMessage msg; 70 | msg.headers.add(rfc822_subject, "ReadyMail test via port " + String(port)); 71 | msg.headers.add(rfc822_from, "ReadyMail <" + String(AUTHOR_EMAIL) + ">"); 72 | msg.headers.add(rfc822_to, "User <" + String(RECIPIENT_EMAIL) + ">"); 73 | 74 | String bodyText = "Hello test."; 75 | msg.text.body(bodyText); 76 | msg.html.body("
" + bodyText + "
"); 77 | msg.timestamp = 1746013620; 78 | smtp.send(msg); 79 | } 80 | 81 | void setup() 82 | { 83 | Serial.begin(115200); 84 | Serial.println(); 85 | 86 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 87 | Serial.print("Connecting to Wi-Fi"); 88 | while (WiFi.status() != WL_CONNECTED) 89 | { 90 | Serial.print("."); 91 | delay(300); 92 | } 93 | Serial.println(); 94 | Serial.print("Connected with IP: "); 95 | Serial.println(WiFi.localIP()); 96 | Serial.println(); 97 | 98 | #if defined(USE_ESP_SSLCLIENT) 99 | ssl_client.setClient(&basic_client); 100 | #endif 101 | 102 | // If server SSL certificate verification was ignored for this SSL Client. 103 | // To verify root CA or server SSL cerificate, 104 | // please consult SSL Client documentation. 105 | ssl_client.setInsecure(); 106 | 107 | Serial.println("ReadyMail, version " + String(READYMAIL_VERSION)); 108 | 109 | rClient.addPort(465, readymail_protocol_ssl); 110 | rClient.addPort(587, readymail_protocol_tls); 111 | rClient.addPort(25, readymail_protocol_plain_text); 112 | 113 | sendEmail(465); // SSL 114 | 115 | sendEmail(587); // STARTTLS 116 | 117 | // If your server supports this protocol. 118 | sendEmail(25); // Plain Text 119 | } 120 | 121 | void loop() 122 | { 123 | } -------------------------------------------------------------------------------- /examples/Network/AutoPort/AutoPort.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The example to send simple text message which port can be changed at run time. 3 | * For proper network/SSL client and port selection, please see http://bit.ly/46Xu9Yk 4 | */ 5 | #include 6 | #include "Networks.h" 7 | #include 8 | #include 9 | 10 | #define ENABLE_SMTP 11 | #define ENABLE_DEBUG 12 | #define READYMAIL_DEBUG_PORT Serial 13 | #include 14 | 15 | #define SMTP_HOST "_______" 16 | #define AUTHOR_EMAIL "_______" 17 | #define AUTHOR_PASSWORD "_______" 18 | #define RECIPIENT_EMAIL "_______" 19 | 20 | #define WIFI_SSID "_______" 21 | #define WIFI_PASSWORD "_______" 22 | 23 | WiFiClient basic_client; 24 | ESP_SSLClient ssl_client; 25 | SMTPClient smtp(ssl_client); 26 | 27 | // For more information, see http://bit.ly/474niML 28 | void smtpCb(SMTPStatus status) 29 | { 30 | if (status.progress.available) 31 | ReadyMail.printf("ReadyMail[smtp][%d] Uploading file %s, %d %% completed\n", status.state, 32 | status.progress.filename.c_str(), status.progress.value); 33 | else 34 | ReadyMail.printf("ReadyMail[smtp][%d]%s\n", status.state, status.text.c_str()); 35 | } 36 | 37 | void sendEmail(int port) 38 | { 39 | // Starting SSL Client connection in SSL mode when port is 465. 40 | ssl_client.enableSSL(port == 465); 41 | 42 | // Send start TLS only when port is 587 43 | bool startTLS = port == 587; 44 | 45 | // Use SSL for SMTP connection option when port is 465 or 587 46 | bool ssl = (port == 465 || port == 587); 47 | 48 | // Anonymous function that handles TLS handshake process. 49 | auto startTLSCallback = [](bool &success) 50 | { success = ssl_client.connectSSL(); }; 51 | 52 | // Set/reset the TLS handshake callback and STARTTLS option. 53 | // Set when port is 587 otherwise reset. 54 | if (startTLS) 55 | smtp.setStartTLS((startTLSCallback), true); 56 | else 57 | smtp.setStartTLS(NULL, false); 58 | 59 | // In case ESP8266 crashes, please see https://bit.ly/48r4wSe 60 | 61 | // Everytime you call connect(), the previouse session will stop. 62 | // If you want to reuse the session, just skipping the connect() and authenticate(). 63 | smtp.connect(SMTP_HOST, port, smtpCb, ssl); 64 | if (!smtp.isConnected()) 65 | return; 66 | 67 | smtp.authenticate(AUTHOR_EMAIL, AUTHOR_PASSWORD, readymail_auth_password); 68 | if (!smtp.isAuthenticated()) 69 | return; 70 | 71 | SMTPMessage msg; 72 | msg.headers.add(rfc822_subject, "ReadyMail test via port " + String(port)); 73 | msg.headers.add(rfc822_from, "ReadyMail <" + String(AUTHOR_EMAIL) + ">"); 74 | msg.headers.add(rfc822_to, "User <" + String(RECIPIENT_EMAIL) + ">"); 75 | 76 | String bodyText = "Hello test."; 77 | msg.text.body(bodyText); 78 | msg.html.body("
" + bodyText + "
"); 79 | msg.timestamp = 1746013620; 80 | smtp.send(msg); 81 | } 82 | 83 | void setup() 84 | { 85 | Serial.begin(115200); 86 | Serial.println(); 87 | 88 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 89 | Serial.print("Connecting to Wi-Fi"); 90 | while (WiFi.status() != WL_CONNECTED) 91 | { 92 | Serial.print("."); 93 | delay(300); 94 | } 95 | Serial.println(); 96 | Serial.print("Connected with IP: "); 97 | Serial.println(WiFi.localIP()); 98 | Serial.println(); 99 | 100 | ssl_client.setClient(&basic_client); 101 | 102 | // If server SSL certificate verification was ignored for this SSL Client. 103 | // To verify root CA or server SSL cerificate, 104 | // please consult SSL Client documentation. 105 | ssl_client.setInsecure(); 106 | 107 | Serial.println("ReadyMail, version " + String(READYMAIL_VERSION)); 108 | 109 | sendEmail(465); // SSL 110 | 111 | sendEmail(587); // STARTTLS 112 | 113 | // If your server supports this protocol. 114 | sendEmail(25); // Plain Text 115 | } 116 | 117 | void loop() 118 | { 119 | } -------------------------------------------------------------------------------- /resources/docs/ADVANCED.md: -------------------------------------------------------------------------------- 1 | # ⚙️ Advanced Usage — ReadyMail 2 | 3 | This document provides in-depth information for developers who want to monitor, debug, or extend the behavior of ReadyMail using low-level processing callbacks and custom commands. 4 | 5 | --- 6 | 7 | ## 📚 Table of Contents 8 | 9 | - [📤 SMTP Processing Information](#-smtp-processing-information) 10 | - [🧪 SMTP Custom Command Processing Information](#-smtp-custom-command-processing-information) 11 | - [📥 IMAP Processing Information](#-imap-processing-information) 12 | - [✉️ IMAP Envelope and Body Data](#-imap-envelope-and-body-data) 13 | - [🧩 IMAP Custom Command Processing Information](#-imap-custom-command-processing-information) 14 | 15 | --- 16 | 17 | ## 📤 SMTP Processing Information 18 | 19 | The `SMTPStatus` struct provides detailed information about the current state of the SMTP process. You can access it via: 20 | 21 | - `SMTPClient::status()` — to poll the current status 22 | - `SMTPResponseCallback` — to receive real-time updates during sending 23 | 24 | ### 🔄 Callback Example 25 | 26 | ```cpp 27 | void smtpStatusCallback(SMTPStatus status) { 28 | if (status.progress.available) { 29 | ReadyMail.printf("State: %d, Uploading file %s, %d%% completed\n", 30 | status.state, 31 | status.progress.filename.c_str(), 32 | status.progress.value); 33 | } else { 34 | ReadyMail.printf("State: %d, %s\n", status.state, status.text.c_str()); 35 | } 36 | 37 | if (status.isComplete) { 38 | if (status.errorCode < 0) { 39 | ReadyMail.printf("Process Error: %d\n", status.errorCode); 40 | } else { 41 | ReadyMail.printf("Server Status: %d\n", status.statusCode); 42 | } 43 | } 44 | } 45 | ``` 46 | 47 | --- 48 | 49 | ## 🧪 SMTP Custom Command Processing Information 50 | 51 | You can send raw SMTP commands using `SMTPClient::sendCommand()` and receive responses via: 52 | 53 | - `SMTPCustomComandCallback` — for real-time response 54 | - `SMTPClient::commandResponse()` — to retrieve the last response 55 | 56 | ### 📦 `SMTPCommandResponse` Structure 57 | 58 | - `command` — The command sent (e.g. `"VRFY"`) 59 | - `text` — Server response text 60 | - `statusCode` — SMTP status code (e.g. 250, 550) 61 | - `errorCode` — Negative value indicates error 62 | 63 | --- 64 | 65 | ## 📥 IMAP Processing Information 66 | 67 | The `IMAPStatus` struct provides real-time updates and final results of IMAP operations. You can access it via: 68 | 69 | - `IMAPClient::status()` — to poll 70 | - `IMAPResponseCallback` — for real-time updates 71 | 72 | ### 🔄 Callback Example 73 | 74 | ```cpp 75 | void imapStatusCallback(IMAPStatus status) { 76 | ReadyMail.printf("State: %d, %s\n", status.state, status.text.c_str()); 77 | } 78 | ``` 79 | 80 | --- 81 | 82 | ## ✉️ IMAP Envelope and Body Data 83 | 84 | The `IMAPDataCallback` provides access to both envelope (headers) and body (attachments, inline content) during fetch operations. 85 | 86 | ### 📬 Envelope Example 87 | 88 | ```cpp 89 | auto dataCallback = [](IMAPCallbackData data) { 90 | if (data.event() == imap_data_event_fetch_envelope) { 91 | for (int i = 0; i < data.headerCount(); i++) { 92 | Serial.printf("%s: %s\n", data.getHeader(i).first.c_str(), data.getHeader(i).second.c_str()); 93 | } 94 | } 95 | }; 96 | ``` 97 | 98 | ### 📎 File Info 99 | 100 | During body fetch (`imap_data_event_fetch_body`), you can access: 101 | 102 | - `fileInfo().filename`, `mime`, `charset`, `transferEncoding`, `fileSize` 103 | - `fileChunk().data`, `index`, `size`, `isComplete` 104 | - `fileProgress().value` — percentage complete 105 | 106 | --- 107 | 108 | ## 🧩 IMAP Custom Command Processing Information 109 | 110 | Use `IMAPClient::sendCommand()` to send raw IMAP commands (e.g. `STORE`, `COPY`, `MOVE`, `CREATE`, `DELETE`) and receive responses via: 111 | 112 | - `IMAPCustomComandCallback` 113 | - `IMAPClient::commandResponse()` 114 | 115 | ### 📦 `IMAPCommandResponse` Structure 116 | 117 | - `command` — The command sent 118 | - `text` — Server response (untagged or full) 119 | - `isComplete` — True when response is complete 120 | - `errorCode` — Negative value indicates error 121 | 122 | --- 123 | 124 | For examples of advanced usage, see the `Command.ino`, `OTA.ino`, and `AutoClient.ino` examples in the `examples/` folder. 125 | -------------------------------------------------------------------------------- /src/core/Utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 Suwatchai K. 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef READY_UTILS_H 8 | #define READY_UTILS_H 9 | #include 10 | #include 11 | 12 | #if defined(ENABLE_IMAP) || defined(ENABLE_SMTP) 13 | class IPChecker 14 | { 15 | 16 | public: 17 | IPChecker() {} 18 | 19 | bool isValidHost(const char *s) 20 | { 21 | bool invalid = isValidIP(s) == 0 || (isValidIP(s) == -1 && !isValidDomain(s)); 22 | return !invalid; 23 | } 24 | 25 | int isValidIP(const char *ip) 26 | { 27 | int len = strlen(ip); 28 | if (len == 0) 29 | return -1; 30 | 31 | char segment[4]; 32 | int segIndex = 0, segCount = 0; 33 | 34 | for (int i = 0; i <= len; i++) 35 | { 36 | if (ip[i] == '.' || ip[i] == '\0') 37 | { 38 | if (segIndex == 0) 39 | return -1; 40 | 41 | segment[segIndex] = '\0'; 42 | int ret = valid_part(segment); 43 | if (ret <= 0) 44 | return ret; 45 | 46 | segIndex = 0; 47 | segCount++; 48 | } 49 | else if (isdigit(ip[i])) 50 | { 51 | if (segIndex >= 3) 52 | return -1; 53 | 54 | segment[segIndex++] = ip[i]; 55 | } 56 | else 57 | return -1; 58 | } 59 | 60 | return segCount == 4 ? 1 : 0; 61 | } 62 | 63 | int isValidDomain(const char *domain) 64 | { 65 | int len = strlen(domain); 66 | 67 | if (len < 3 || len > 253) 68 | return false; 69 | 70 | if (!isalnum(domain[0]) || !isalnum(domain[len - 1])) 71 | return false; 72 | 73 | int dot_count = 0; 74 | for (int i = 0; i < len; i++) 75 | { 76 | if (isalnum(domain[i]) || domain[i] == '-') 77 | continue; 78 | else if (domain[i] == '.') 79 | { 80 | dot_count++; 81 | if (i == 0 || domain[i - 1] == '.' || i == len - 1) 82 | return false; 83 | } 84 | else 85 | return false; 86 | } 87 | return dot_count > 0; 88 | } 89 | 90 | private: 91 | int valid_part(const char *s) 92 | { 93 | int len = strlen(s); 94 | if (len == 0 || len > 3 || (s[0] == '0' && len > 1)) 95 | return -1; 96 | 97 | for (int i = 0; i < len; i++) 98 | { 99 | if (!isdigit(s[i])) 100 | return -1; 101 | } 102 | 103 | int num = 0; 104 | for (int i = 0; i < len; i++) 105 | num = num * 10 + (s[i] - '0'); 106 | 107 | if (num == 0 || num > 255) 108 | return 0; 109 | 110 | return 1; 111 | } 112 | }; 113 | 114 | class NumString 115 | { 116 | public: 117 | NumString() {} 118 | ~NumString() {} 119 | template 120 | auto get(T val, bool negative = false) -> typename std::enable_if<(std::is_same::value), String>::type 121 | { 122 | String v; 123 | char buffer[21]; 124 | char *ndx = &buffer[sizeof(buffer) - 1]; 125 | *ndx = '\0'; 126 | do 127 | { 128 | *--ndx = val % 10 + '0'; 129 | val = val / 10; 130 | } while (val != 0); 131 | 132 | if (negative) 133 | v += '-'; 134 | v += ndx; 135 | return v; 136 | } 137 | 138 | template 139 | auto get(T val) -> typename std::enable_if<(std::is_same::value), String>::type 140 | { 141 | uint64_t value = val < 0 ? -val : val; 142 | return get(value, val < 0); 143 | } 144 | 145 | template 146 | auto get(T val) -> typename std::enable_if<(!std::is_same::value && !std::is_same::value), String>::type 147 | { 148 | return String(val); 149 | } 150 | 151 | uint32_t toNum(const char *str) 152 | { 153 | char *endptr; 154 | return (uint32_t)strtoul(str, &endptr, 10); 155 | } 156 | }; 157 | #endif 158 | #endif -------------------------------------------------------------------------------- /examples/Network/GSMClient/GSMClient.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The example to use LilyGO TTGO T-A7670 (ESP32 board with built-in SIMA7670) 3 | * to connect to SMTP server. 4 | * 5 | * The TinyGSMClient and ESP_SSLClient libraries are used in this example. 6 | * 7 | * For proper network/SSL client and port selection, please see http://bit.ly/46Xu9Yk 8 | */ 9 | #include 10 | #include 11 | 12 | #define ENABLE_SMTP // Allows SMTP class and data 13 | #define ENABLE_DEBUG // Allows debugging 14 | #define READYMAIL_DEBUG_PORT Serial 15 | #include 16 | #include 17 | 18 | #define TINY_GSM_MODEM_SIM7600 19 | #define SerialMon Serial 20 | #define SerialAT Serial1 21 | 22 | #define TINY_GSM_DEBUG SerialMon 23 | #define TINY_GSM_USE_GPRS true 24 | #define TINY_GSM_USE_WIFI false 25 | 26 | #define GSM_PIN "" 27 | const char apn[] = "YourAPN"; 28 | const char gprsUser[] = ""; 29 | const char gprsPass[] = ""; 30 | 31 | #define UART_BAUD 115200 32 | 33 | // LilyGO TTGO T-A7670 development board (ESP32 with SIMCom A7670) 34 | #define SIM_MODEM_RST 5 35 | #define SIM_MODEM_RST_LOW true // active LOW 36 | #define SIM_MODEM_RST_DELAY 200 37 | #define SIM_MODEM_TX 26 38 | #define SIM_MODEM_RX 27 39 | #include 40 | 41 | #define SMTP_HOST "_______" 42 | #define SMTP_PORT 465 43 | 44 | TinyGsm modem(SerialAT); 45 | TinyGsmClient basic_client(modem); 46 | 47 | // https://github.com/mobizt/ESP_SSLClient 48 | ESP_SSLClient ssl_client; 49 | SMTPClient smtp(ssl_client); 50 | 51 | // For more information, see http://bit.ly/474niML 52 | void smtpCb(SMTPStatus status) 53 | { 54 | if (status.progress.available) 55 | ReadyMail.printf("ReadyMail[smtp][%d] Uploading file %s, %d %% completed\n", status.state, 56 | status.progress.filename.c_str(), status.progress.value); 57 | else 58 | ReadyMail.printf("ReadyMail[smtp][%d]%s\n", status.state, status.text.c_str()); 59 | } 60 | 61 | bool initModem() 62 | { 63 | SerialMon.begin(115200); 64 | delay(10); 65 | 66 | // Resetting the modem 67 | #if defined(SIM_MODEM_RST) 68 | pinMode(SIM_MODEM_RST, SIM_MODEM_RST_LOW ? OUTPUT_OPEN_DRAIN : OUTPUT); 69 | digitalWrite(SIM_MODEM_RST, SIM_MODEM_RST_LOW); 70 | delay(100); 71 | digitalWrite(SIM_MODEM_RST, !SIM_MODEM_RST_LOW); 72 | delay(3000); 73 | digitalWrite(SIM_MODEM_RST, SIM_MODEM_RST_LOW); 74 | #endif 75 | 76 | DBG("Wait..."); 77 | delay(3000); 78 | 79 | SerialAT.begin(UART_BAUD, SERIAL_8N1, SIM_MODEM_RX, SIM_MODEM_TX); 80 | 81 | DBG("Initializing modem..."); 82 | if (!modem.init()) 83 | { 84 | DBG("Failed to restart modem, delaying 10s and retrying"); 85 | return false; 86 | } 87 | 88 | /** 89 | * 2 Automatic 90 | * 13 GSM Only 91 | * 14 WCDMA Only 92 | * 38 LTE Only 93 | */ 94 | modem.setNetworkMode(38); 95 | if (modem.waitResponse(10000L) != 1) 96 | { 97 | DBG(" setNetworkMode faill"); 98 | return false; 99 | } 100 | 101 | String name = modem.getModemName(); 102 | DBG("Modem Name:", name); 103 | 104 | String modemInfo = modem.getModemInfo(); 105 | DBG("Modem Info:", modemInfo); 106 | 107 | SerialMon.print("Waiting for network..."); 108 | if (!modem.waitForNetwork()) 109 | { 110 | SerialMon.println(" fail"); 111 | delay(10000); 112 | return false; 113 | } 114 | SerialMon.println(" success"); 115 | 116 | if (modem.isNetworkConnected()) 117 | SerialMon.println("Network connected"); 118 | 119 | return true; 120 | } 121 | 122 | void setup() 123 | { 124 | Serial.begin(115200); 125 | Serial.println(); 126 | 127 | if (!initModem()) 128 | return; 129 | 130 | ssl_client.setClient(&basic_client); 131 | 132 | // If server SSL certificate verification was ignored for this SSL Client. 133 | // To verify root CA or server SSL cerificate, 134 | // please consult SSL Client documentation. 135 | ssl_client.setInsecure(); 136 | ssl_client.setBufferSizes(2048, 1024); 137 | ssl_client.setDebugLevel(1); 138 | 139 | Serial.println("ReadyMail, version " + String(READYMAIL_VERSION)); 140 | 141 | // In case ESP8266 crashes, please see https://bit.ly/48r4wSe 142 | 143 | smtp.connect(SMTP_HOST, SMTP_PORT, smtpCb); 144 | if (!smtp.isConnected()) 145 | return; 146 | } 147 | 148 | void loop() 149 | { 150 | } -------------------------------------------------------------------------------- /examples/Reading/Command/Command.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The example to send the IMAP command to create the mailbox, copy the message to and delete the message and mailbox. 3 | * For proper network/SSL client and port selection, please see http://bit.ly/46Xu9Yk 4 | */ 5 | #include 6 | #include "Networks.h" 7 | 8 | #define ENABLE_IMAP // Allows IMAP class and data 9 | #define ENABLE_DEBUG // Allows debugging 10 | #define READYMAIL_DEBUG_PORT Serial 11 | #include 12 | 13 | #define IMAP_HOST "_______" 14 | #define IMAP_PORT 993 // SSL or 143 for PLAIN TEXT or STARTTLS 15 | #define AUTHOR_EMAIL "_______" 16 | #define AUTHOR_PASSWORD "_______" 17 | 18 | #define WIFI_SSID "_______" 19 | #define WIFI_PASSWORD "_______" 20 | 21 | #define READ_ONLY_MODE true 22 | #define AWAIT_MODE true 23 | #define MAX_SEARCH_RESULT 10 24 | #define RECENT_SORT true 25 | 26 | WiFiClientSecure ssl_client; 27 | IMAPClient imap(ssl_client); 28 | 29 | // For more information, see https://bit.ly/4h9JR7p 30 | void imapCb(IMAPStatus status) 31 | { 32 | ReadyMail.printf("ReadyMail[dbg][%d]%s\n", status.state, status.text.c_str()); 33 | } 34 | 35 | // For more information, see https://bit.ly/430BPan 36 | void cmdCb(IMAPCommandResponse response) 37 | { 38 | if (response.isComplete) 39 | ReadyMail.printf("ReadyMail[cmd][%d] %s\n", imap.status().state, 40 | response.errorCode < 0 ? "error" : "success"); 41 | else 42 | ReadyMail.printf("ReadyMail[cmd][%d] %s\n", imap.status().state, response.text.c_str()); 43 | } 44 | 45 | void setup() 46 | { 47 | Serial.begin(115200); 48 | Serial.println(); 49 | 50 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 51 | Serial.print("Connecting to Wi-Fi"); 52 | while (WiFi.status() != WL_CONNECTED) 53 | { 54 | Serial.print("."); 55 | delay(300); 56 | } 57 | Serial.println(); 58 | Serial.print("Connected with IP: "); 59 | Serial.println(WiFi.localIP()); 60 | Serial.println(); 61 | 62 | // If server SSL certificate verification was ignored for this ESP32 WiFiClientSecure. 63 | // To verify root CA or server SSL cerificate, 64 | // please consult your SSL client documentation. 65 | ssl_client.setInsecure(); 66 | 67 | // In case ESP8266 crashes, please see https://bit.ly/48r4wSe 68 | 69 | imap.connect(IMAP_HOST, IMAP_PORT, imapCb); 70 | if (!imap.isConnected()) 71 | return; 72 | 73 | imap.authenticate(AUTHOR_EMAIL, AUTHOR_PASSWORD, readymail_auth_password); 74 | if (!imap.isAuthenticated()) 75 | return; 76 | 77 | // List all mailboxes. 78 | imap.list(); 79 | 80 | for (int i = 0; i < imap.mailboxes.size(); i++) 81 | ReadyMail.printf("Attributes: %s, Delimiter: %s, Name: %s\n%s", 82 | imap.mailboxes[i][0].c_str(), imap.mailboxes[i][1].c_str(), 83 | imap.mailboxes[i][2].c_str(), (i == imap.mailboxes.size() - 1 ? "\n" : "")); 84 | 85 | // Select INBOX mailbox. 86 | bool success = imap.select("INBOX", READ_ONLY_MODE); 87 | if (!success) 88 | return; 89 | 90 | // Create "ReadyMail" mailbox. 91 | success = imap.sendCommand("CREATE ReadyMail", cmdCb); 92 | if (!success) 93 | return; 94 | 95 | // Copy the latest message from INBOX to ReadyMail 96 | success = imap.sendCommand("COPY " + String(imap.getMailbox().msgCount) + " ReadyMail", cmdCb); 97 | if (!success) 98 | return; 99 | 100 | // Select ReadyMail for read/write 101 | success = imap.select("ReadyMail", false); 102 | if (!success) 103 | return; 104 | 105 | // Delete the latest message in ReadyMail mailbox by assigning the \Deleted flag. 106 | success = imap.sendCommand("STORE " + String(imap.getMailbox().msgCount) + " +FLAGS (\\Deleted)", cmdCb); 107 | if (!success) 108 | return; 109 | 110 | // Permanently delete messages in ReadyMail that the \Deleted flag was assigned. 111 | success = imap.sendCommand("EXPUNGE", cmdCb); 112 | if (!success) 113 | return; 114 | 115 | // Before deleting ReadyMail mailbox that we are currently selected, 116 | // select other mailbox first e.g. INBOX to avoid server connection closing error especially in ESP32. 117 | success = imap.select("INBOX", false); 118 | if (!success) 119 | return; 120 | 121 | // Delete ReadyMail mailbox 122 | success = imap.sendCommand("DELETE ReadyMail", cmdCb); 123 | 124 | if (success) 125 | Serial.println("All commands are sent successfully"); 126 | } 127 | 128 | void loop() 129 | { 130 | } -------------------------------------------------------------------------------- /examples/Sending/SimpleText/SimpleText.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The example to send simple text message. 3 | * For proper network/SSL client and port selection, please see http://bit.ly/46Xu9Yk 4 | */ 5 | #include 6 | #include "Networks.h" 7 | 8 | #define ENABLE_SMTP // Allows SMTP class and data 9 | #define ENABLE_DEBUG // Allows debugging 10 | #define READYMAIL_DEBUG_PORT Serial 11 | 12 | // If message timestamp and/or Date header was not set, 13 | // the message timestamp will be taken from this source, otherwise 14 | // the default timestamp will be used. 15 | #if defined(ESP32) || defined(ESP8266) 16 | #define READYMAIL_TIME_SOURCE time(nullptr); // Or using WiFi.getTime() in WiFiNINA and WiFi101 firmwares. 17 | #endif 18 | 19 | #include 20 | 21 | #define SMTP_HOST "_______" 22 | #define SMTP_PORT 465 // SSL or 587 for STARTTLS 23 | #define AUTHOR_EMAIL "_______" 24 | #define AUTHOR_PASSWORD "_______" 25 | #define RECIPIENT_EMAIL "_______" 26 | #define CC_EMAIL "_______" 27 | #define BCC_EMAIL "_______" 28 | 29 | #define WIFI_SSID "_______" 30 | #define WIFI_PASSWORD "_______" 31 | 32 | #define SSL_MODE true 33 | #define AUTHENTICATION true 34 | #define NOTIFY "SUCCESS,FAILURE,DELAY" // Delivery Status Notification (if SMTP server supports this DSN extension) 35 | #define PRIORITY "High" // High, Normal, Low 36 | #define PRIORITY_NUM "1" // 1 = high, 3, 5 = low 37 | #define EMBED_MESSAGE false // To send the html or text content as attachment 38 | 39 | WiFiClientSecure ssl_client; 40 | SMTPClient smtp(ssl_client); 41 | 42 | // For more information, see http://bit.ly/474niML 43 | void smtpCb(SMTPStatus status) 44 | { 45 | if (status.progress.available) 46 | ReadyMail.printf("ReadyMail[smtp][%d] Uploading file %s, %d %% completed\n", status.state, 47 | status.progress.filename.c_str(), status.progress.value); 48 | else 49 | ReadyMail.printf("ReadyMail[smtp][%d]%s\n", status.state, status.text.c_str()); 50 | } 51 | 52 | void setup() 53 | { 54 | Serial.begin(115200); 55 | Serial.println(); 56 | 57 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 58 | Serial.print("Connecting to Wi-Fi"); 59 | while (WiFi.status() != WL_CONNECTED) 60 | { 61 | Serial.print("."); 62 | delay(300); 63 | } 64 | Serial.println(); 65 | Serial.print("Connected with IP: "); 66 | Serial.println(WiFi.localIP()); 67 | Serial.println(); 68 | 69 | // If server SSL certificate verification was ignored for this ESP32 WiFiClientSecure. 70 | // To verify root CA or server SSL cerificate, 71 | // please consult your SSL client documentation. 72 | ssl_client.setInsecure(); 73 | 74 | Serial.println("ReadyMail, version " + String(READYMAIL_VERSION)); 75 | 76 | // In case ESP8266 crashes, please see https://bit.ly/48r4wSe 77 | 78 | smtp.connect(SMTP_HOST, SMTP_PORT, smtpCb, SSL_MODE); 79 | if (!smtp.isConnected()) 80 | return; 81 | 82 | if (AUTHENTICATION) 83 | { 84 | smtp.authenticate(AUTHOR_EMAIL, AUTHOR_PASSWORD, readymail_auth_password); 85 | if (!smtp.isAuthenticated()) 86 | return; 87 | } 88 | 89 | SMTPMessage msg; 90 | msg.headers.add(rfc822_subject, "ReadyMail simple message"); 91 | 92 | // Using 'name ' or or 'email' for the from, sender and recipients. 93 | // The 'name' section of cc and bcc is ignored. 94 | 95 | // Multiple recipents can be added but only the first one of sender and from can be added. 96 | msg.headers.add(rfc822_from, "ReadyMail <" + String(AUTHOR_EMAIL) + ">"); 97 | // msg.headers.add(rfc822_sender, "ReadyMail <" + String(AUTHOR_EMAIL) + ">"); 98 | msg.headers.add(rfc822_to, "User <" + String(RECIPIENT_EMAIL) + ">"); 99 | 100 | // add some cc and bcc recipient emails. 101 | // msg.headers.add(rfc822_cc, CC_EMAIL); 102 | // msg.headers.add(rfc822_bcc, BCC_EMAIL); 103 | 104 | // Use addCustom to add custom header e.g. Imprtance and Priority. 105 | msg.headers.addCustom("Importance", PRIORITY); 106 | msg.headers.addCustom("X-MSMail-Priority", PRIORITY); 107 | msg.headers.addCustom("X-Priority", PRIORITY_NUM); 108 | 109 | String bodyText = "Hello everyone.\r\n"; 110 | bodyText += "こんにちは、日本の皆さん\r\n"; 111 | bodyText += "大家好,中国人\r\n"; 112 | bodyText += "Здравей български народе"; 113 | 114 | // Set the content, content transfer encoding or charset 115 | msg.text.body(bodyText); 116 | 117 | // content transfer encoding e.g. 7bit, base64, quoted-printable 118 | // 7bit is default for ascii text and quoted-printable is default set for non-ascii text. 119 | // msg.text.transferEncoding("base64"); 120 | 121 | bodyText.replace("\r\n", "
\r\n"); 122 | msg.html.body("
" + bodyText + "
"); 123 | 124 | // msg.html.transferEncoding("base64"); 125 | 126 | // With embedFile function, the html message will send as attachment. 127 | if (EMBED_MESSAGE) 128 | msg.html.embedFile(true, "msg.html", embed_message_type_attachment); 129 | 130 | // Set message timestamp (change this with current time) 131 | // See https://bit.ly/4nEuBlk 132 | msg.timestamp = 1746013620; 133 | 134 | smtp.send(msg, NOTIFY); 135 | } 136 | 137 | void loop() 138 | { 139 | } -------------------------------------------------------------------------------- /examples/Reading/FetchAsync/FetchAsync.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The example to fetch the latest message in the INBOX in async mode (non-await). 3 | * For proper network/SSL client and port selection, please see http://bit.ly/46Xu9Yk 4 | */ 5 | #include 6 | #include "Networks.h" 7 | 8 | #define ENABLE_IMAP // Allows IMAP class and data 9 | #define ENABLE_DEBUG // Allows debugging 10 | #define READYMAIL_DEBUG_PORT Serial 11 | #include 12 | 13 | #define IMAP_HOST "_______" 14 | #define IMAP_PORT 993 // SSL or 143 for PLAIN TEXT or STARTTLS 15 | #define AUTHOR_EMAIL "_______" 16 | #define AUTHOR_PASSWORD "_______" 17 | 18 | #define WIFI_SSID "_______" 19 | #define WIFI_PASSWORD "_______" 20 | 21 | #define READ_ONLY_MODE true 22 | #define SSL_MODE true 23 | #define AWAIT_MODE false 24 | #define MAX_CONTENT_SIZE 1024 * 1024 // Maximum size in bytes of the body parts (text and attachment) to be downloaded. 25 | 26 | WiFiClientSecure ssl_client; 27 | IMAPClient imap(ssl_client); 28 | 29 | unsigned long ms = 0; 30 | 31 | // For more information, see https://bit.ly/4h9JR7p 32 | void imapCb(IMAPStatus status) 33 | { 34 | ReadyMail.printf("ReadyMail[imap][%d]%s\n", status.state, status.text.c_str()); 35 | } 36 | 37 | // For more information, see https://bit.ly/3KLvz0y 38 | void dataCb(IMAPCallbackData &data) 39 | { 40 | // Showing envelope data. 41 | if (data.event() == imap_data_event_search || data.event() == imap_data_event_fetch_envelope) 42 | { 43 | // Show additional search info 44 | if (data.event() == imap_data_event_search) 45 | ReadyMail.printf("Showing Search result %d (%d) of %d from %d\n\n", data.messageIndex() + 1, 46 | data.messageNum(), data.messageAvailable(), data.messageFound()); 47 | 48 | // Headers data 49 | for (size_t i = 0; i < data.headerCount(); i++) 50 | ReadyMail.printf("%s: %s\n%s", data.getHeader(i).first.c_str(), 51 | data.getHeader(i).second.c_str(), i == data.headerCount() - 1 ? "\n" : ""); 52 | 53 | // Files data 54 | for (size_t i = 0; i < data.fileCount(); i++) 55 | { 56 | // You can fetch or download file while searching 57 | // (or disable it while fetching) with the following options. 58 | 59 | // To enable/disable this file for fetching. 60 | // data.fetchOption(i) = true; 61 | 62 | // To download if the fetch option is set. 63 | // data.setFileCallback(i, filecb, "/downloads"); 64 | ReadyMail.printf("name: %s, mime: %s, charset: %s, trans-enc: %s, size: %d, fetch: %s%s\n", 65 | data.fileInfo(i).filename.c_str(), data.fileInfo(i).mime.c_str(), data.fileInfo(i).charset.c_str(), 66 | data.fileInfo(i).transferEncoding.c_str(), data.fileInfo(i).fileSize, 67 | data.fetchOption(i) ? "yes" : "no", i == data.fileCount() - 1 ? "\n" : ""); 68 | } 69 | } 70 | else if (data.event() == imap_data_event_fetch_body) 71 | { 72 | // Showing the text file content 73 | if (data.fileInfo().mime == "text/html" || data.fileInfo().mime == "text/plain") 74 | { 75 | if (data.fileChunk().index == 0) // Fist chunk 76 | Serial.println("------------------"); 77 | Serial.print((char *)data.fileChunk().data); 78 | if (data.fileChunk().isComplete) // Last chunk 79 | Serial.println("------------------"); 80 | } 81 | else 82 | { 83 | // Showing the progress of current file fetching 84 | if (data.fileChunk().index == 0) 85 | Serial.print("Downloading"); 86 | if (data.fileProgress().available) 87 | Serial.print("."); 88 | if (data.fileChunk().isComplete) 89 | Serial.println(" complete"); 90 | } 91 | } 92 | } 93 | 94 | void setup() 95 | { 96 | Serial.begin(115200); 97 | Serial.println(); 98 | 99 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 100 | Serial.print("Connecting to Wi-Fi"); 101 | while (WiFi.status() != WL_CONNECTED) 102 | { 103 | Serial.print("."); 104 | delay(300); 105 | } 106 | Serial.println(); 107 | Serial.print("Connected with IP: "); 108 | Serial.println(WiFi.localIP()); 109 | Serial.println(); 110 | 111 | // If server SSL certificate verification was ignored for this ESP32 WiFiClientSecure. 112 | // To verify root CA or server SSL cerificate, 113 | // please consult your SSL client documentation. 114 | ssl_client.setInsecure(); 115 | 116 | Serial.println("ReadyMail, version " + String(READYMAIL_VERSION)); 117 | 118 | // In case ESP8266 crashes, please see https://bit.ly/48r4wSe 119 | 120 | imap.connect(IMAP_HOST, IMAP_PORT, imapCb, SSL_MODE, AWAIT_MODE); 121 | } 122 | 123 | void loop() 124 | { 125 | // This is required to be placed in the loop for async mode usage. 126 | imap.loop(); 127 | 128 | if (imap.isProcessing()) 129 | return; 130 | 131 | if (!imap.isConnected()) 132 | imap.connect(IMAP_HOST, IMAP_PORT, imapCb, SSL_MODE, AWAIT_MODE); 133 | 134 | if (imap.isConnected() && !imap.isAuthenticated()) 135 | imap.authenticate(AUTHOR_EMAIL, AUTHOR_PASSWORD, readymail_auth_password, AWAIT_MODE); 136 | 137 | if (imap.isAuthenticated() && imap.getMailbox().name != "INBOX") 138 | imap.select("INBOX", READ_ONLY_MODE); 139 | 140 | if ((millis() - ms > 120 * 1000 || ms == 0) && imap.isAuthenticated() && imap.getMailbox().name == "INBOX") 141 | { 142 | ms = millis(); 143 | imap.fetch(imap.getMailbox().msgCount, dataCb, NULL /* FileCallback */, AWAIT_MODE, MAX_CONTENT_SIZE); 144 | } 145 | } -------------------------------------------------------------------------------- /examples/Sending/InlineImage/InlineImage.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The example to send message with inline image. 3 | * For proper network/SSL client and port selection, please see http://bit.ly/46Xu9Yk 4 | */ 5 | #include 6 | #include "Networks.h" 7 | 8 | #define ENABLE_SMTP // Allows SMTP class and data 9 | #define ENABLE_DEBUG // Allows debugging 10 | #define READYMAIL_DEBUG_PORT Serial 11 | 12 | // If message timestamp and/or Date header was not set, 13 | // the message timestamp will be taken from this source, otherwise 14 | // the default timestamp will be used. 15 | #if defined(ESP32) || defined(ESP8266) 16 | #define READYMAIL_TIME_SOURCE time(nullptr); // Or using WiFi.getTime() in WiFiNINA and WiFi101 firmwares. 17 | #endif 18 | 19 | #include 20 | 21 | #define SMTP_HOST "_______" 22 | #define SMTP_PORT 465 // SSL or 587 for STARTTLS 23 | #define AUTHOR_EMAIL "_______" 24 | #define AUTHOR_PASSWORD "_______" 25 | #define RECIPIENT_EMAIL "_______" 26 | 27 | #define WIFI_SSID "_______" 28 | #define WIFI_PASSWORD "_______" 29 | 30 | WiFiClientSecure ssl_client; 31 | SMTPClient smtp(ssl_client); 32 | 33 | const char *orangeImg = "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAEASURBV" 34 | "Hhe7dEhAQAgEMBA2hCT6I+nABMnzsxuzdlDx3oDfxkSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMIT" 35 | "GGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITG" 36 | "GxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0jMBYxyLEpP9PqyAAAAAElFTkSuQmCC" 37 | "jhSDb5FKG9Q4"; 38 | 39 | const char *blueImg = "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAEASURBVHh" 40 | "e7dEhAQAgAMAwmmEJTyfwFOBiYub2Y6596Bhv4C9DYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkB" 41 | "hDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDY" 42 | "gyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJuZ7+qGdQMlUbAAAAAElFTkSuQmCC"; 43 | 44 | // For more information, see http://bit.ly/474niML 45 | void smtpCb(SMTPStatus status) 46 | { 47 | if (status.progress.available) 48 | ReadyMail.printf("ReadyMail[smtp][%d] Uploading file %s, %d %% completed\n", status.state, 49 | status.progress.filename.c_str(), status.progress.value); 50 | else 51 | ReadyMail.printf("ReadyMail[smtp][%d]%s\n", status.state, status.text.c_str()); 52 | } 53 | 54 | void addBlobAttachment(SMTPMessage &msg, const String &filename, const String &mime, const String &name, const uint8_t *blob, size_t size, const String &encoding = "", const String &cid = "") 55 | { 56 | Attachment attachment; 57 | attachment.filename = filename; 58 | attachment.mime = mime; 59 | attachment.name = name; 60 | // The inline content disposition. 61 | // Should be matched the image src's cid in html body 62 | attachment.content_id = cid; 63 | attachment.attach_file.blob = blob; 64 | attachment.attach_file.blob_size = size; 65 | // Specify only when content is already encoded. 66 | attachment.content_encoding = encoding; 67 | msg.attachments.add(attachment, cid.length() > 0 ? attach_type_inline : attach_type_attachment); 68 | } 69 | 70 | void setup() 71 | { 72 | Serial.begin(115200); 73 | Serial.println(); 74 | 75 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 76 | Serial.print("Connecting to Wi-Fi"); 77 | while (WiFi.status() != WL_CONNECTED) 78 | { 79 | Serial.print("."); 80 | delay(300); 81 | } 82 | Serial.println(); 83 | Serial.print("Connected with IP: "); 84 | Serial.println(WiFi.localIP()); 85 | Serial.println(); 86 | 87 | // If server SSL certificate verification was ignored for this ESP32 WiFiClientSecure. 88 | // To verify root CA or server SSL cerificate, 89 | // please consult your SSL client documentation. 90 | ssl_client.setInsecure(); 91 | 92 | Serial.println("ReadyMail, version " + String(READYMAIL_VERSION)); 93 | 94 | // In case ESP8266 crashes, please see https://bit.ly/48r4wSe 95 | 96 | smtp.connect(SMTP_HOST, SMTP_PORT, smtpCb); 97 | if (!smtp.isConnected()) 98 | return; 99 | 100 | smtp.authenticate(AUTHOR_EMAIL, AUTHOR_PASSWORD, readymail_auth_password); 101 | if (!smtp.isAuthenticated()) 102 | return; 103 | 104 | SMTPMessage msg; 105 | msg.headers.add(rfc822_subject, "ReadyMail message with inline image"); 106 | msg.headers.add(rfc822_from, "ReadyMail <" + String(AUTHOR_EMAIL) + ">"); 107 | // msg.headers.add(rfc822_sender, "ReadyMail <" + String(AUTHOR_EMAIL) + ">"); 108 | msg.headers.add(rfc822_to, "User <" + String(RECIPIENT_EMAIL) + ">"); 109 | 110 | String bodyText = "This message contains inline images."; 111 | msg.text.body(bodyText); 112 | msg.html.body("
" + bodyText + "

\"orange \"blue
"); 113 | 114 | // Set message timestamp (change this with current time) 115 | // See https://bit.ly/4nEuBlk 116 | msg.timestamp = 1746013620; 117 | 118 | addBlobAttachment(msg, "orange.png", "image/png", "orange.png", (const uint8_t *)orangeImg, strlen(orangeImg), "base64", "orange_image"); 119 | addBlobAttachment(msg, "blue.png", "image/png", "blue.png", (const uint8_t *)blueImg, strlen(blueImg), "base64", "blue_image"); 120 | smtp.send(msg); 121 | } 122 | 123 | void loop() 124 | { 125 | } -------------------------------------------------------------------------------- /examples/Reading/Search/Search.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The example to search the message in the selected mailbox. 3 | * For proper network/SSL client and port selection, please see http://bit.ly/46Xu9Yk 4 | */ 5 | #include 6 | #include "Networks.h" 7 | 8 | #define ENABLE_IMAP // Allows IMAP class and data 9 | #define ENABLE_DEBUG // Allows debugging 10 | #define READYMAIL_DEBUG_PORT Serial 11 | #include 12 | 13 | #define IMAP_HOST "_______" 14 | #define IMAP_PORT 993 // SSL or 143 for PLAIN TEXT or STARTTLS 15 | #define AUTHOR_EMAIL "_______" 16 | #define AUTHOR_PASSWORD "_______" 17 | 18 | #define WIFI_SSID "_______" 19 | #define WIFI_PASSWORD "_______" 20 | 21 | #define READ_ONLY_MODE true 22 | #define AWAIT_MODE true 23 | #define MAX_SEARCH_RESULT 10 24 | #define RECENT_SORT true 25 | 26 | WiFiClientSecure ssl_client; 27 | IMAPClient imap(ssl_client); 28 | 29 | // For more information, see https://bit.ly/4h9JR7p 30 | void imapCb(IMAPStatus status) 31 | { 32 | ReadyMail.printf("ReadyMail[imap][%d]%s\n", status.state, status.text.c_str()); 33 | } 34 | 35 | // For more information, see https://bit.ly/3KLvz0y 36 | void dataCb(IMAPCallbackData &data) 37 | { 38 | // Showing envelope data. 39 | if (data.event() == imap_data_event_search || data.event() == imap_data_event_fetch_envelope) 40 | { 41 | // Show additional search info 42 | if (data.event() == imap_data_event_search) 43 | ReadyMail.printf("Showing Search result %d (%d) of %d from %d\n\n", data.messageIndex() + 1, 44 | data.messageNum(), data.messageAvailable(), data.messageFound()); 45 | 46 | // Headers data 47 | for (size_t i = 0; i < data.headerCount(); i++) 48 | ReadyMail.printf("%s: %s\n%s", data.getHeader(i).first.c_str(), 49 | data.getHeader(i).second.c_str(), i == data.headerCount() - 1 ? "\n" : ""); 50 | 51 | // Files data 52 | for (size_t i = 0; i < data.fileCount(); i++) 53 | { 54 | // You can fetch or download file while searching 55 | // (or disable it while fetching) with the following options. 56 | 57 | // To enable/disable this file for fetching. 58 | // data.fetchOption(i) = true; 59 | 60 | // To download if the fetch option is set. 61 | // data.setFileCallback(i, filecb, "/downloads"); 62 | ReadyMail.printf("name: %s, mime: %s, charset: %s, trans-enc: %s, size: %d, fetch: %s%s\n", 63 | data.fileInfo(i).filename.c_str(), data.fileInfo(i).mime.c_str(), data.fileInfo(i).charset.c_str(), 64 | data.fileInfo(i).transferEncoding.c_str(), data.fileInfo(i).fileSize, 65 | data.fetchOption(i) ? "yes" : "no", i == data.fileCount() - 1 ? "\n" : ""); 66 | } 67 | } 68 | else if (data.event() == imap_data_event_fetch_body) 69 | { 70 | // While in search mode, this event will be called when data.fetchOption is true. 71 | 72 | // Showing the text file content 73 | if (data.fileInfo().mime == "text/html" || data.fileInfo().mime == "text/plain") 74 | { 75 | if (data.fileChunk().index == 0) // Fist chunk 76 | Serial.println("------------------"); 77 | Serial.print((char *)data.fileChunk().data); 78 | if (data.fileChunk().isComplete) // Last chunk 79 | Serial.println("------------------"); 80 | } 81 | else 82 | { 83 | // Showing the progress of current file fetching 84 | if (data.fileChunk().index == 0) 85 | Serial.print("Downloading"); 86 | if (data.fileProgress().available) 87 | Serial.print("."); 88 | if (data.fileChunk().isComplete) 89 | Serial.println(" complete"); 90 | } 91 | } 92 | } 93 | 94 | void setup() 95 | { 96 | Serial.begin(115200); 97 | Serial.println(); 98 | 99 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 100 | Serial.print("Connecting to Wi-Fi"); 101 | while (WiFi.status() != WL_CONNECTED) 102 | { 103 | Serial.print("."); 104 | delay(300); 105 | } 106 | Serial.println(); 107 | Serial.print("Connected with IP: "); 108 | Serial.println(WiFi.localIP()); 109 | Serial.println(); 110 | 111 | // If server SSL certificate verification was ignored for this ESP32 WiFiClientSecure. 112 | // To verify root CA or server SSL cerificate, 113 | // please consult your SSL client documentation. 114 | ssl_client.setInsecure(); 115 | 116 | Serial.println("ReadyMail, version " + String(READYMAIL_VERSION)); 117 | 118 | // In case ESP8266 crashes, please see https://bit.ly/48r4wSe 119 | 120 | imap.connect(IMAP_HOST, IMAP_PORT, imapCb); 121 | if (!imap.isConnected()) 122 | return; 123 | 124 | imap.authenticate(AUTHOR_EMAIL, AUTHOR_PASSWORD, readymail_auth_password); 125 | if (!imap.isAuthenticated()) 126 | return; 127 | 128 | // List all mailboxes. 129 | imap.list(); 130 | 131 | for (int i = 0; i < imap.mailboxes.size(); i++) 132 | ReadyMail.printf("Attributes: %s, Delimiter: %s, Name: %s\n%s", 133 | imap.mailboxes[i][0].c_str(), imap.mailboxes[i][1].c_str(), 134 | imap.mailboxes[i][2].c_str(), (i == imap.mailboxes.size() - 1 ? "\n" : "")); 135 | 136 | // Select INBOX mailbox. 137 | imap.select("INBOX", READ_ONLY_MODE); 138 | 139 | imap.search("SEARCH ALL" /* criteria */, MAX_SEARCH_RESULT, RECENT_SORT, dataCb, AWAIT_MODE); 140 | 141 | // If UID is provided in the search criteria, 142 | // the imap.searchResult() will contains the list of message UIDs otherwise the message numbers. 143 | for (int i = 0; i < imap.searchResult().size(); i++) 144 | { 145 | uint32_t uid_num = imap.searchResult()[i]; 146 | } 147 | } 148 | 149 | void loop() 150 | { 151 | } -------------------------------------------------------------------------------- /examples/Reading/Idling/Idling.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The example to listen to the changes in selected mailbox. 3 | * For proper network/SSL client and port selection, please see http://bit.ly/46Xu9Yk 4 | */ 5 | #include 6 | #include "NetworkClient.h" 7 | 8 | #define ENABLE_IMAP // Allows IMAP class and data 9 | #define ENABLE_DEBUG // Allows debugging 10 | #define READYMAIL_DEBUG_PORT Serial 11 | #include 12 | 13 | #define IMAP_HOST "_______" 14 | #define IMAP_PORT 993 // SSL or 143 for PLAIN TEXT or STARTTLS 15 | #define AUTHOR_EMAIL "_______" 16 | #define AUTHOR_PASSWORD "_______" 17 | 18 | #define WIFI_SSID "_______" 19 | #define WIFI_PASSWORD "_______" 20 | 21 | #define IDLE_MODE true 22 | #define IDLE_TIMEOUT 10 * 60 * 1000 23 | #define READ_ONLY_MODE true 24 | #define AWAIT_MODE true 25 | #define MAX_CONTENT_SIZE 1024 * 1024 // Maximum size in bytes of the body parts (text and attachment) to be downloaded. 26 | 27 | WiFiClientSecure ssl_client; 28 | IMAPClient imap(ssl_client); 29 | 30 | // For more information, see https://bit.ly/4h9JR7p 31 | void imapCb(IMAPStatus status) 32 | { 33 | ReadyMail.printf("ReadyMail[imap][%d]%s\n", status.state, status.text.c_str()); 34 | } 35 | 36 | // For more information, see https://bit.ly/3KLvz0y 37 | void dataCb(IMAPCallbackData &data) 38 | { 39 | // Showing envelope data. 40 | if (data.event() == imap_data_event_search || data.event() == imap_data_event_fetch_envelope) 41 | { 42 | // Show additional search info 43 | if (data.event() == imap_data_event_search) 44 | ReadyMail.printf("Showing Search result %d (%d) of %d from %d\n\n", data.messageIndex() + 1, 45 | data.messageNum(), data.messageAvailable(), data.messageFound()); 46 | 47 | // Headers data 48 | for (size_t i = 0; i < data.headerCount(); i++) 49 | ReadyMail.printf("%s: %s\n%s", data.getHeader(i).first.c_str(), data.getHeader(i).second.c_str(), i == data.headerCount() - 1 ? "\n" : ""); 50 | 51 | // Files data 52 | for (size_t i = 0; i < data.fileCount(); i++) 53 | { 54 | // You can fetch or download file while searching 55 | // (or disable it while fetching) with the following options. 56 | 57 | // To enable/disable this file for fetching. 58 | // data.fetchOption(i) = true; 59 | 60 | // To download if the fetch option is set. 61 | // data.setFileCallback(i, filecb, "/downloads"); 62 | ReadyMail.printf("name: %s, mime: %s, charset: %s, trans-enc: %s, size: %d, fetch: %s%s\n", 63 | data.fileInfo(i).filename.c_str(), data.fileInfo(i).mime.c_str(), data.fileInfo(i).charset.c_str(), 64 | data.fileInfo(i).transferEncoding.c_str(), data.fileInfo(i).fileSize, 65 | data.fetchOption(i) ? "yes" : "no", i == data.fileCount() - 1 ? "\n" : ""); 66 | } 67 | } 68 | else if (data.event() == imap_data_event_fetch_body) 69 | { 70 | // Showing the text file content 71 | if (data.fileInfo().mime == "text/html" || data.fileInfo().mime == "text/plain") 72 | { 73 | if (data.fileChunk().index == 0) // Fist chunk 74 | Serial.println("------------------"); 75 | Serial.print((char *)data.fileChunk().data); 76 | if (data.fileChunk().isComplete) // Last chunk 77 | Serial.println("------------------"); 78 | } 79 | else 80 | { 81 | // Showing the progress of current file fetching 82 | if (data.fileChunk().index == 0) 83 | Serial.print("Downloading"); 84 | if (data.fileProgress().available) 85 | Serial.print("."); 86 | if (data.fileChunk().isComplete) 87 | Serial.println(" complete"); 88 | } 89 | } 90 | } 91 | 92 | void setup() 93 | { 94 | Serial.begin(115200); 95 | Serial.println(); 96 | 97 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 98 | Serial.print("Connecting to Wi-Fi"); 99 | while (WiFi.status() != WL_CONNECTED) 100 | { 101 | Serial.print("."); 102 | delay(300); 103 | } 104 | Serial.println(); 105 | Serial.print("Connected with IP: "); 106 | Serial.println(WiFi.localIP()); 107 | Serial.println(); 108 | 109 | // If server SSL certificate verification was ignored for this ESP32 WiFiClientSecure. 110 | // To verify root CA or server SSL cerificate, 111 | // please consult your SSL client documentation. 112 | ssl_client.setInsecure(); 113 | 114 | Serial.println("ReadyMail, version " + String(READYMAIL_VERSION)); 115 | 116 | // In case ESP8266 crashes, please see https://bit.ly/48r4wSe 117 | 118 | imap.connect(IMAP_HOST, IMAP_PORT, imapCb); 119 | if (!imap.isConnected()) 120 | return; 121 | 122 | imap.authenticate(AUTHOR_EMAIL, AUTHOR_PASSWORD, readymail_auth_password); 123 | if (!imap.isAuthenticated()) 124 | return; 125 | 126 | // Select INBOX mailbox. 127 | imap.select("INBOX", READ_ONLY_MODE); 128 | } 129 | 130 | void loop() 131 | { 132 | // This is required to be placed in the loop for idling. 133 | imap.loop(IDLE_MODE, IDLE_TIMEOUT); 134 | 135 | if (imap.available()) 136 | { 137 | ReadyMail.printf("ReadyMail[imap][%d] %s\n", imap.status().state, imap.idleStatus().c_str()); 138 | // The imap.currentMessage() returns the message number that added/removed or flags updated from idling 139 | imap.fetch(imap.currentMessage(), dataCb, NULL /* FileCallback */, AWAIT_MODE, MAX_CONTENT_SIZE); 140 | 141 | // Note that, imap.idleStatus() returns the string in the following formats 142 | // [+] 123456 When the message number 123456 was added to the mailbox or new message is arrived. 143 | // [-] 123456 When the message number 123456 was removed or deleted from mailbox. 144 | // [=][/aaa /bbb ] 123456 When the message number 123456 status was changed as the existing flag /aaa and /bbb are assigned 145 | } 146 | } -------------------------------------------------------------------------------- /examples/Sending/SendAsync/SendAsync.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The example to send simple text message in async mode (non-await mode). 3 | * For proper network/SSL client and port selection, please see http://bit.ly/46Xu9Yk 4 | */ 5 | #include 6 | #include "Networks.h" 7 | 8 | #define ENABLE_SMTP // Allows SMTP class and data 9 | #define ENABLE_DEBUG // Allows debugging 10 | #define READYMAIL_DEBUG_PORT Serial 11 | 12 | // If message timestamp and/or Date header was not set, 13 | // the message timestamp will be taken from this source, otherwise 14 | // the default timestamp will be used. 15 | #if defined(ESP32) || defined(ESP8266) 16 | #define READYMAIL_TIME_SOURCE time(nullptr); // Or using WiFi.getTime() in WiFiNINA and WiFi101 firmwares. 17 | #endif 18 | 19 | #include 20 | 21 | #define SMTP_HOST "_______" 22 | #define SMTP_PORT 465 // SSL or 587 for STARTTLS 23 | #define AUTHOR_EMAIL "_______" 24 | #define AUTHOR_PASSWORD "_______" 25 | #define RECIPIENT_EMAIL "_______" 26 | 27 | #define WIFI_SSID "_______" 28 | #define WIFI_PASSWORD "_______" 29 | 30 | #define SSL_MODE true 31 | #define AWAIT_MODE false 32 | #define NOTIFY "SUCCESS,FAILURE,DELAY" // Delivery Status Notification (if SMTP server supports this DSN extension) 33 | 34 | const char *greenImg = "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAGHaVRYd" 35 | "FhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49J++7vycgaWQ9J1c1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCc/Pg0KPHg6eG1wbWV0YSB4bW" 36 | "xuczp4PSJhZG9iZTpuczptZXRhLyI+PHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj48cmR" 37 | "mOkRlc2NyaXB0aW9uIHJkZjphYm91dD0idXVpZDpmYWY1YmRkNS1iYTNkLTExZGEtYWQzMS1kMzNkNzUxODJmMWIiIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5h" 38 | "ZG9iZS5jb20vdGlmZi8xLjAvIj48dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPjwvcmRmOkRlc2NyaXB0aW9uPjwvcmRmOlJERj48L3g6e" 39 | "G1wbWV0YT4NCjw/eHBhY2tldCBlbmQ9J3cnPz4slJgLAAABAUlEQVR4Xu3RoQHAIBDAwKf7L8C04LsAEXcyNmv2nCHj+wfeMiTGkBhDYgyJMSTGkBhDYgyJMS" 40 | "TGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMST" 41 | "GkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTG" 42 | "kBhDYgyJMSTGkBhDYgyJMSTGkJgLfx8CYHc7t9oAAAAASUVORK5CYII="; 43 | 44 | WiFiClientSecure ssl_client; 45 | SMTPClient smtp(ssl_client); 46 | 47 | unsigned long ms = 0; 48 | 49 | // For more information, see http://bit.ly/474niML 50 | void smtpCb(SMTPStatus status) 51 | { 52 | if (status.progress.available) 53 | ReadyMail.printf("ReadyMail[smtp][%d] Uploading file %s, %d %% completed\n", status.state, 54 | status.progress.filename.c_str(), status.progress.value); 55 | else 56 | ReadyMail.printf("ReadyMail[smtp][%d]%s\n", status.state, status.text.c_str()); 57 | } 58 | 59 | void addBlobAttachment(SMTPMessage &msg, const String &filename, const String &mime, const String &name, const uint8_t *blob, size_t size, const String &encoding = "", const String &cid = "") 60 | { 61 | Attachment attachment; 62 | attachment.filename = filename; 63 | attachment.mime = mime; 64 | attachment.name = name; 65 | // The inline content disposition. 66 | // Should be matched the image src's cid in html body 67 | attachment.content_id = cid; 68 | attachment.attach_file.blob = blob; 69 | attachment.attach_file.blob_size = size; 70 | // Specify only when content is already encoded. 71 | attachment.content_encoding = encoding; 72 | msg.attachments.add(attachment, cid.length() > 0 ? attach_type_inline : attach_type_attachment); 73 | } 74 | 75 | void sendMesssage() 76 | { 77 | SMTPMessage &msg = smtp.getMessage(); 78 | msg.headers.add(rfc822_subject, "ReadyMail Hello message"); 79 | msg.headers.add(rfc822_from, "ReadyMail <" + String(AUTHOR_EMAIL) + ">"); 80 | // msg.headers.add(rfc822_sender, "ReadyMail <" + String(AUTHOR_EMAIL) + ">"); 81 | msg.headers.add(rfc822_to, "User <" + String(RECIPIENT_EMAIL) + ">"); 82 | 83 | String bodyText = "Hello everyone."; 84 | msg.text.body(bodyText); 85 | msg.html.body("
" + bodyText + "
"); 86 | 87 | // Set message timestamp (change this with current time) 88 | // See https://bit.ly/4nEuBlk 89 | msg.timestamp = 1746013620; 90 | 91 | addBlobAttachment(msg, "green.png", "image/png", "green.png", (const uint8_t *)greenImg, strlen(greenImg), "base64"); 92 | smtp.send(msg, NOTIFY, AWAIT_MODE); 93 | } 94 | 95 | void setup() 96 | { 97 | Serial.begin(115200); 98 | Serial.println(); 99 | 100 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 101 | Serial.print("Connecting to Wi-Fi"); 102 | while (WiFi.status() != WL_CONNECTED) 103 | { 104 | Serial.print("."); 105 | delay(300); 106 | } 107 | Serial.println(); 108 | Serial.print("Connected with IP: "); 109 | Serial.println(WiFi.localIP()); 110 | Serial.println(); 111 | 112 | // If server SSL certificate verification was ignored for this ESP32 WiFiClientSecure. 113 | // To verify root CA or server SSL cerificate, 114 | // please consult your SSL client documentation. 115 | ssl_client.setInsecure(); 116 | 117 | Serial.println("ReadyMail, version " + String(READYMAIL_VERSION)); 118 | 119 | // In case ESP8266 crashes, please see https://bit.ly/48r4wSe 120 | 121 | // Setting AWAIT_MODE parameter with false 122 | smtp.connect(SMTP_HOST, SMTP_PORT, smtpCb, SSL_MODE, AWAIT_MODE); 123 | } 124 | 125 | void loop() 126 | { 127 | // This is required to be placed in the loop for async mode usage. 128 | smtp.loop(); 129 | 130 | if (smtp.isProcessing()) 131 | return; 132 | 133 | if (!smtp.isConnected()) 134 | smtp.connect(SMTP_HOST, SMTP_PORT, smtpCb, SSL_MODE, AWAIT_MODE); 135 | 136 | if (smtp.isConnected() && !smtp.isAuthenticated()) 137 | smtp.authenticate(AUTHOR_EMAIL, AUTHOR_PASSWORD, readymail_auth_password, AWAIT_MODE); 138 | 139 | if ((millis() - ms > 20 * 1000 || ms == 0) && smtp.isAuthenticated()) 140 | { 141 | ms = millis(); 142 | sendMesssage(); 143 | } 144 | } -------------------------------------------------------------------------------- /examples/Reading/Download/Download.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The example to fetch the latest message in the INBOX and download. 3 | * For proper network/SSL client and port selection, please see http://bit.ly/46Xu9Yk 4 | */ 5 | #include 6 | #include "Networks.h" 7 | 8 | #define ENABLE_IMAP // Allows IMAP class and data 9 | #define ENABLE_DEBUG // Allows debugging 10 | #define READYMAIL_DEBUG_PORT Serial 11 | #define ENABLE_FS // Allow filesystem integration 12 | #include 13 | 14 | #define IMAP_HOST "_______" 15 | #define IMAP_PORT 993 // SSL or 143 for PLAIN TEXT or STARTTLS 16 | #define AUTHOR_EMAIL "_______" 17 | #define AUTHOR_PASSWORD "_______" 18 | 19 | #define WIFI_SSID "_______" 20 | #define WIFI_PASSWORD "_______" 21 | 22 | #define READ_ONLY_MODE true 23 | #define AWAIT_MODE true 24 | #define MAX_CONTENT_SIZE 1024 * 1024 // Maximum size in bytes of the body parts (text and attachment) to be downloaded. 25 | #define BASE_DOWNLOAD_FOLDER "readymail" 26 | 27 | WiFiClientSecure ssl_client; 28 | IMAPClient imap(ssl_client); 29 | 30 | // For more information, see https://bit.ly/4h9JR7p 31 | void imapCb(IMAPStatus status) 32 | { 33 | ReadyMail.printf("ReadyMail[imap][%d]%s\n", status.state, status.text.c_str()); 34 | } 35 | 36 | // For more information, see https://bit.ly/3KLvz0y 37 | void dataCb(IMAPCallbackData &data) 38 | { 39 | // Showing envelope data. 40 | if (data.event() == imap_data_event_search || data.event() == imap_data_event_fetch_envelope) 41 | { 42 | // Show additional search info 43 | if (data.event() == imap_data_event_search) 44 | ReadyMail.printf("Showing Search result %d (%d) of %d from %d\n\n", data.messageIndex() + 1, 45 | data.messageNum(), data.messageAvailable(), data.messageFound()); 46 | 47 | // Headers data 48 | for (size_t i = 0; i < data.headerCount(); i++) 49 | ReadyMail.printf("%s: %s\n%s", data.getHeader(i).first.c_str(), 50 | data.getHeader(i).second.c_str(), i == data.headerCount() - 1 ? "\n" : ""); 51 | 52 | // Files data 53 | for (size_t i = 0; i < data.fileCount(); i++) 54 | { 55 | // You can fetch or download file while searching 56 | // (or disable it while fetching) with the following options. 57 | 58 | // To enable/disable this file for fetching. 59 | // data.fetchOption(i) = true; 60 | 61 | // To download if the fetch option is set. 62 | // data.setFileCallback(i, filecb, "/downloads"); 63 | ReadyMail.printf("name: %s, mime: %s, charset: %s, trans-enc: %s, size: %d, fetch: %s%s\n", 64 | data.fileInfo(i).filename.c_str(), data.fileInfo(i).mime.c_str(), data.fileInfo(i).charset.c_str(), 65 | data.fileInfo(i).transferEncoding.c_str(), data.fileInfo(i).fileSize, 66 | data.fetchOption(i) ? "yes" : "no", i == data.fileCount() - 1 ? "\n" : ""); 67 | } 68 | } 69 | else if (data.event() == imap_data_event_fetch_body) 70 | { 71 | // Showing the text file content 72 | if (data.fileInfo().mime == "text/html" || data.fileInfo().mime == "text/plain") 73 | { 74 | if (data.fileChunk().index == 0) // Fist chunk 75 | Serial.println("------------------"); 76 | Serial.print((char *)data.fileChunk().data); 77 | if (data.fileChunk().isComplete) // Last chunk 78 | Serial.println("------------------"); 79 | } 80 | else 81 | { 82 | // Showing the progress of current file fetching 83 | if (data.fileChunk().index == 0) 84 | Serial.print("Downloading"); 85 | if (data.fileProgress().available) 86 | Serial.print("."); 87 | if (data.fileChunk().isComplete) 88 | Serial.println(" complete"); 89 | } 90 | } 91 | } 92 | 93 | #if defined(ENABLE_FS) 94 | #include 95 | File myFile; 96 | #if defined(ESP32) 97 | #include 98 | #endif 99 | #define MY_FS SPIFFS 100 | 101 | void fileCb(File &file, const char *filename, readymail_file_operating_mode mode) 102 | { 103 | switch (mode) 104 | { 105 | case readymail_file_mode_open_read: 106 | myFile = MY_FS.open(filename, FILE_OPEN_MODE_READ); 107 | break; 108 | case readymail_file_mode_open_write: 109 | myFile = MY_FS.open(filename, FILE_OPEN_MODE_WRITE); 110 | break; 111 | case readymail_file_mode_open_append: 112 | myFile = MY_FS.open(filename, FILE_OPEN_MODE_APPEND); 113 | break; 114 | case readymail_file_mode_remove: 115 | MY_FS.remove(filename); 116 | break; 117 | default: 118 | break; 119 | } 120 | 121 | // This is required by library to get the file object 122 | // that uses in its read/write processes. 123 | file = myFile; 124 | } 125 | #endif 126 | 127 | void setup() 128 | { 129 | Serial.begin(115200); 130 | Serial.println(); 131 | 132 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 133 | Serial.print("Connecting to Wi-Fi"); 134 | while (WiFi.status() != WL_CONNECTED) 135 | { 136 | Serial.print("."); 137 | delay(300); 138 | } 139 | Serial.println(); 140 | Serial.print("Connected with IP: "); 141 | Serial.println(WiFi.localIP()); 142 | Serial.println(); 143 | 144 | // If server SSL certificate verification was ignored for this ESP32 WiFiClientSecure. 145 | // To verify root CA or server SSL cerificate, 146 | // please consult your SSL client documentation. 147 | ssl_client.setInsecure(); 148 | 149 | MY_FS.begin(); 150 | 151 | // In case ESP8266 crashes, please see https://bit.ly/48r4wSe 152 | 153 | imap.connect(IMAP_HOST, IMAP_PORT, imapCb); 154 | if (!imap.isConnected()) 155 | return; 156 | 157 | imap.authenticate(AUTHOR_EMAIL, AUTHOR_PASSWORD, readymail_auth_password); 158 | if (!imap.isAuthenticated()) 159 | return; 160 | 161 | // Select INBOX mailbox. 162 | // If READ_ONLY_MODE is false, the flag /Seen will set to the fetched message. 163 | imap.select("INBOX", READ_ONLY_MODE); 164 | 165 | // Fetch the latest message in INBOX. 166 | imap.fetch(imap.getMailbox().msgCount, dataCb, fileCb, AWAIT_MODE, MAX_CONTENT_SIZE, BASE_DOWNLOAD_FOLDER); 167 | 168 | // To fetch message with UID, use imap.fetchUID instead. 169 | } 170 | 171 | void loop() 172 | { 173 | } -------------------------------------------------------------------------------- /examples/Sending/ESP32Camera/ESP32Camera.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The example to send image from ESP32 camera. 3 | * For proper network/SSL client and port selection, please see http://bit.ly/46Xu9Yk 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include "esp_camera.h" 9 | 10 | // =================== 11 | // Select camera model 12 | // =================== 13 | // #define CAMERA_MODEL_WROVER_KIT // Has PSRAM 14 | #define CAMERA_MODEL_ESP_EYE // Has PSRAM 15 | // #define CAMERA_MODEL_ESP32S3_EYE // Has PSRAM 16 | // #define CAMERA_MODEL_M5STACK_PSRAM // Has PSRAM 17 | // #define CAMERA_MODEL_M5STACK_V2_PSRAM // M5Camera version B Has PSRAM 18 | // #define CAMERA_MODEL_M5STACK_WIDE // Has PSRAM 19 | // #define CAMERA_MODEL_M5STACK_ESP32CAM // No PSRAM 20 | // #define CAMERA_MODEL_M5STACK_UNITCAM // No PSRAM 21 | // #define CAMERA_MODEL_AI_THINKER // Has PSRAM 22 | // #define CAMERA_MODEL_TTGO_T_JOURNAL // No PSRAM 23 | // #define CAMERA_MODEL_XIAO_ESP32S3 // Has PSRAM 24 | // ** Espressif Internal Boards ** 25 | // #define CAMERA_MODEL_ESP32_CAM_BOARD 26 | // #define CAMERA_MODEL_ESP32S2_CAM_BOARD 27 | // #define CAMERA_MODEL_ESP32S3_CAM_LCD 28 | // #define CAMERA_MODEL_DFRobot_FireBeetle2_ESP32S3 // Has PSRAM 29 | // #define CAMERA_MODEL_DFRobot_Romeo_ESP32S3 // Has PSRAM 30 | #include "camera_pins.h" 31 | 32 | #define ENABLE_SMTP // Allows SMTP class and data 33 | #define ENABLE_DEBUG // Allows debugging 34 | #define READYMAIL_DEBUG_PORT Serial 35 | 36 | // If message timestamp and/or Date header was not set, 37 | // the message timestamp will be taken from this source, otherwise 38 | // the default timestamp will be used. 39 | #if defined(ESP32) || defined(ESP8266) 40 | #define READYMAIL_TIME_SOURCE time(nullptr); // Or using WiFi.getTime() in WiFiNINA and WiFi101 firmwares. 41 | #endif 42 | 43 | #include 44 | 45 | #define SMTP_HOST "_______" 46 | #define SMTP_PORT 465 // SSL or 587 for STARTTLS 47 | #define AUTHOR_EMAIL "_______" 48 | #define AUTHOR_PASSWORD "_______" 49 | #define RECIPIENT_EMAIL "_______" 50 | 51 | #define WIFI_SSID "_______" 52 | #define WIFI_PASSWORD "_______" 53 | 54 | WiFiClientSecure ssl_client; 55 | SMTPClient smtp(ssl_client); 56 | 57 | // For more information, see http://bit.ly/474niML 58 | void smtpCb(SMTPStatus status) 59 | { 60 | if (status.progress.available) 61 | ReadyMail.printf("ReadyMail[smtp][%d] Uploading file %s, %d %% completed\n", status.state, 62 | status.progress.filename.c_str(), status.progress.value); 63 | else 64 | ReadyMail.printf("ReadyMail[smtp][%d]%s\n", status.state, status.text.c_str()); 65 | } 66 | 67 | void addBlobAttachment(SMTPMessage &msg, const String &filename, const String &mime, const String &name, const uint8_t *blob, size_t size, const String &encoding = "", const String &cid = "") 68 | { 69 | Attachment attachment; 70 | attachment.filename = filename; 71 | attachment.mime = mime; 72 | attachment.name = name; 73 | // The inline content disposition. 74 | // Should be matched the image src's cid in html body 75 | attachment.content_id = cid; 76 | attachment.attach_file.blob = blob; 77 | attachment.attach_file.blob_size = size; 78 | // Specify only when content is already encoded. 79 | attachment.content_encoding = encoding; 80 | msg.attachments.add(attachment, cid.length() > 0 ? attach_type_inline : attach_type_attachment); 81 | } 82 | 83 | void setup() 84 | { 85 | Serial.begin(115200); 86 | Serial.println(); 87 | 88 | camera_config_t camCfg; 89 | camCfg.ledc_channel = LEDC_CHANNEL_0; 90 | camCfg.ledc_timer = LEDC_TIMER_0; 91 | camCfg.pin_d0 = Y2_GPIO_NUM; 92 | camCfg.pin_d1 = Y3_GPIO_NUM; 93 | camCfg.pin_d2 = Y4_GPIO_NUM; 94 | camCfg.pin_d3 = Y5_GPIO_NUM; 95 | camCfg.pin_d4 = Y6_GPIO_NUM; 96 | camCfg.pin_d5 = Y7_GPIO_NUM; 97 | camCfg.pin_d6 = Y8_GPIO_NUM; 98 | camCfg.pin_d7 = Y9_GPIO_NUM; 99 | camCfg.pin_xclk = XCLK_GPIO_NUM; 100 | camCfg.pin_pclk = PCLK_GPIO_NUM; 101 | camCfg.pin_vsync = VSYNC_GPIO_NUM; 102 | camCfg.pin_href = HREF_GPIO_NUM; 103 | camCfg.pin_sscb_sda = SIOD_GPIO_NUM; 104 | camCfg.pin_sscb_scl = SIOC_GPIO_NUM; 105 | camCfg.pin_pwdn = PWDN_GPIO_NUM; 106 | camCfg.pin_reset = RESET_GPIO_NUM; 107 | camCfg.xclk_freq_hz = 20000000; 108 | camCfg.pixel_format = PIXFORMAT_JPEG; 109 | camCfg.frame_size = FRAMESIZE_QXGA; 110 | camCfg.jpeg_quality = 10; 111 | camCfg.fb_count = 2; 112 | 113 | // camera init 114 | esp_err_t err = esp_camera_init(&camCfg); 115 | if (err != ESP_OK) 116 | { 117 | Serial.printf("Camera init failed with error 0x%x", err); 118 | return; 119 | } 120 | 121 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 122 | Serial.print("Connecting to Wi-Fi"); 123 | while (WiFi.status() != WL_CONNECTED) 124 | { 125 | Serial.print("."); 126 | delay(300); 127 | } 128 | Serial.println(); 129 | Serial.print("Connected with IP: "); 130 | Serial.println(WiFi.localIP()); 131 | Serial.println(); 132 | 133 | // If server SSL certificate verification was ignored for this ESP32 WiFiClientSecure. 134 | // To verify root CA or server SSL cerificate, 135 | // please consult your SSL client documentation. 136 | ssl_client.setInsecure(); 137 | 138 | Serial.println("ReadyMail, version " + String(READYMAIL_VERSION)); 139 | 140 | smtp.connect(SMTP_HOST, SMTP_PORT, smtpCb); 141 | if (!smtp.isConnected()) 142 | return; 143 | 144 | smtp.authenticate(AUTHOR_EMAIL, AUTHOR_PASSWORD, readymail_auth_password); 145 | if (!smtp.isAuthenticated()) 146 | return; 147 | 148 | SMTPMessage msg; 149 | msg.headers.add(rfc822_subject, "ReadyMail SP32 camera"); 150 | msg.headers.add(rfc822_from, "ReadyMail <" + String(AUTHOR_EMAIL) + ">"); 151 | // msg.headers.add(rfc822_sender, "ReadyMail <" + String(AUTHOR_EMAIL) + ">"); 152 | msg.headers.add(rfc822_to, "User <" + String(RECIPIENT_EMAIL) + ">"); 153 | 154 | String bodyText = "This is image from ESP32 camera."; 155 | msg.text.body(bodyText); 156 | msg.html.body("
" + bodyText + "

\"ESP32
"); 157 | 158 | // Set message timestamp (change this with current time) 159 | // See https://bit.ly/4nEuBlk 160 | msg.timestamp = 1746013620; 161 | 162 | camera_fb_t *fb = esp_camera_fb_get(); 163 | addBlobAttachment(msg, "camera.jpg", "image/jpg", "camera.jpg", fb->buf, fb->len, "", "camera_image"); 164 | smtp.send(msg); 165 | } 166 | 167 | void loop() 168 | { 169 | } -------------------------------------------------------------------------------- /examples/Sending/StaticText/StaticText.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The example to send simple text message from file of flash. 3 | * For proper network/SSL client and port selection, please see http://bit.ly/46Xu9Yk 4 | */ 5 | #include 6 | #include "Networks.h" 7 | 8 | #define ENABLE_SMTP // Allows SMTP class and data 9 | #define ENABLE_DEBUG // Allows debugging 10 | #define READYMAIL_DEBUG_PORT Serial 11 | #define ENABLE_FS // Allow filesystem integration 12 | 13 | // If message timestamp and/or Date header was not set, 14 | // the message timestamp will be taken from this source, otherwise 15 | // the default timestamp will be used. 16 | #if defined(ESP32) || defined(ESP8266) 17 | #define READYMAIL_TIME_SOURCE time(nullptr); // Or using WiFi.getTime() in WiFiNINA and WiFi101 firmwares. 18 | #endif 19 | 20 | #include 21 | 22 | #define SMTP_HOST "_______" 23 | #define SMTP_PORT 465 // SSL or 587 for STARTTLS 24 | #define AUTHOR_EMAIL "_______" 25 | #define AUTHOR_PASSWORD "_______" 26 | #define RECIPIENT_EMAIL "_______" 27 | 28 | #define WIFI_SSID "_______" 29 | #define WIFI_PASSWORD "_______" 30 | 31 | WiFiClientSecure ssl_client; 32 | SMTPClient smtp(ssl_client); 33 | 34 | #if defined(ENABLE_FS) 35 | #include 36 | File myFile; 37 | #if defined(ESP32) 38 | #include 39 | #endif 40 | #define MY_FS SPIFFS 41 | 42 | const char *static_text1 = "A rabies-like disease spreads across the planet, transforming people into aggressive creatures. Manel takes refuge at " 43 | "home with his cat, using his wits to survive. Soon, they’ll have to leave for food, looking for safe places on land " 44 | "and sea. Apocalypse Z: The Beginning of the End is a story of survival, both physical and emotional, with action, " 45 | "tension, a rabid infection... and a grumpy cat."; 46 | 47 | const char *static_text2 = "Speak No Evil is a 2024 American psychological horror thriller film written and directed by James Watkins. A remake " 48 | "of the 2022 Danish-Dutch film of the same name, the film stars James McAvoy, Mackenzie Davis, Aisling Franciosi, Alix " 49 | "West Lefler, Dan Hough, and Scoot McNairy. Its plot follows an American family who are invited to stay at a remote " 50 | "farmhouse of a British couple for the weekend, and the hosts soon test the limits of their guests as the situation " 51 | "escalates. Jason Blum serves as a producer through his Blumhouse Productions banner. Speak No Evil premiered at the " 52 | "DGA Theater in New York City on September 9, 2024 and was released in the United States by Universal Pictures on " 53 | "September 13, 2024. The film received generally positive reviews from critics and grossed $77 million worldwide " 54 | "with a budget of $15 million."; 55 | 56 | void fileCb(File &file, const char *filename, readymail_file_operating_mode mode) 57 | { 58 | switch (mode) 59 | { 60 | case readymail_file_mode_open_read: 61 | myFile = MY_FS.open(filename, FILE_OPEN_MODE_READ); 62 | break; 63 | case readymail_file_mode_open_write: 64 | myFile = MY_FS.open(filename, FILE_OPEN_MODE_WRITE); 65 | break; 66 | case readymail_file_mode_open_append: 67 | myFile = MY_FS.open(filename, FILE_OPEN_MODE_APPEND); 68 | break; 69 | case readymail_file_mode_remove: 70 | MY_FS.remove(filename); 71 | break; 72 | default: 73 | break; 74 | } 75 | 76 | // This is required by library to get the file object 77 | // that uses in its read/write processes. 78 | file = myFile; 79 | } 80 | 81 | void createTextFile() 82 | { 83 | MY_FS.begin(); 84 | 85 | File file = MY_FS.open("/static.txt", FILE_OPEN_MODE_WRITE); 86 | file.print(static_text2); 87 | file.close(); 88 | } 89 | #endif 90 | 91 | // For more information, see http://bit.ly/474niML 92 | void smtpCb(SMTPStatus status) 93 | { 94 | if (status.progress.available) 95 | ReadyMail.printf("ReadyMail[smtp][%d] Uploading file %s, %d %% completed\n", status.state, 96 | status.progress.filename.c_str(), status.progress.value); 97 | else 98 | ReadyMail.printf("ReadyMail[smtp][%d]%s\n", status.state, status.text.c_str()); 99 | } 100 | 101 | void setup() 102 | { 103 | Serial.begin(115200); 104 | Serial.println(); 105 | 106 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 107 | Serial.print("Connecting to Wi-Fi"); 108 | while (WiFi.status() != WL_CONNECTED) 109 | { 110 | Serial.print("."); 111 | delay(300); 112 | } 113 | Serial.println(); 114 | Serial.print("Connected with IP: "); 115 | Serial.println(WiFi.localIP()); 116 | Serial.println(); 117 | 118 | // If server SSL certificate verification was ignored for this ESP32 WiFiClientSecure. 119 | // To verify root CA or server SSL cerificate, 120 | // please consult your SSL client documentation. 121 | ssl_client.setInsecure(); 122 | 123 | #if defined(ENABLE_FS) 124 | createTextFile(); 125 | #endif 126 | 127 | Serial.println("ReadyMail, version " + String(READYMAIL_VERSION)); 128 | 129 | // In case ESP8266 crashes, please see https://bit.ly/48r4wSe 130 | 131 | smtp.connect(SMTP_HOST, SMTP_PORT, smtpCb); 132 | if (!smtp.isConnected()) 133 | return; 134 | 135 | smtp.authenticate(AUTHOR_EMAIL, AUTHOR_PASSWORD, readymail_auth_password); 136 | if (!smtp.isAuthenticated()) 137 | return; 138 | 139 | SMTPMessage msg; 140 | msg.headers.add(rfc822_subject, "ReadyMail static message"); 141 | 142 | // Using 'name ' or or 'email' for the from, sender and recipients. 143 | // The 'name' section of cc and bcc is ignored. 144 | 145 | // Multiple recipents can be added but only the first one of sender and from can be added. 146 | msg.headers.add(rfc822_from, "ReadyMail <" + String(AUTHOR_EMAIL) + ">"); 147 | // msg.headers.add(rfc822_sender, "ReadyMail <" + String(AUTHOR_EMAIL) + ">"); 148 | msg.headers.add(rfc822_to, "User <" + String(RECIPIENT_EMAIL) + ">"); 149 | 150 | // Text wrapping 151 | msg.text.textFlow(true); 152 | 153 | // Set the static body content from flle 154 | msg.text.body("/static.txt", fileCb); 155 | 156 | // Set the static body content from blob 157 | // msg.text.body(static_text1, strlen(static_text1)); 158 | 159 | // Set message timestamp (change this with current time) 160 | // See https://bit.ly/4nEuBlk 161 | msg.timestamp = 1746013620; 162 | 163 | smtp.send(msg); 164 | } 165 | 166 | void loop() 167 | { 168 | } -------------------------------------------------------------------------------- /examples/Reading/OTA/OTA.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The example to fetch the latest message in the INBOX. 3 | * If message contains attachment "firmware.bin", the firmware update will begin. 4 | * 5 | * For proper network/SSL client and port selection, please see http://bit.ly/46Xu9Yk 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define ENABLE_IMAP // Allows IMAP class and data 13 | #define ENABLE_DEBUG // Allows debugging 14 | #define READYMAIL_DEBUG_PORT Serial 15 | #include 16 | 17 | #define IMAP_HOST "_______" 18 | #define IMAP_PORT 993 // SSL or 143 for PLAIN TEXT or STARTTLS 19 | #define AUTHOR_EMAIL "_______" 20 | #define AUTHOR_PASSWORD "_______" 21 | 22 | #define WIFI_SSID "_______" 23 | #define WIFI_PASSWORD "_______" 24 | 25 | #define READ_ONLY_MODE true 26 | #define AWAIT_MODE true 27 | #define MAX_CONTENT_SIZE 1024 * 1024 // Maximum size in bytes of the body parts (text and attachment) to be downloaded. 28 | 29 | WiFiClientSecure ssl_client; 30 | IMAPClient imap(ssl_client); 31 | 32 | bool otaErr = false; 33 | 34 | // For more information, see https://bit.ly/4h9JR7p 35 | void imapCb(IMAPStatus status) 36 | { 37 | ReadyMail.printf("ReadyMail[imap][%d]%s\n", status.state, status.text.c_str()); 38 | } 39 | 40 | // For more information, see https://bit.ly/3KLvz0y 41 | void dataCb(IMAPCallbackData &data) 42 | { 43 | // Showing envelope data. 44 | if (data.event() == imap_data_event_search || data.event() == imap_data_event_fetch_envelope) 45 | { 46 | // Show additional search info 47 | if (data.event() == imap_data_event_search) 48 | ReadyMail.printf("Showing Search result %d (%d) of %d from %d\n\n", data.messageIndex() + 1, data.messageNum(), data.messageAvailable(), data.messageFound()); 49 | 50 | // Headers data 51 | for (size_t i = 0; i < data.headerCount(); i++) 52 | ReadyMail.printf("%s: %s\n%s", data.getHeader(i).first.c_str(), data.getHeader(i).second.c_str(), i == data.headerCount() - 1 ? "\n" : ""); 53 | 54 | // Files data 55 | for (size_t i = 0; i < data.fileCount(); i++) 56 | { 57 | // You can fetch or download file while searching 58 | // (or disable it while fetching) with the following options. 59 | 60 | // To enable/disable this file for fetching. 61 | // data.fetchOption(i) = true; 62 | 63 | // To download if the fetch option is set. 64 | // data.setFileCallback(i, filecb, "/downloads"); 65 | ReadyMail.printf("name: %s, mime: %s, charset: %s, trans-enc: %s, size: %d, fetch: %s%s\n", 66 | data.fileInfo(i).filename.c_str(), data.fileInfo(i).mime.c_str(), data.fileInfo(i).charset.c_str(), 67 | data.fileInfo(i).transferEncoding.c_str(), data.fileInfo(i).fileSize, 68 | data.fetchOption(i) ? "yes" : "no", i == data.fileCount() - 1 ? "\n" : ""); 69 | } 70 | } 71 | else if (data.event() == imap_data_event_fetch_body) 72 | { 73 | // Showing the text content 74 | if (data.fileInfo().mime == "application/octet-stream" && data.fileInfo().filename == "firmware.bin") 75 | { 76 | if (data.fileChunk().index == 0) // Fist chunk 77 | { 78 | ReadyMail.printf("Performing OTA update...\n"); 79 | otaErr = !Update.begin(data.fileInfo().fileSize); 80 | 81 | if (data.fileProgress().available) 82 | ReadyMail.printf("Downloading %s, %d of %d, %d %% completed\n", data.fileInfo().filename, 83 | data.fileChunk().index + data.fileChunk().size, data.fileInfo().fileSize, data.fileProgress().value); 84 | 85 | if (!otaErr) 86 | otaErr = Update.write((uint8_t *)data.fileChunk().data, data.fileChunk().size) != data.fileChunk().size; 87 | } 88 | else if (!data.fileChunk().isComplete) 89 | { 90 | if (!otaErr) 91 | otaErr = Update.write((uint8_t *)data.fileChunk().data, data.fileChunk().size) != data.fileChunk().size; 92 | 93 | if (data.fileProgress().available) 94 | ReadyMail.printf("Downloading %s, %d of %d, %d %% completed\n", data.fileInfo().filename, 95 | data.fileChunk().index + data.fileChunk().size, data.fileInfo().fileSize, data.fileProgress().value); 96 | } 97 | else // Last chunk 98 | { 99 | if (data.fileProgress().available) 100 | ReadyMail.printf("Downloading %s, %d of %d, %d %% completed\n", data.fileInfo().filename, 101 | data.fileChunk().index + data.fileChunk().size, data.fileInfo().fileSize, data.fileProgress().value); 102 | 103 | if (!otaErr) 104 | otaErr = !Update.end(true); 105 | 106 | if (otaErr) 107 | ReadyMail.printf("OTA update failed.\n"); 108 | else 109 | { 110 | ReadyMail.printf("OTA update success.\n"); 111 | ReadyMail.printf("Restarting...\n"); 112 | delay(2000); 113 | ESP.restart(); 114 | } 115 | } 116 | } 117 | } 118 | } 119 | 120 | void setup() 121 | { 122 | Serial.begin(115200); 123 | Serial.println(); 124 | 125 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 126 | Serial.print("Connecting to Wi-Fi"); 127 | while (WiFi.status() != WL_CONNECTED) 128 | { 129 | Serial.print("."); 130 | delay(300); 131 | } 132 | Serial.println(); 133 | Serial.print("Connected with IP: "); 134 | Serial.println(WiFi.localIP()); 135 | Serial.println(); 136 | 137 | // If server SSL certificate verification was ignored for this ESP32 WiFiClientSecure. 138 | // To verify root CA or server SSL cerificate, 139 | // please consult your SSL client documentation. 140 | ssl_client.setInsecure(); 141 | 142 | Serial.println("ReadyMail, version " + String(READYMAIL_VERSION)); 143 | 144 | // In case ESP8266 crashes, please see https://bit.ly/48r4wSe 145 | 146 | imap.connect(IMAP_HOST, IMAP_PORT, imapCb); 147 | if (!imap.isConnected()) 148 | return; 149 | 150 | imap.authenticate(AUTHOR_EMAIL, AUTHOR_PASSWORD, readymail_auth_password); 151 | if (!imap.isAuthenticated()) 152 | return; 153 | 154 | // List all mailboxes. 155 | imap.list(); 156 | 157 | // Select INBOX mailbox. 158 | // If READ_ONLY_MODE is false, the flag /Seen will set to the fetched message. 159 | imap.select("INBOX", READ_ONLY_MODE); 160 | 161 | // Fetch the latest message in INBOX. 162 | imap.fetch(imap.getMailbox().msgCount, dataCb, NULL /* FileCallback */, AWAIT_MODE, MAX_CONTENT_SIZE); 163 | } 164 | 165 | void loop() 166 | { 167 | } -------------------------------------------------------------------------------- /examples/Sending/Attachment/Attachment.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The example to send message with attachment. 3 | * For proper network/SSL client and port selection, please see http://bit.ly/46Xu9Yk 4 | */ 5 | #include 6 | #include "Networks.h" 7 | 8 | #define ENABLE_SMTP // Allows SMTP class and data 9 | #define ENABLE_DEBUG // Allows debugging 10 | #define READYMAIL_DEBUG_PORT Serial 11 | #define ENABLE_FS // Allow filesystem integration 12 | 13 | // If message timestamp and/or Date header was not set, 14 | // the message timestamp will be taken from this source, otherwise 15 | // the default timestamp will be used. 16 | #if defined(ESP32) || defined(ESP8266) 17 | #define READYMAIL_TIME_SOURCE time(nullptr); // Or using WiFi.getTime() in WiFiNINA and WiFi101 firmwares. 18 | #endif 19 | 20 | #include 21 | 22 | #define SMTP_HOST "_______" 23 | #define SMTP_PORT 465 // SSL or 587 for STARTTLS 24 | #define AUTHOR_EMAIL "_______" 25 | #define AUTHOR_PASSWORD "_______" 26 | #define RECIPIENT_EMAIL "_______" 27 | 28 | #define WIFI_SSID "_______" 29 | #define WIFI_PASSWORD "_______" 30 | 31 | WiFiClientSecure ssl_client; 32 | SMTPClient smtp(ssl_client); 33 | 34 | #if defined(ENABLE_FS) 35 | #include 36 | File myFile; 37 | #if defined(ESP32) 38 | #include 39 | #endif 40 | #define MY_FS SPIFFS 41 | 42 | const char *orangeImg = "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAEASURBV" 43 | "Hhe7dEhAQAgEMBA2hCT6I+nABMnzsxuzdlDx3oDfxkSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMIT" 44 | "GGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITG" 45 | "GxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0iMITGGxBgSY0jMBYxyLEpP9PqyAAAAAElFTkSuQmCC" 46 | "jhSDb5FKG9Q4"; 47 | 48 | const char *blueImg = "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAEASURBVHh" 49 | "e7dEhAQAgAMAwmmEJTyfwFOBiYub2Y6596Bhv4C9DYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkB" 50 | "hDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDY" 51 | "gyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJMSTGkBhDYgyJuZ7+qGdQMlUbAAAAAElFTkSuQmCC"; 52 | 53 | void fileCb(File &file, const char *filename, readymail_file_operating_mode mode) 54 | { 55 | switch (mode) 56 | { 57 | case readymail_file_mode_open_read: 58 | myFile = MY_FS.open(filename, FILE_OPEN_MODE_READ); 59 | break; 60 | case readymail_file_mode_open_write: 61 | myFile = MY_FS.open(filename, FILE_OPEN_MODE_WRITE); 62 | break; 63 | case readymail_file_mode_open_append: 64 | myFile = MY_FS.open(filename, FILE_OPEN_MODE_APPEND); 65 | break; 66 | case readymail_file_mode_remove: 67 | MY_FS.remove(filename); 68 | break; 69 | default: 70 | break; 71 | } 72 | 73 | // This is required by library to get the file object 74 | // that uses in its read/write processes. 75 | file = myFile; 76 | } 77 | 78 | void createAttachment() 79 | { 80 | MY_FS.begin(); 81 | 82 | File file = MY_FS.open("/orange.png", FILE_OPEN_MODE_WRITE); 83 | file.print(orangeImg); 84 | file.close(); 85 | 86 | file = MY_FS.open("/blue.png", FILE_OPEN_MODE_WRITE); 87 | file.print(blueImg); 88 | file.close(); 89 | } 90 | #endif 91 | 92 | // For more information, see http://bit.ly/474niML 93 | void smtpCb(SMTPStatus status) 94 | { 95 | if (status.progress.available) 96 | ReadyMail.printf("ReadyMail[smtp][%d] Uploading file %s, %d %% completed\n", status.state, 97 | status.progress.filename.c_str(), status.progress.value); 98 | else 99 | ReadyMail.printf("ReadyMail[smtp][%d]%s\n", status.state, status.text.c_str()); 100 | } 101 | 102 | void addFileAttachment(SMTPMessage &msg, const String &filename, const String &mime, const String &name, FileCallback cb, const String &filepath, const String &encoding = "", const String &cid = "") 103 | { 104 | Attachment attachment; 105 | attachment.filename = filename; 106 | attachment.mime = mime; 107 | attachment.name = name; 108 | // The inline content disposition. 109 | // Should be matched the image src's cid in html body 110 | attachment.content_id = cid; 111 | attachment.attach_file.callback = cb; 112 | attachment.attach_file.path = filepath; 113 | // Specify only when content is already encoded. 114 | attachment.content_encoding = encoding; 115 | msg.attachments.add(attachment, cid.length() > 0 ? attach_type_inline : attach_type_attachment); 116 | } 117 | 118 | void setup() 119 | { 120 | Serial.begin(115200); 121 | Serial.println(); 122 | 123 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 124 | Serial.print("Connecting to Wi-Fi"); 125 | while (WiFi.status() != WL_CONNECTED) 126 | { 127 | Serial.print("."); 128 | delay(300); 129 | } 130 | Serial.println(); 131 | Serial.print("Connected with IP: "); 132 | Serial.println(WiFi.localIP()); 133 | Serial.println(); 134 | 135 | createAttachment(); 136 | 137 | // If server SSL certificate verification was ignored for this ESP32 WiFiClientSecure. 138 | // To verify root CA or server SSL cerificate, 139 | // please consult your SSL client documentation. 140 | ssl_client.setInsecure(); 141 | 142 | Serial.println("ReadyMail, version " + String(READYMAIL_VERSION)); 143 | 144 | // In case ESP8266 crashes, please see https://bit.ly/48r4wSe 145 | 146 | smtp.connect(SMTP_HOST, SMTP_PORT, smtpCb); 147 | if (!smtp.isConnected()) 148 | return; 149 | 150 | smtp.authenticate(AUTHOR_EMAIL, AUTHOR_PASSWORD, readymail_auth_password); 151 | if (!smtp.isAuthenticated()) 152 | return; 153 | 154 | SMTPMessage msg; 155 | msg.headers.add(rfc822_subject, "ReadyMail Hello message with attachment"); 156 | msg.headers.add(rfc822_from, "ReadyMail <" + String(AUTHOR_EMAIL) + ">"); 157 | // msg.headers.add(rfc822_sender, "ReadyMail <" + String(AUTHOR_EMAIL) + ">"); 158 | msg.headers.add(rfc822_to, "User <" + String(RECIPIENT_EMAIL) + ">"); 159 | 160 | String bodyText = "Hello everyone."; 161 | msg.text.body(bodyText); 162 | msg.html.body("
" + bodyText + "
"); 163 | 164 | // Set message timestamp (change this with current time) 165 | // See https://bit.ly/4nEuBlk 166 | msg.timestamp = 1746013620; 167 | 168 | addFileAttachment(msg, "orange.png", "image/png", "orange.png", fileCb, "/orange.png", "base64"); 169 | addFileAttachment(msg, "blue.png", "image/png", "blue.png", fileCb, "/blue.png", "base64"); 170 | smtp.send(msg); 171 | } 172 | 173 | void loop() 174 | { 175 | } -------------------------------------------------------------------------------- /src/ReadyMail.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 Suwatchai K. 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef READYMAIL_H 8 | #define READYMAIL_H 9 | 10 | #include 11 | #if defined(ENABLE_FS) 12 | #include 13 | #endif 14 | 15 | #if defined(ARDUINO_UNOWIFIR4) || defined(ARDUINO_MINIMA) || defined(ARDUINO_PORTENTA_C33) 16 | #define READYMAIL_USE_STRSEP_IMPL 17 | #endif 18 | 19 | #if !defined(AVR) 20 | #include 21 | #include 22 | #include 23 | #endif 24 | #include 25 | #include 26 | #include "./core/ReadyTimer.h" 27 | #include "./core/ReadyCodec.h" 28 | #include "./core/Utils.h" 29 | 30 | #define READYMAIL_VERSION "0.3.6" 31 | #define READYMAIL_TIMESTAMP 1755052104 32 | #define READYMAIL_LOOPBACK_IPV4 "127.0.0.1" 33 | 34 | #if !defined(READYMAIL_TIME_SOURCE) 35 | #define READYMAIL_TIME_SOURCE READYMAIL_TIMESTAMP; 36 | #endif 37 | 38 | #if defined(READYMAIL_DEBUG_PORT) 39 | #define READYMAIL_DEFAULT_DEBUG_PORT READYMAIL_DEBUG_PORT 40 | #else 41 | #define READYMAIL_DEFAULT_DEBUG_PORT Serial 42 | #endif 43 | 44 | #if defined(ENABLE_SMTP) 45 | #define ENABLE_IMAP_APPEND 46 | #endif 47 | 48 | #if defined(ARDUINO_ARCH_RP2040) 49 | 50 | #if defined(ARDUINO_NANO_RP2040_CONNECT) 51 | 52 | #else 53 | #ifndef ARDUINO_ARCH_RP2040_PICO 54 | #define ARDUINO_ARCH_RP2040_PICO 55 | #endif 56 | #endif 57 | 58 | #endif 59 | 60 | #define MAX_LINE_LEN 76 61 | 62 | #define TCP_CLIENT_ERROR_INITIALIZE -1 63 | #define TCP_CLIENT_ERROR_CONNECTION -2 64 | #define TCP_CLIENT_ERROR_NOT_CONNECTED -3 65 | #define TCP_CLIENT_ERROR_CONNECTION_TIMEOUT -4 66 | #define TCP_CLIENT_ERROR_STARTTLS -5 67 | #define TCP_CLIENT_ERROR_TLS_HANDSHAKE -6 68 | #define TCP_CLIENT_ERROR_SEND_DATA -7 69 | #define TCP_CLIENT_ERROR_READ_DATA -8 70 | 71 | #define AUTH_ERROR_UNAUTHENTICATE -200 72 | #define AUTH_ERROR_AUTHENTICATION -201 73 | #define AUTH_ERROR_OAUTH2_NOT_SUPPORTED -202 74 | 75 | #if defined(ENABLE_FS) 76 | #if __has_include() 77 | #include 78 | #elif __has_include() && __has_include() 79 | #include 80 | #include 81 | #else 82 | #undef ENABLE_FS 83 | #endif 84 | #endif // ENABLE_FS 85 | 86 | #if defined(ENABLE_FS) 87 | 88 | #if (defined(ESP8266) || defined(CORE_ARDUINO_PICO)) || (defined(ESP32) && __has_include()) 89 | 90 | #if !defined(FILE_OPEN_MODE_READ) 91 | #define FILE_OPEN_MODE_READ "r" 92 | #endif 93 | 94 | #if !defined(FILE_OPEN_MODE_WRITE) 95 | #define FILE_OPEN_MODE_WRITE "w" 96 | #endif 97 | 98 | #if !defined(FILE_OPEN_MODE_APPEND) 99 | #define FILE_OPEN_MODE_APPEND "a" 100 | #endif 101 | 102 | #elif __has_include() && __has_include() 103 | 104 | #if !defined(FILE_OPEN_MODE_READ) 105 | #define FILE_OPEN_MODE_READ FILE_READ 106 | #endif 107 | 108 | #if !defined(FILE_OPEN_MODE_WRITE) 109 | #define FILE_OPEN_MODE_WRITE FILE_WRITE 110 | #endif 111 | 112 | #if !defined(FILE_OPEN_MODE_APPEND) 113 | #define FILE_OPEN_MODE_APPEND FILE_WRITE 114 | #endif 115 | 116 | #endif // __has_include() && __has_include() 117 | 118 | #endif // ENABLE_FS 119 | 120 | class ReadyMailClass 121 | { 122 | public: 123 | ReadyMailClass() {}; 124 | ~ReadyMailClass() {}; 125 | 126 | /** Printf 127 | */ 128 | void printf(const char *format, ...) 129 | { 130 | #if defined(READYMAIL_PRINTF_BUFFER) 131 | const int size = READYMAIL_PRINTF_BUFFER; 132 | #else 133 | const int size = 1024; 134 | #endif 135 | char s[size]; 136 | va_list va; 137 | va_start(va, format); 138 | vsnprintf(s, size, format, va); 139 | va_end(va); 140 | READYMAIL_DEFAULT_DEBUG_PORT.print(s); 141 | } 142 | 143 | /** Provides date/time string 144 | * 145 | * @param ts The UNIX timestamp in seconds since midnight January 1, 1970. 146 | * @param format The date/time format e.g. "%a, %d %b %Y %H:%M:%S %z". 147 | * @return String of date/time. 148 | */ 149 | String getDateTimeString(time_t ts, const char *format) 150 | { 151 | char tbuf[100]; 152 | strftime(tbuf, 100, format, localtime(&ts)); 153 | return tbuf; 154 | } 155 | 156 | /** Provides base64 encoded string 157 | * 158 | * @param str The string to convert to base64 string. 159 | * @return String of base64 encoding. 160 | */ 161 | String base64Encode(const String &str) 162 | { 163 | String buf; 164 | char *enc = rd_b64_enc(rd_cast(str.c_str()), str.length()); 165 | if (enc) 166 | { 167 | buf = enc; 168 | rd_free(&enc); 169 | } 170 | return buf; 171 | } 172 | 173 | /** Provides base64 encozded string used for RFC 4616 PLAIN SASL mechanism 174 | * 175 | * @param email The email to convert to base64 PLAIN SASL string. 176 | * @param email The password to convert to base64 PLAIN SASL string. 177 | * @return String of base64 PLAIN SASL string. 178 | */ 179 | String plainSASLEncode(const String &email, const String &password) { return rd_enc_plain(email, password); } 180 | 181 | private: 182 | }; 183 | 184 | ReadyMailClass ReadyMail; 185 | 186 | enum readymail_auth_type 187 | { 188 | readymail_auth_password, 189 | readymail_auth_accesstoken, 190 | readymail_auth_digest_md5, 191 | readymail_auth_disabled 192 | }; 193 | 194 | enum readymail_file_operating_mode 195 | { 196 | readymail_file_mode_open_read, 197 | readymail_file_mode_open_write, 198 | readymail_file_mode_open_append, 199 | readymail_file_mode_remove 200 | }; 201 | 202 | #if defined(READYCLIENT_SSL_CLIENT) && (defined(ENABLE_IMAP) || defined(ENABLE_SMTP)) 203 | 204 | #if __has_include() && !defined(READYCLIENT_TYPE_1) 205 | #include 206 | #define READYCLIENT_TYPE_1 207 | #endif 208 | 209 | #if __has_include() && !defined(READYCLIENT_TYPE_2) 210 | #include 211 | #define READYCLIENT_TYPE_2 212 | #endif 213 | 214 | #if defined(READYCLIENT_TYPE_1) || defined(READYCLIENT_TYPE_2) 215 | #define ENABLE_READYCLIENT 216 | #endif 217 | 218 | enum readymail_protocol 219 | { 220 | readymail_protocol_plain_text, 221 | readymail_protocol_ssl, 222 | readymail_protocol_tls 223 | }; 224 | 225 | struct readymail_port_function 226 | { 227 | uint16_t port; 228 | readymail_protocol protocol; 229 | }; 230 | #include "./core/ReadyClient.h" 231 | #endif 232 | 233 | namespace ReadyMailCallbackNS 234 | { 235 | #if defined(ENABLE_FS) 236 | typedef void (*FileCallback)(File &file, const char *filename, readymail_file_operating_mode mode); 237 | #else 238 | typedef void (*FileCallback)(); 239 | #endif 240 | typedef void (*TLSHandshakeCallback)(bool &success); 241 | } 242 | 243 | #include "./core/ReadyError.h" 244 | 245 | #if defined(ENABLE_IMAP) 246 | #include "imap/MailboxInfo.h" 247 | #include "imap/IMAPClient.h" 248 | #endif 249 | 250 | #if defined(ENABLE_SMTP) 251 | #include "smtp/SMTPMessage.h" 252 | #include "smtp/SMTPClient.h" 253 | #endif 254 | 255 | #endif -------------------------------------------------------------------------------- /examples/Sending/Command/Command.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The example to use SMTP command to connect, authenticate and send Email. 3 | * 4 | * Due to the STARTTLS is also presented in this example, the ESP_SSLClient will be used as SSL client 5 | * and use WiFiClient for the network client. 6 | * 7 | * For ESP32, WiFiClientSecure or NetworkClientSecure can also be used in this example for STARTTLS. 8 | * 9 | * For proper network/SSL client and port selection, please see http://bit.ly/46Xu9Yk 10 | */ 11 | #include 12 | #include "Networks.h" 13 | 14 | #define ENABLE_SMTP // Allows SMTP class and data 15 | #define ENABLE_DEBUG // Allows debugging 16 | #define READYMAIL_DEBUG_PORT Serial 17 | 18 | // If message timestamp and/or Date header was not set, 19 | // the message timestamp will be taken from this source, otherwise 20 | // the default timestamp will be used. 21 | #if defined(ESP32) || defined(ESP8266) 22 | #define READYMAIL_TIME_SOURCE time(nullptr); // Or using WiFi.getTime() in WiFiNINA and WiFi101 firmwares. 23 | #endif 24 | 25 | #include 26 | 27 | #define SMTP_HOST "_______" 28 | #define SMTP_PORT 465 // SSL or 587 for STARTTLS 29 | #define AUTHOR_EMAIL "_______" 30 | #define AUTHOR_PASSWORD "_______" 31 | #define RECIPIENT_EMAIL "_______" 32 | 33 | #define WIFI_SSID "_______" 34 | #define WIFI_PASSWORD "_______" 35 | 36 | #include 37 | #include 38 | 39 | // https://github.com/mobizt/ESP_SSLClient 40 | ESP_SSLClient ssl_client; 41 | WiFiClient basic_client; 42 | 43 | SMTPClient smtp(ssl_client); 44 | 45 | bool startTLSCap = false; 46 | bool plainCap = false; 47 | 48 | // For debugging 49 | void smtpCb(SMTPStatus status) 50 | { 51 | ReadyMail.printf("ReadyMail[dbg][%d]%s\n", status.state, status.text.c_str()); 52 | } 53 | 54 | void cmdCb(SMTPCommandResponse response) 55 | { 56 | // The current command can be obtained from status.command. 57 | // The response.text provides a response line. 58 | ReadyMail.printf("ReadyMail[cmd][%d] %s\n", smtp.status().state, response.text.c_str()); 59 | 60 | // Check for STARTTLS capability 61 | if (response.text.indexOf("STARTTLS") > -1) 62 | startTLSCap = true; 63 | 64 | // Check for PLAIN SASL capability. 65 | if (response.text.indexOf("PLAIN") > -1) 66 | plainCap = true; 67 | } 68 | 69 | void setup() 70 | { 71 | Serial.begin(115200); 72 | Serial.println(); 73 | 74 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 75 | Serial.print("Connecting to Wi-Fi"); 76 | while (WiFi.status() != WL_CONNECTED) 77 | { 78 | Serial.print("."); 79 | delay(300); 80 | } 81 | Serial.println(); 82 | Serial.print("Connected with IP: "); 83 | Serial.println(WiFi.localIP()); 84 | Serial.println(); 85 | 86 | // For ESP_SSLClient 87 | // if SMTP port is 465, starts in SSL, otherwise starts in plain text 88 | ssl_client.setClient(&basic_client, SMTP_PORT == 465); 89 | 90 | // For ESP32 WiFiClientSecure 91 | // If SMTP port is 587, starts in plain text 92 | 93 | // if (SMTP_PORT == 587) 94 | // ssl_client.setPlainStart(); 95 | 96 | // If server SSL certificate verification was ignored for this ESP32 WiFiClientSecure. 97 | // To verify root CA or server SSL cerificate, 98 | // please consult your SSL client documentation. 99 | ssl_client.setInsecure(); 100 | 101 | Serial.println("ReadyMail, version " + String(READYMAIL_VERSION)); 102 | 103 | bool esmtp = true; 104 | 105 | // In case ESP8266 crashes, please see https://bit.ly/48r4wSe 106 | 107 | smtp.connect(SMTP_HOST, SMTP_PORT, cmdCb, smtpCb /* for showing debug info */); 108 | if (smtp.commandResponse().statusCode != 220) 109 | return; 110 | 111 | smtp.sendCommand("EHLO 127.0.0.1", cmdCb); 112 | if (smtp.commandResponse().statusCode != 250) 113 | { 114 | // No ESMTP supports 115 | smtp.sendCommand("HELO 127.0.0.1", cmdCb); 116 | if (smtp.commandResponse().statusCode != 250) 117 | return; 118 | esmtp = false; 119 | } 120 | 121 | // Find the "STARTTLS" in the response if server supports STARTTLS. 122 | if (SMTP_PORT == 587 && !startTLSCap) 123 | { 124 | // Server does not support STARTTLS, use port 465 for SSL instead. 125 | return; 126 | } 127 | 128 | if (SMTP_PORT == 587) 129 | { 130 | smtp.sendCommand("STARTTLS", cmdCb); 131 | if (smtp.commandResponse().statusCode != 220) 132 | return; 133 | 134 | // Calling SSL client to Start TLS 135 | // For ESP_SSLClient 136 | ssl_client.connectSSL(); 137 | 138 | // For ESP32 WiFIClientSecure 139 | // ssl_client.startTLS(); 140 | 141 | smtp.sendCommand(esmtp ? "EHLO 127.0.0.1" : "HELO 127.0.0.1", cmdCb); 142 | if (smtp.commandResponse().statusCode != 250) 143 | return; 144 | } 145 | 146 | // Find the "PLAIN" in the response if server supports the PLAIN SASL mechanism 147 | if (plainCap) 148 | { 149 | String cmd = "AUTH PLAIN " + ReadyMail.plainSASLEncode(AUTHOR_EMAIL, AUTHOR_PASSWORD); 150 | smtp.sendCommand(cmd, cmdCb); 151 | if (smtp.commandResponse().statusCode != 235) 152 | return; 153 | } 154 | else 155 | { 156 | smtp.sendCommand("AUTH LOGIN", cmdCb); 157 | if (smtp.commandResponse().statusCode != 334) 158 | return; 159 | 160 | smtp.sendCommand(ReadyMail.base64Encode(AUTHOR_EMAIL), cmdCb); 161 | if (smtp.commandResponse().statusCode != 334) 162 | return; 163 | 164 | smtp.sendCommand(ReadyMail.base64Encode(AUTHOR_PASSWORD), cmdCb); 165 | if (smtp.commandResponse().statusCode != 235) 166 | return; 167 | } 168 | 169 | // If client is authenticated, send Email. 170 | if (smtp.isAuthenticated()) 171 | { 172 | smtp.sendCommand("MAIL FROM:<" + String(AUTHOR_EMAIL) + ">", cmdCb); 173 | if (smtp.commandResponse().statusCode != 250) 174 | return; 175 | 176 | smtp.sendCommand("RCPT TO:<" + String(RECIPIENT_EMAIL) + ">", cmdCb); 177 | if (smtp.commandResponse().statusCode != 250) 178 | return; 179 | 180 | smtp.sendCommand("DATA", cmdCb); 181 | if (smtp.commandResponse().statusCode != 354) 182 | return; 183 | 184 | smtp.sendData("From: <" + String(AUTHOR_EMAIL) + ">\r\n", cmdCb); 185 | smtp.sendData("To: <" + String(RECIPIENT_EMAIL) + ">\r\n", cmdCb); 186 | 187 | smtp.sendData("Subject: ReadyMail Test\r\n", cmdCb); 188 | smtp.sendData("Date: Mon, 05 May 2025 16:51:36 +0300\r\n", cmdCb); 189 | smtp.sendData("Mime-Version: 1.0\r\n", cmdCb); 190 | smtp.sendData("Content-Type: text/plain; charset=\"utf-8\";\r\n\r\n", cmdCb); 191 | smtp.sendData("Hello again!\r\n", cmdCb); 192 | 193 | smtp.sendCommand(".", cmdCb); 194 | if (smtp.commandResponse().statusCode != 250) 195 | return; 196 | } 197 | 198 | smtp.sendCommand("QUIT", cmdCb); 199 | Serial.println("The Email was sent successfully."); 200 | } 201 | 202 | void loop() 203 | { 204 | } -------------------------------------------------------------------------------- /src/core/QBDecoder.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 Suwatchai K. 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef QB_DECODER_H 8 | #define QB_DECODER_H 9 | 10 | #include 11 | 12 | #if defined(ENABLE_IMAP) || defined(ENABLE_SMTP) 13 | 14 | // Renesas devices 15 | #if defined(ARDUINO_UNOWIFIR4) || defined(ARDUINO_MINIMA) || defined(ARDUINO_PORTENTA_C33) 16 | #define XMAILER_STRSEP strsepImpl 17 | #define XMAILER_USE_STRSEP_IMPL 18 | #else 19 | #define XMAILER_STRSEP strsep 20 | #endif 21 | 22 | // Re-interpret cast 23 | template 24 | static To rd_cast(From frm) 25 | { 26 | return reinterpret_cast(frm); 27 | } 28 | 29 | static void rd_set(void *m, int size) { memset(m, 0, size); } 30 | 31 | template 32 | static T rd_mem(int len, bool set = false) 33 | { 34 | T buf = rd_cast(malloc(len)); 35 | if (set) 36 | rd_set(buf, len); 37 | return buf; 38 | } 39 | 40 | // we have to set null, pass the pointer instead 41 | static void rd_free(void *ptr) 42 | { 43 | void **p = rd_cast(ptr); 44 | if (*p) 45 | { 46 | free(*p); 47 | *p = 0; 48 | } 49 | } 50 | 51 | #define strfcpy(A, B, C) strncpy(A, B, C), *(A + (C) - 1) = 0 52 | 53 | __attribute__((used)) static int Index_base64[128] = { 54 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 55 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 56 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 57 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, 58 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 59 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, 60 | -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 61 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1}; 62 | 63 | __attribute__((used)) static int Index_hex[128] = { 64 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 65 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 66 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 67 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, 68 | -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, 69 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 70 | -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, 71 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; 72 | 73 | #if defined(XMAILER_USE_STRSEP_IMPL) 74 | // This is strsep implementation because strdup may not available in some platform. 75 | static char *__attribute__((used)) strsepImpl(char **stringp, const char *delim) 76 | { 77 | char *rv = *stringp; 78 | if (rv) 79 | { 80 | *stringp += strcspn(*stringp, delim); 81 | if (**stringp) 82 | *(*stringp)++ = '\0'; 83 | else 84 | *stringp = 0; 85 | } 86 | return rv; 87 | } 88 | 89 | #endif 90 | 91 | #define IsPrint(c) (isprint((unsigned char)(c)) || \ 92 | ((unsigned char)(c) >= 0xa0)) 93 | 94 | #define hexval(c) Index_hex[(unsigned int)(c)] 95 | #define base64val(c) Index_base64[(unsigned int)(c)] 96 | 97 | // Quoted-printable and base64 decoder 98 | class QBDecoder 99 | { 100 | 101 | public: 102 | QBDecoder() {}; 103 | ~QBDecoder() {}; 104 | void decode(char *d, const char *s, size_t dlen) 105 | { 106 | const char *q; 107 | size_t n; 108 | int found_encoded = 0; 109 | 110 | dlen--; /* save room for the terminal nul */ 111 | 112 | while (*s && dlen > 0) 113 | { 114 | const char *p; 115 | if ((p = strstr(s, "=?")) == NULL || 116 | (q = strchr(p + 2, '?')) == NULL || 117 | (q = strchr(q + 1, '?')) == NULL || 118 | (q = strstr(q + 1, "?=")) == NULL) 119 | { 120 | /* no encoded words */ 121 | if (d != s) 122 | strfcpy(d, s, dlen + 1); 123 | return; 124 | } 125 | 126 | if (p != s) 127 | { 128 | n = (size_t)(p - s); 129 | /* ignore spaces between encoded words */ 130 | if (!found_encoded || strspn(s, " \t\r\n") != n) 131 | { 132 | if (n > dlen) 133 | n = dlen; 134 | if (d != s) 135 | memcpy(d, s, n); 136 | d += n; 137 | dlen -= n; 138 | } 139 | } 140 | 141 | decodeWord(d, p, dlen); 142 | found_encoded = 1; 143 | s = q + 2; 144 | n = strlen(d); 145 | dlen -= n; 146 | d += n; 147 | } 148 | *d = 0; 149 | } 150 | 151 | private: 152 | enum 153 | { 154 | ENCOTHER, 155 | ENC7BIT, 156 | ENC8BIT, 157 | ENCQUOTEDPRINTABLE, 158 | ENCBASE64, 159 | ENCBINARY 160 | }; 161 | 162 | void decodeWord(char *d, const char *s, size_t dlen) 163 | { 164 | char *p = safe_strdup(s); 165 | char *pp = p; 166 | char *end = p; 167 | char *pd = d; 168 | size_t len = dlen; 169 | int enc = 0, filter = 0, count = 0, c1, c2, c3, c4; 170 | 171 | while (pp != NULL) 172 | { 173 | // See RFC2047.h 174 | XMAILER_STRSEP(&end, "?"); 175 | count++; 176 | switch (count) 177 | { 178 | case 2: 179 | if (strcasecmp(pp, "utf-8") != 0) 180 | { 181 | filter = 1; 182 | } 183 | break; 184 | case 3: 185 | if (toupper(*pp) == 'Q') 186 | enc = ENCQUOTEDPRINTABLE; 187 | else if (toupper(*pp) == 'B') 188 | enc = ENCBASE64; 189 | else 190 | return; 191 | break; 192 | case 4: 193 | if (enc == ENCQUOTEDPRINTABLE) 194 | { 195 | while (*pp && len > 0) 196 | { 197 | if (*pp == '_') 198 | { 199 | *pd++ = ' '; 200 | len--; 201 | } 202 | else if (*pp == '=') 203 | { 204 | *pd++ = (hexval(pp[1]) << 4) | hexval(pp[2]); 205 | len--; 206 | pp += 2; 207 | } 208 | else 209 | { 210 | *pd++ = *pp; 211 | len--; 212 | } 213 | pp++; 214 | } 215 | *pd = 0; 216 | } 217 | else if (enc == ENCBASE64) 218 | { 219 | while (*pp && len > 0) 220 | { 221 | c1 = base64val(pp[0]); 222 | c2 = base64val(pp[1]); 223 | *pd++ = (c1 << 2) | ((c2 >> 4) & 0x3); 224 | if (--len == 0) 225 | break; 226 | 227 | if (pp[2] == '=') 228 | break; 229 | 230 | c3 = base64val(pp[2]); 231 | *pd++ = ((c2 & 0xf) << 4) | ((c3 >> 2) & 0xf); 232 | if (--len == 0) 233 | break; 234 | 235 | if (pp[3] == '=') 236 | break; 237 | 238 | c4 = base64val(pp[3]); 239 | *pd++ = ((c3 & 0x3) << 6) | c4; 240 | if (--len == 0) 241 | break; 242 | 243 | pp += 4; 244 | } 245 | *pd = 0; 246 | } 247 | break; 248 | } 249 | 250 | pp = end; 251 | } 252 | 253 | rd_free(&p); 254 | 255 | if (filter) 256 | { 257 | pd = d; 258 | while (*pd) 259 | { 260 | if (!IsPrint(*pd)) 261 | *pd = '?'; 262 | pd++; 263 | } 264 | } 265 | return; 266 | } 267 | char *safe_strdup(const char *s) 268 | { 269 | char *p; 270 | size_t l; 271 | 272 | if (!s || !*s) 273 | return 0; 274 | l = strlen(s) + 1; 275 | p = rd_mem(l); 276 | memcpy(p, s, l); 277 | return (p); 278 | } 279 | }; 280 | #endif 281 | #endif // QB_DECODER_H -------------------------------------------------------------------------------- /examples/Reading/Fetch/Fetch.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * The example to fetch the latest message in the INBOX. 3 | * If you want to download the content to file, see Download.ino. 4 | * 5 | * For proper network/SSL client and port selection, please see http://bit.ly/46Xu9Yk 6 | */ 7 | #include 8 | #include "Networks.h" 9 | 10 | #define ENABLE_IMAP // Allows IMAP class and data 11 | #define ENABLE_DEBUG // Allows debugging 12 | #define READYMAIL_DEBUG_PORT Serial 13 | #include 14 | 15 | #define IMAP_HOST "_______" 16 | #define IMAP_PORT 993 // SSL or 143 for PLAIN TEXT or STARTTLS 17 | #define AUTHOR_EMAIL "_______" 18 | #define AUTHOR_PASSWORD "_______" 19 | 20 | #define WIFI_SSID "_______" 21 | #define WIFI_PASSWORD "_______" 22 | 23 | #define READ_ONLY_MODE true 24 | #define AWAIT_MODE true 25 | #define MAX_CONTENT_SIZE 1024 * 1024 // Maximum size in bytes of the body parts (text and attachment) to be downloaded. 26 | 27 | WiFiClientSecure ssl_client; 28 | IMAPClient imap(ssl_client); 29 | 30 | // For more information, see https://bit.ly/4h9JR7p 31 | void imapCb(IMAPStatus status) 32 | { 33 | ReadyMail.printf("ReadyMail[imap][%d]%s\n", status.state, status.text.c_str()); 34 | } 35 | 36 | void showMailboxInfo(MailboxInfo info) 37 | { 38 | ReadyMail.printf("Info of the selected mailbox\nTotal Messages: %d\n", info.msgCount); 39 | ReadyMail.printf("UID Validity: %d\n", info.UIDValidity); 40 | ReadyMail.printf("Predicted next UID: %d\n", info.nextUID); 41 | if (info.UnseenIndex > 0) 42 | ReadyMail.printf("First Unseen Message Number: %d\n", info.UnseenIndex); 43 | else 44 | ReadyMail.printf("Unseen Messages: No\n"); 45 | 46 | if (info.noModseq) 47 | ReadyMail.printf("Highest Modification Sequence: %llu\n", info.highestModseq); 48 | for (size_t i = 0; i < info.flags.size(); i++) 49 | ReadyMail.printf("%s%s%s", i == 0 ? "Flags: " : ", ", info.flags[i].c_str(), i == info.flags.size() - 1 ? "\n" : ""); 50 | 51 | if (info.permanentFlags.size()) 52 | { 53 | for (size_t i = 0; i < info.permanentFlags.size(); i++) 54 | ReadyMail.printf("%s%s%s", i == 0 ? "Permanent Flags: " : ", ", info.permanentFlags[i].c_str(), i == info.permanentFlags.size() - 1 ? "\n" : ""); 55 | } 56 | ReadyMail.printf("\n"); 57 | } 58 | 59 | void textDecodingCb(const String &charset, const uint8_t *in, int inSize, uint8_t *out, int &outSize) 60 | { 61 | /** Custom text body decoding based on the character set 62 | * 63 | * in - The encoded text 64 | * inSize - The length of encoded text 65 | * out - The buffer to save the unencoded (converted) text 66 | * outSize - The length of unencoded text 67 | */ 68 | } 69 | 70 | // For more information, see https://bit.ly/3KLvz0y 71 | void dataCb(IMAPCallbackData &data) 72 | { 73 | // Showing envelope data. 74 | if (data.event() == imap_data_event_search || data.event() == imap_data_event_fetch_envelope) 75 | { 76 | // Show additional search info 77 | if (data.event() == imap_data_event_search) 78 | ReadyMail.printf("Showing Search result %d (%d) of %d from %d\n\n", data.messageIndex() + 1, 79 | data.messageNum(), data.messageAvailable(), data.messageFound()); 80 | 81 | // Headers data 82 | for (size_t i = 0; i < data.headerCount(); i++) 83 | ReadyMail.printf("%s: %s\n%s", data.getHeader(i).first.c_str(), 84 | data.getHeader(i).second.c_str(), i == data.headerCount() - 1 ? "\n" : ""); 85 | 86 | // Files data 87 | for (size_t i = 0; i < data.fileCount(); i++) 88 | { 89 | // You can fetch or download file while searching 90 | // (or disable it while fetching) with the following options. 91 | 92 | // To enable/disable this file for fetching. 93 | // data.fetchOption(i) = true; 94 | 95 | // To download if the fetch option is set. 96 | // data.setFileCallback(i, filecb, "/downloads"); 97 | 98 | // To decode the text body based on the character set. 99 | // if (data.fileInfo(i).charset.length()) 100 | // data.setTextEncodingCallback(i, textDecodingCb); 101 | ReadyMail.printf("name: %s, mime: %s, charset: %s, trans-enc: %s, size: %d, fetch: %s%s\n", 102 | data.fileInfo(i).filename.c_str(), data.fileInfo(i).mime.c_str(), data.fileInfo(i).charset.c_str(), 103 | data.fileInfo(i).transferEncoding.c_str(), data.fileInfo(i).fileSize, 104 | data.fetchOption(i) ? "yes" : "no", i == data.fileCount() - 1 ? "\n" : ""); 105 | } 106 | } 107 | else if (data.event() == imap_data_event_fetch_body) 108 | { 109 | // Showing the text file content 110 | if (data.fileInfo().mime == "text/html" || data.fileInfo().mime == "text/plain") 111 | { 112 | if (data.fileChunk().index == 0) // Fist chunk 113 | Serial.println("------------------"); 114 | Serial.print((char *)data.fileChunk().data); 115 | if (data.fileChunk().isComplete) // Last chunk 116 | Serial.println("------------------"); 117 | } 118 | else 119 | { 120 | // Showing the progress of current file fetching 121 | if (data.fileChunk().index == 0) 122 | Serial.print("Downloading"); 123 | if (data.fileProgress().available) 124 | Serial.print("."); 125 | if (data.fileChunk().isComplete) 126 | Serial.println(" complete"); 127 | } 128 | } 129 | } 130 | 131 | void setup() 132 | { 133 | Serial.begin(115200); 134 | Serial.println(); 135 | 136 | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); 137 | Serial.print("Connecting to Wi-Fi"); 138 | while (WiFi.status() != WL_CONNECTED) 139 | { 140 | Serial.print("."); 141 | delay(300); 142 | } 143 | Serial.println(); 144 | Serial.print("Connected with IP: "); 145 | Serial.println(WiFi.localIP()); 146 | Serial.println(); 147 | 148 | // If server SSL certificate verification was ignored for this ESP32 WiFiClientSecure. 149 | // To verify root CA or server SSL cerificate, 150 | // please consult your SSL client documentation. 151 | ssl_client.setInsecure(); 152 | 153 | Serial.println("ReadyMail, version " + String(READYMAIL_VERSION)); 154 | 155 | // In case ESP8266 crashes, please see https://bit.ly/48r4wSe 156 | 157 | imap.connect(IMAP_HOST, IMAP_PORT, imapCb); 158 | if (!imap.isConnected()) 159 | return; 160 | 161 | imap.authenticate(AUTHOR_EMAIL, AUTHOR_PASSWORD, readymail_auth_password); 162 | if (!imap.isAuthenticated()) 163 | return; 164 | 165 | // List all mailboxes. 166 | imap.list(); 167 | 168 | for (size_t i = 0; i < imap.mailboxes.size(); i++) 169 | ReadyMail.printf("Attributes: %s, Delimiter: %s, Name: %s\n%s", imap.mailboxes[i][0].c_str(), imap.mailboxes[i][1].c_str(), imap.mailboxes[i][2].c_str(), (i == imap.mailboxes.size() - 1 ? "\n" : "")); 170 | 171 | // Select INBOX mailbox. 172 | // If READ_ONLY_MODE is false, the flag /Seen will set to the fetched message. 173 | imap.select("INBOX", READ_ONLY_MODE); 174 | 175 | // Show the INBOX information. 176 | showMailboxInfo(imap.getMailbox()); 177 | 178 | // Fetch the latest message in INBOX. 179 | imap.fetch(imap.getMailbox().msgCount, dataCb, NULL /* FileCallback */, AWAIT_MODE, MAX_CONTENT_SIZE); 180 | 181 | // To fetch message with UID, use imap.fetchUID instead. 182 | } 183 | 184 | void loop() 185 | { 186 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![ReadyMail Logo](resources/images/logo.svg) 3 | 4 | # ✉️ ReadyMail 5 | 6 | **Fast, lightweight, and asynchronous email client library for Arduino.** 7 | Supports both **SMTP** and **IMAP** with full RFC compliance. Designed for 32-bit MCUs including ESP8266, ESP32, Teensy, Arduino MKR, SAMD, STM32, RP2040, and more. 8 | 9 | ReadyMail is designed to be hardware-agnostic. It supports all filesystem types (e.g. SPIFFS, LittleFS, SD) and all network client libraries, including GSMClient, WiFiClient, EthernetClient, and PPP. This ensures seamless integration across diverse hardware and connectivity environments. 10 | 11 | ![License](https://img.shields.io/badge/license-MIT-blue.svg) 12 | ![Platform](https://img.shields.io/badge/platform-Arduino%2032--bit-green) 13 | ![RFC](https://img.shields.io/badge/RFC-5321%2C%209051%2C%20822-important) 14 | 15 | --- 16 | 17 | ## 📚 Table of Contents 18 | 19 | - [Features](#-features) 20 | - [Installation](#-installation) 21 | - [Supported Devices](#-supported-devices) 22 | - [RFC Compliance](#-rfc-compliance) 23 | - [Getting Started](#-getting-started) 24 | - [Sending Email (SMTP)](#-sending-email-smtp) 25 | - [SMTP Server Rejection and Spam Prevention](#-smtp-server-rejection-and-spam-prevention) 26 | - [Receiving Email (IMAP)](#-receiving-email-imap) 27 | - [Ports & Clients](#-ports--clients) 28 | - [Known Issues](#-known-issues) 29 | - [License](#-license) 30 | - [Advanced Usage](/resources/docs/ADVANCED.md) 31 | - [Troubleshooting Guide](/resources/docs/TROUBLESHOOTING.md) 32 | - [Connection Guide](/resources/docs/CONNECTION_GUIDE.md) 33 | 34 | --- 35 | 36 | ## 🚀 Features 37 | 38 | - ✅ Simple and intuitive interface — minimal setup, full control 39 | - 📎 Supports inline images, attachments (file and nested RFC822 messages), and large static files (text/html). 40 | - 🔐 SSL/TLS and STARTTLS support 41 | - 📥 IMAP search, fetch, append, and idle support 42 | - 🧩 Lightweight and non-blocking design 43 | - 🧠 RFC-compliant message formatting 44 | - 🌐 Compatible with all network client libraries: GSM, WiFi, Ethernet, PPP 45 | - 📁 Supports all Arduino-compatible filesystem types: SD, SDMMC, SPIFFS, LittleFS and more 46 | - 🧰 Full-featured API for advanced use cases: custom commands, envelope parsing, OTA streaming 47 | 48 | --- 49 | 50 | ## 📦 Installation 51 | 52 | ### 🔧 Arduino IDE 53 | 54 | 1. Open Arduino IDE 55 | 2. Go to **Sketch > Include Library > Manage Libraries…** 56 | 3. Search for `ReadyMail` and click **Install** 57 | 58 | Alternatively, install via ZIP file: 59 | 60 | ```text 61 | Download the latest release from: 62 | https://github.com/mobizt/ReadyMail/releases 63 | 64 | Then go to Sketch > Include Library > Add .ZIP Library… 65 | ``` 66 | 67 | --- 68 | 69 | ### ⚙️ PlatformIO 70 | 71 | Add this to your `platformio.ini`: 72 | 73 | ```ini 74 | lib_deps = 75 | mobizt/ReadyMail@^0.3.6 76 | ``` 77 | 78 | > ✅ Supports ESP32, STM32, RP2040, SAMD, Renesas and more — except AVR 79 | 80 | --- 81 | 82 | ### 📁 Manual Installation 83 | 84 | 1. Download the source from [ReadyMail GitHub](https://github.com/mobizt/ReadyMail) 85 | 2. Extract to your Arduino `libraries/ReadyMail` folder 86 | 87 | --- 88 | 89 | ## 🧩 Supported Devices 90 | 91 | - ESP32 / ESP8266 92 | - STM32 / SAMD 93 | - RP2040 / Renesas 94 | - Teensy / Arduino MKR 95 | > ❌ Not compatible with 8-bit AVR devices 96 | 97 | --- 98 | 99 | ## 📖 RFC Compliance 100 | 101 | ReadyMail adheres to key email protocol standards: 102 | 103 | | Protocol | RFC | Description | 104 | |---------|-----|-------------| 105 | | SMTP | [RFC 5321](https://www.rfc-editor.org/rfc/rfc5321.html) | Simple Mail Transfer Protocol | 106 | | IMAP | [RFC 9051](https://datatracker.ietf.org/doc/html/rfc9051) | Internet Message Access Protocol v4rev2 | 107 | | Message Format | [RFC 822](https://www.rfc-editor.org/rfc/rfc822.html) | ARPA Internet Text Messages | 108 | 109 | --- 110 | 111 | ## 🛠️ Getting Started 112 | 113 | ### 📤 Sending Email (SMTP) 114 | 115 | ```cpp 116 | #include 117 | #include 118 | #include 119 | 120 | #define ENABLE_SMTP 121 | #define ENABLE_DEBUG 122 | #include 123 | 124 | WiFiClientSecure ssl_client; 125 | SMTPClient smtp(ssl_client); 126 | 127 | void setup() { 128 | Serial.begin(115200); 129 | WiFi.begin("YOUR_SSID", "YOUR_PASSWORD"); 130 | while (WiFi.status() != WL_CONNECTED) delay(500); 131 | 132 | ssl_client.setInsecure(); 133 | 134 | auto statusCallback = [](SMTPStatus status) { 135 | Serial.println(status.text); 136 | }; 137 | 138 | smtp.connect("smtp.example.com", 465, statusCallback); 139 | 140 | if (smtp.isConnected()) { 141 | smtp.authenticate("user@example.com", "password", readymail_auth_password); 142 | 143 | SMTPMessage msg; 144 | msg.headers.add(rfc822_from, "ReadyMail "); 145 | msg.headers.add(rfc822_to, "Recipient "); 146 | msg.headers.add(rfc822_subject, "Hello from ReadyMail"); 147 | msg.text.body("This is a plain text message."); 148 | msg.html.body("

Hello!

"); 149 | 150 | configTime(0, 0, "pool.ntp.org"); 151 | while (time(nullptr) < 100000) delay(100); 152 | msg.timestamp = time(nullptr); 153 | 154 | smtp.send(msg); 155 | } 156 | } 157 | 158 | void loop() {} 159 | ``` 160 | 161 | --- 162 | 163 | ## 🛡️ SMTP Server Rejection and Spam Prevention 164 | 165 | To ensure successful email delivery and avoid rejection or spam filtering by SMTP servers, follow these best practices: 166 | 167 | - Provide a valid EHLO/HELO hostname or IP 168 | - Set the Date header using `msg.timestamp = time(nullptr);` 169 | - Use `\r\n` (CRLF) line breaks instead of `\n` (LF) 170 | - Sync time using `configTime()` or `WiFi.getTime()` 171 | 172 | --- 173 | 174 | ### 📥 Receiving Email (IMAP) 175 | 176 | ```cpp 177 | #include 178 | #include 179 | #include 180 | 181 | #define ENABLE_IMAP 182 | #define ENABLE_DEBUG 183 | #include 184 | 185 | WiFiClientSecure ssl_client; 186 | IMAPClient imap(ssl_client); 187 | 188 | void setup() { 189 | Serial.begin(115200); 190 | WiFi.begin("YOUR_SSID", "YOUR_PASSWORD"); 191 | while (WiFi.status() != WL_CONNECTED) delay(500); 192 | 193 | ssl_client.setInsecure(); 194 | 195 | auto statusCallback = [](IMAPStatus status) { 196 | Serial.println(status.text); 197 | }; 198 | 199 | auto dataCallback = [](IMAPCallbackData data) { 200 | if (data.event() == imap_data_event_fetch_envelope) { 201 | for (int i = 0; i < data.headerCount(); i++) { 202 | Serial.printf("%s: %s\n", data.getHeader(i).first.c_str(), data.getHeader(i).second.c_str()); 203 | } 204 | } 205 | }; 206 | 207 | imap.connect("imap.example.com", 993, statusCallback); 208 | 209 | if (imap.isConnected()) { 210 | imap.authenticate("user@example.com", "password", readymail_auth_password); 211 | imap.select("INBOX"); 212 | imap.fetch(imap.getMailbox().msgCount, dataCallback); 213 | } 214 | } 215 | 216 | void loop() {} 217 | ``` 218 | 219 | --- 220 | 221 | ## 🔌 Ports & Clients 222 | 223 | | Protocol | Port | Security | Notes | 224 | |---------|------|----------|-------| 225 | | SMTP | 465 | SSL | Recommended | 226 | | SMTP | 587 | STARTTLS | Requires upgrade-capable client | 227 | | IMAP | 993 | SSL | Recommended | 228 | | IMAP | 143 | STARTTLS | Requires upgrade-capable client | 229 | 230 | > See [Connection Guide](/resources/docs/CONNECTION_GUIDE.md) for client selection and port compatibility. 231 | 232 | --- 233 | 234 | ## ⚠️ Known Issues 235 | 236 | - ESP8266 requires buffer tuning via `setBufferSizes()` 237 | - ESP32 v3.x may hang on `setPlainStart()` in plain mode 238 | - Some devices may fail TLS handshake due to memory limits 239 | 240 | > See [Troubleshooting Guide](/resources/docs/TROUBLESHOOTING.md) for detailed solutions and platform-specific workarounds. 241 | 242 | --- 243 | 244 | ## 📄 License 245 | 246 | MIT License © 2025 Suwatchai K (Mobizt). 247 | See [LICENSE](LICENSE) for details. 248 | 249 | --- 250 | 251 | ## 📘 Advanced Usage 252 | 253 | For developers who need deeper control, debugging, or custom command support, see [Advanced Usage](/resources/docs/ADVANCED.md) for: 254 | 255 | - SMTP and IMAP status monitoring 256 | - Custom command responses 257 | - Envelope and body parsing 258 | - OTA and file streaming techniques 259 | 260 | --- 261 | 262 | ## 🧯 Troubleshooting Guide 263 | 264 | If you encounter crashes, SSL handshake failures, or unexpected behavior on ESP8266, ESP32, or Renesas boards, see the [Troubleshooting Guide](/resources/docs/TROUBLESHOOTING.md) for: 265 | 266 | - Buffer tuning and heap configuration 267 | - TLS handshake issues 268 | - IMAP parsing limitations 269 | - Platform-specific workarounds 270 | 271 | --- 272 | 273 | ## 🌐 Connection Guide 274 | 275 | To learn how to select the correct ports and SSL/network clients for your board, see the [Connection Guide](/resources/docs/CONNECTION_GUIDE.md) for: 276 | 277 | - SSL vs STARTTLS vs Plain Text 278 | - Client compatibility matrix 279 | - Dynamic port switching examples 280 | - ESP_SSLClient and WiFiClientSecure usage 281 | -------------------------------------------------------------------------------- /examples/Sending/ESP32Camera/camera_pins.h: -------------------------------------------------------------------------------- 1 | 2 | #if defined(CAMERA_MODEL_WROVER_KIT) 3 | #define PWDN_GPIO_NUM -1 4 | #define RESET_GPIO_NUM -1 5 | #define XCLK_GPIO_NUM 21 6 | #define SIOD_GPIO_NUM 26 7 | #define SIOC_GPIO_NUM 27 8 | 9 | #define Y9_GPIO_NUM 35 10 | #define Y8_GPIO_NUM 34 11 | #define Y7_GPIO_NUM 39 12 | #define Y6_GPIO_NUM 36 13 | #define Y5_GPIO_NUM 19 14 | #define Y4_GPIO_NUM 18 15 | #define Y3_GPIO_NUM 5 16 | #define Y2_GPIO_NUM 4 17 | #define VSYNC_GPIO_NUM 25 18 | #define HREF_GPIO_NUM 23 19 | #define PCLK_GPIO_NUM 22 20 | 21 | #elif defined(CAMERA_MODEL_ESP_EYE) 22 | #define PWDN_GPIO_NUM -1 23 | #define RESET_GPIO_NUM -1 24 | #define XCLK_GPIO_NUM 4 25 | #define SIOD_GPIO_NUM 18 26 | #define SIOC_GPIO_NUM 23 27 | 28 | #define Y9_GPIO_NUM 36 29 | #define Y8_GPIO_NUM 37 30 | #define Y7_GPIO_NUM 38 31 | #define Y6_GPIO_NUM 39 32 | #define Y5_GPIO_NUM 35 33 | #define Y4_GPIO_NUM 14 34 | #define Y3_GPIO_NUM 13 35 | #define Y2_GPIO_NUM 34 36 | #define VSYNC_GPIO_NUM 5 37 | #define HREF_GPIO_NUM 27 38 | #define PCLK_GPIO_NUM 25 39 | 40 | #define LED_GPIO_NUM 22 41 | 42 | #elif defined(CAMERA_MODEL_M5STACK_PSRAM) 43 | #define PWDN_GPIO_NUM -1 44 | #define RESET_GPIO_NUM 15 45 | #define XCLK_GPIO_NUM 27 46 | #define SIOD_GPIO_NUM 25 47 | #define SIOC_GPIO_NUM 23 48 | 49 | #define Y9_GPIO_NUM 19 50 | #define Y8_GPIO_NUM 36 51 | #define Y7_GPIO_NUM 18 52 | #define Y6_GPIO_NUM 39 53 | #define Y5_GPIO_NUM 5 54 | #define Y4_GPIO_NUM 34 55 | #define Y3_GPIO_NUM 35 56 | #define Y2_GPIO_NUM 32 57 | #define VSYNC_GPIO_NUM 22 58 | #define HREF_GPIO_NUM 26 59 | #define PCLK_GPIO_NUM 21 60 | 61 | #elif defined(CAMERA_MODEL_M5STACK_V2_PSRAM) 62 | #define PWDN_GPIO_NUM -1 63 | #define RESET_GPIO_NUM 15 64 | #define XCLK_GPIO_NUM 27 65 | #define SIOD_GPIO_NUM 22 66 | #define SIOC_GPIO_NUM 23 67 | 68 | #define Y9_GPIO_NUM 19 69 | #define Y8_GPIO_NUM 36 70 | #define Y7_GPIO_NUM 18 71 | #define Y6_GPIO_NUM 39 72 | #define Y5_GPIO_NUM 5 73 | #define Y4_GPIO_NUM 34 74 | #define Y3_GPIO_NUM 35 75 | #define Y2_GPIO_NUM 32 76 | #define VSYNC_GPIO_NUM 25 77 | #define HREF_GPIO_NUM 26 78 | #define PCLK_GPIO_NUM 21 79 | 80 | #elif defined(CAMERA_MODEL_M5STACK_WIDE) 81 | #define PWDN_GPIO_NUM -1 82 | #define RESET_GPIO_NUM 15 83 | #define XCLK_GPIO_NUM 27 84 | #define SIOD_GPIO_NUM 22 85 | #define SIOC_GPIO_NUM 23 86 | 87 | #define Y9_GPIO_NUM 19 88 | #define Y8_GPIO_NUM 36 89 | #define Y7_GPIO_NUM 18 90 | #define Y6_GPIO_NUM 39 91 | #define Y5_GPIO_NUM 5 92 | #define Y4_GPIO_NUM 34 93 | #define Y3_GPIO_NUM 35 94 | #define Y2_GPIO_NUM 32 95 | #define VSYNC_GPIO_NUM 25 96 | #define HREF_GPIO_NUM 26 97 | #define PCLK_GPIO_NUM 21 98 | 99 | #define LED_GPIO_NUM 2 100 | 101 | #elif defined(CAMERA_MODEL_M5STACK_ESP32CAM) 102 | #define PWDN_GPIO_NUM -1 103 | #define RESET_GPIO_NUM 15 104 | #define XCLK_GPIO_NUM 27 105 | #define SIOD_GPIO_NUM 25 106 | #define SIOC_GPIO_NUM 23 107 | 108 | #define Y9_GPIO_NUM 19 109 | #define Y8_GPIO_NUM 36 110 | #define Y7_GPIO_NUM 18 111 | #define Y6_GPIO_NUM 39 112 | #define Y5_GPIO_NUM 5 113 | #define Y4_GPIO_NUM 34 114 | #define Y3_GPIO_NUM 35 115 | #define Y2_GPIO_NUM 17 116 | #define VSYNC_GPIO_NUM 22 117 | #define HREF_GPIO_NUM 26 118 | #define PCLK_GPIO_NUM 21 119 | 120 | #elif defined(CAMERA_MODEL_M5STACK_UNITCAM) 121 | #define PWDN_GPIO_NUM -1 122 | #define RESET_GPIO_NUM 15 123 | #define XCLK_GPIO_NUM 27 124 | #define SIOD_GPIO_NUM 25 125 | #define SIOC_GPIO_NUM 23 126 | 127 | #define Y9_GPIO_NUM 19 128 | #define Y8_GPIO_NUM 36 129 | #define Y7_GPIO_NUM 18 130 | #define Y6_GPIO_NUM 39 131 | #define Y5_GPIO_NUM 5 132 | #define Y4_GPIO_NUM 34 133 | #define Y3_GPIO_NUM 35 134 | #define Y2_GPIO_NUM 32 135 | #define VSYNC_GPIO_NUM 22 136 | #define HREF_GPIO_NUM 26 137 | #define PCLK_GPIO_NUM 21 138 | 139 | #elif defined(CAMERA_MODEL_AI_THINKER) 140 | #define PWDN_GPIO_NUM 32 141 | #define RESET_GPIO_NUM -1 142 | #define XCLK_GPIO_NUM 0 143 | #define SIOD_GPIO_NUM 26 144 | #define SIOC_GPIO_NUM 27 145 | 146 | #define Y9_GPIO_NUM 35 147 | #define Y8_GPIO_NUM 34 148 | #define Y7_GPIO_NUM 39 149 | #define Y6_GPIO_NUM 36 150 | #define Y5_GPIO_NUM 21 151 | #define Y4_GPIO_NUM 19 152 | #define Y3_GPIO_NUM 18 153 | #define Y2_GPIO_NUM 5 154 | #define VSYNC_GPIO_NUM 25 155 | #define HREF_GPIO_NUM 23 156 | #define PCLK_GPIO_NUM 22 157 | 158 | // 4 for flash led or 33 for normal led 159 | #define LED_GPIO_NUM 4 160 | 161 | #elif defined(CAMERA_MODEL_TTGO_T_JOURNAL) 162 | #define PWDN_GPIO_NUM 0 163 | #define RESET_GPIO_NUM 15 164 | #define XCLK_GPIO_NUM 27 165 | #define SIOD_GPIO_NUM 25 166 | #define SIOC_GPIO_NUM 23 167 | 168 | #define Y9_GPIO_NUM 19 169 | #define Y8_GPIO_NUM 36 170 | #define Y7_GPIO_NUM 18 171 | #define Y6_GPIO_NUM 39 172 | #define Y5_GPIO_NUM 5 173 | #define Y4_GPIO_NUM 34 174 | #define Y3_GPIO_NUM 35 175 | #define Y2_GPIO_NUM 17 176 | #define VSYNC_GPIO_NUM 22 177 | #define HREF_GPIO_NUM 26 178 | #define PCLK_GPIO_NUM 21 179 | 180 | #elif defined(CAMERA_MODEL_XIAO_ESP32S3) 181 | #define PWDN_GPIO_NUM -1 182 | #define RESET_GPIO_NUM -1 183 | #define XCLK_GPIO_NUM 10 184 | #define SIOD_GPIO_NUM 40 185 | #define SIOC_GPIO_NUM 39 186 | 187 | #define Y9_GPIO_NUM 48 188 | #define Y8_GPIO_NUM 11 189 | #define Y7_GPIO_NUM 12 190 | #define Y6_GPIO_NUM 14 191 | #define Y5_GPIO_NUM 16 192 | #define Y4_GPIO_NUM 18 193 | #define Y3_GPIO_NUM 17 194 | #define Y2_GPIO_NUM 15 195 | #define VSYNC_GPIO_NUM 38 196 | #define HREF_GPIO_NUM 47 197 | #define PCLK_GPIO_NUM 13 198 | 199 | #elif defined(CAMERA_MODEL_ESP32_CAM_BOARD) 200 | // The 18 pin header on the board has Y5 and Y3 swapped 201 | #define USE_BOARD_HEADER 0 202 | #define PWDN_GPIO_NUM 32 203 | #define RESET_GPIO_NUM 33 204 | #define XCLK_GPIO_NUM 4 205 | #define SIOD_GPIO_NUM 18 206 | #define SIOC_GPIO_NUM 23 207 | 208 | #define Y9_GPIO_NUM 36 209 | #define Y8_GPIO_NUM 19 210 | #define Y7_GPIO_NUM 21 211 | #define Y6_GPIO_NUM 39 212 | #if USE_BOARD_HEADER 213 | #define Y5_GPIO_NUM 13 214 | #else 215 | #define Y5_GPIO_NUM 35 216 | #endif 217 | #define Y4_GPIO_NUM 14 218 | #if USE_BOARD_HEADER 219 | #define Y3_GPIO_NUM 35 220 | #else 221 | #define Y3_GPIO_NUM 13 222 | #endif 223 | #define Y2_GPIO_NUM 34 224 | #define VSYNC_GPIO_NUM 5 225 | #define HREF_GPIO_NUM 27 226 | #define PCLK_GPIO_NUM 25 227 | 228 | #elif defined(CAMERA_MODEL_ESP32S3_CAM_LCD) 229 | #define PWDN_GPIO_NUM -1 230 | #define RESET_GPIO_NUM -1 231 | #define XCLK_GPIO_NUM 40 232 | #define SIOD_GPIO_NUM 17 233 | #define SIOC_GPIO_NUM 18 234 | 235 | #define Y9_GPIO_NUM 39 236 | #define Y8_GPIO_NUM 41 237 | #define Y7_GPIO_NUM 42 238 | #define Y6_GPIO_NUM 12 239 | #define Y5_GPIO_NUM 3 240 | #define Y4_GPIO_NUM 14 241 | #define Y3_GPIO_NUM 47 242 | #define Y2_GPIO_NUM 13 243 | #define VSYNC_GPIO_NUM 21 244 | #define HREF_GPIO_NUM 38 245 | #define PCLK_GPIO_NUM 11 246 | 247 | #elif defined(CAMERA_MODEL_ESP32S2_CAM_BOARD) 248 | // The 18 pin header on the board has Y5 and Y3 swapped 249 | #define USE_BOARD_HEADER 0 250 | #define PWDN_GPIO_NUM 1 251 | #define RESET_GPIO_NUM 2 252 | #define XCLK_GPIO_NUM 42 253 | #define SIOD_GPIO_NUM 41 254 | #define SIOC_GPIO_NUM 18 255 | 256 | #define Y9_GPIO_NUM 16 257 | #define Y8_GPIO_NUM 39 258 | #define Y7_GPIO_NUM 40 259 | #define Y6_GPIO_NUM 15 260 | #if USE_BOARD_HEADER 261 | #define Y5_GPIO_NUM 12 262 | #else 263 | #define Y5_GPIO_NUM 13 264 | #endif 265 | #define Y4_GPIO_NUM 5 266 | #if USE_BOARD_HEADER 267 | #define Y3_GPIO_NUM 13 268 | #else 269 | #define Y3_GPIO_NUM 12 270 | #endif 271 | #define Y2_GPIO_NUM 14 272 | #define VSYNC_GPIO_NUM 38 273 | #define HREF_GPIO_NUM 4 274 | #define PCLK_GPIO_NUM 3 275 | 276 | #elif defined(CAMERA_MODEL_ESP32S3_EYE) 277 | #define PWDN_GPIO_NUM -1 278 | #define RESET_GPIO_NUM -1 279 | #define XCLK_GPIO_NUM 15 280 | #define SIOD_GPIO_NUM 4 281 | #define SIOC_GPIO_NUM 5 282 | 283 | #define Y2_GPIO_NUM 11 284 | #define Y3_GPIO_NUM 9 285 | #define Y4_GPIO_NUM 8 286 | #define Y5_GPIO_NUM 10 287 | #define Y6_GPIO_NUM 12 288 | #define Y7_GPIO_NUM 18 289 | #define Y8_GPIO_NUM 17 290 | #define Y9_GPIO_NUM 16 291 | 292 | #define VSYNC_GPIO_NUM 6 293 | #define HREF_GPIO_NUM 7 294 | #define PCLK_GPIO_NUM 13 295 | 296 | #elif defined(CAMERA_MODEL_DFRobot_FireBeetle2_ESP32S3) || defined(CAMERA_MODEL_DFRobot_Romeo_ESP32S3) 297 | #define PWDN_GPIO_NUM -1 298 | #define RESET_GPIO_NUM -1 299 | #define XCLK_GPIO_NUM 45 300 | #define SIOD_GPIO_NUM 1 301 | #define SIOC_GPIO_NUM 2 302 | 303 | #define Y9_GPIO_NUM 48 304 | #define Y8_GPIO_NUM 46 305 | #define Y7_GPIO_NUM 8 306 | #define Y6_GPIO_NUM 7 307 | #define Y5_GPIO_NUM 4 308 | #define Y4_GPIO_NUM 41 309 | #define Y3_GPIO_NUM 40 310 | #define Y2_GPIO_NUM 39 311 | #define VSYNC_GPIO_NUM 6 312 | #define HREF_GPIO_NUM 42 313 | #define PCLK_GPIO_NUM 5 314 | 315 | #else 316 | #error "Camera model not selected" 317 | #endif -------------------------------------------------------------------------------- /src/imap/IMAPResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 Suwatchai K. 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef IMAP_RESPONSE_H 8 | #define IMAP_RESPONSE_H 9 | #if defined(ENABLE_IMAP) 10 | #include 11 | #include "Common.h" 12 | #include "IMAPBase.h" 13 | #include "IMAPConnection.h" 14 | #include "IMAPSend.h" 15 | #include "./core/ReadyTimer.h" 16 | #include "./core/QBDecoder.h" 17 | #include "Parser.h" 18 | 19 | namespace ReadyMailIMAP 20 | { 21 | class IMAPResponse : public IMAPBase 22 | { 23 | friend class IMAPConnection; 24 | friend class IMAPSend; 25 | friend class IMAPClient; 26 | 27 | public: 28 | void begin(imap_context *imap_ctx) 29 | { 30 | beginBase(imap_ctx); 31 | line.remove(0, line.length()); 32 | complete = false; 33 | resp_timer.feed(imap_ctx->options.timeout.read / 1000); 34 | line.reserve(limit + 128); 35 | } 36 | 37 | imap_function_return_code handleResponse() 38 | { 39 | sys_yield(); 40 | bool err = !serverConnected() || (!imap_ctx->options.idling && cState() != imap_state_idle && cState() != imap_state_done && readTimeout()); 41 | if (err || (!imap_ctx->options.searching && (cState() == imap_state_fetch_envelope || cState() == imap_state_fetch_body_part) && cMsg().files.size() && !cMsg().files[cFileIndex()].fetch)) 42 | { 43 | cCode() = err ? function_return_failure : function_return_success; 44 | return cCode(); 45 | } 46 | 47 | cCode() = function_return_undefined; 48 | if (!imap_ctx->options.multiline) 49 | clear(line); 50 | 51 | int readLen = readLine(line); 52 | if (readLen > 0) 53 | { 54 | #if defined(ENABLE_CORE_DEBUG) 55 | if (cState() != imap_state_search && cState() != imap_state_fetch_envelope && cState() != imap_state_fetch_body_part && !imap_ctx->options.multiline) 56 | setDebug(imap_ctx, line, true); 57 | #endif 58 | 59 | getResponseStatus(line, cState() == imap_state_initial_state ? "*" : imap_ctx->tag, *imap_ctx->status); 60 | 61 | if (cType() == imap_response_ok) 62 | cCode() = function_return_success; 63 | 64 | if (cType() == imap_response_bad || cType() == imap_response_no) 65 | { 66 | cCode() = function_return_failure; 67 | setError(imap_ctx, __func__, IMAP_ERROR_RESPONSE, imap_ctx->status->text); 68 | } 69 | 70 | if ((cState() == imap_state_fetch_envelope || cState() == imap_state_fetch_body_part) && line.indexOf(imap_ctx->tag) == 0 && !cMsg().exists) 71 | { 72 | cCode() = function_return_failure; 73 | setError(imap_ctx, __func__, IMAP_ERROR_MESSAGE_NOT_EXISTS); 74 | } 75 | 76 | switch (cState()) 77 | { 78 | case imap_state_greeting: 79 | case imap_state_auth_login: 80 | case imap_state_auth_plain: 81 | case imap_state_auth_xoauth2: 82 | 83 | if (line[0] == '*') 84 | parser.parseCaps(line, imap_ctx); 85 | 86 | if (cState() == imap_state_auth_xoauth2 || cState() == imap_state_auth_plain) 87 | { 88 | if (cState() == imap_state_auth_xoauth2) 89 | { 90 | char *decoded = rd_b64_dec(rd_cast(imap_ctx->status->text.c_str())); 91 | if (decoded) 92 | { 93 | if (indexOf(decoded, "{\"status\":") > -1) 94 | { 95 | cCode() = function_return_failure; 96 | setError(imap_ctx, __func__, AUTH_ERROR_AUTHENTICATION, decoded); 97 | } 98 | rd_free(&decoded); 99 | decoded = nullptr; 100 | } 101 | } 102 | 103 | // In case SASL-IR extension does not support, check for initial zero-length server challenge first "+ " 104 | if (!imap_ctx->auth_caps[imap_auth_cap_sasl_ir] && line.indexOf("+ ") == 0) 105 | { 106 | cCode() = function_return_success; 107 | cState() = cState() == imap_state_auth_xoauth2 ? imap_state_auth_xoauth2_next : imap_state_auth_plain_next; 108 | } 109 | } 110 | break; 111 | 112 | case imap_state_list: 113 | if (line[0] == '*') 114 | { 115 | std::array buf; 116 | parser.parseMailbox(line, buf); 117 | if (buf[2].length()) 118 | imap_ctx->mailboxes->push_back(buf); 119 | } 120 | break; 121 | 122 | case imap_state_examine: 123 | case imap_state_select: 124 | if (line[0] == '*') 125 | parser.parseExamine(line, mailbox_info, imap_ctx); 126 | break; 127 | 128 | case imap_state_search: 129 | parser.parseSearch(line, imap_ctx, msgNumVec()); 130 | break; 131 | 132 | case imap_state_fetch_envelope: 133 | case imap_state_fetch_body_part: 134 | parser.parseFetch(line, imap_ctx, cMsg(), cState(), cMsg().files[cFileIndex()]); 135 | break; 136 | 137 | case imap_state_append_init: 138 | case imap_state_idle: 139 | if (line[0] == '+') 140 | { 141 | cCode() = function_return_success; 142 | return cCode(); 143 | } 144 | else if (line[0] == '*' && cState() == imap_state_idle) 145 | parser.parseIdle(line, mailbox_info, imap_ctx); 146 | break; 147 | 148 | case imap_state_send_command: 149 | if (cCode() != function_return_failure && cCode() != function_return_success) 150 | { 151 | if (imap_ctx->cb.cmd) 152 | imap_ctx->cb.command_response.text = line.substring(0, line.length() - 2); 153 | else 154 | imap_ctx->cb.command_response.text += line; 155 | 156 | imap_ctx->cb.command_response.command = imap_ctx->cmd; 157 | imap_ctx->cb.command_response.errorCode = 0; 158 | imap_ctx->cb.command_response.isComplete = false; 159 | } 160 | else 161 | { 162 | if (imap_ctx->cb.cmd) 163 | imap_ctx->cb.command_response.text.remove(0, imap_ctx->cb.command_response.text.length()); 164 | imap_ctx->cb.command_response.command = imap_ctx->cmd; 165 | imap_ctx->cb.command_response.errorCode = cCode() == function_return_failure ? IMAP_ERROR_RESPONSE : 0; 166 | imap_ctx->cb.command_response.isComplete = true; 167 | } 168 | 169 | if (imap_ctx->cb.cmd) 170 | imap_ctx->cb.cmd(imap_ctx->cb.command_response); 171 | 172 | break; 173 | 174 | default: 175 | break; 176 | } 177 | } 178 | 179 | if (cType() == imap_response_undefined && cState() == imap_state_initial_state) 180 | cCode() = function_return_continue; 181 | 182 | return cCode(); 183 | } 184 | 185 | void getResponseStatus(String &line, const String &tag, imap_response_status_t &status) 186 | { 187 | cType() = imap_response_undefined; 188 | 189 | if (line.indexOf(tag) == 0 && line.length() >= 2 && line[line.length() - 2] != '\r' && line[line.length() - 1] != '\n') 190 | { 191 | imap_ctx->options.multiline = true; 192 | return; 193 | } 194 | 195 | if (line.indexOf(tag) == 0) 196 | { 197 | if (line.indexOf("OK") > 0) 198 | cType() = imap_response_ok; 199 | else if (line.indexOf("NO") > 0) 200 | cType() = imap_response_no; 201 | else if (line.indexOf("BAD") > 0) 202 | cType() = imap_response_bad; 203 | 204 | if (cType() != imap_response_undefined) 205 | { 206 | imap_ctx->options.multiline = false; 207 | status.text = line.substring(tag.length() + (cType() == imap_response_bad ? 5 : 4), line.indexOf("\r\n")); 208 | } 209 | } 210 | } 211 | 212 | bool readTimeout() 213 | { 214 | if (!resp_timer.isRunning()) 215 | resp_timer.feed(imap_ctx->options.timeout.read / 1000); 216 | 217 | if (resp_timer.remaining() == 0) 218 | { 219 | resp_timer.feed(imap_ctx->options.timeout.read / 1000); 220 | setError(imap_ctx, __func__, TCP_CLIENT_ERROR_READ_DATA); 221 | return true; 222 | } 223 | return false; 224 | } 225 | 226 | int readLine(String &buf) 227 | { 228 | int p = 0; 229 | while (tcpAvailable()) 230 | { 231 | #if defined(ESP8266) 232 | sys_yield(); 233 | if (!imap_ctx->client || !imap_ctx->client->connected()) 234 | return -1; 235 | #endif 236 | int res = tcpRead(); 237 | if (res > -1) 238 | { 239 | buf += (char)res; 240 | p++; 241 | if (res == '\n' || (p >= (int)limit && res == ' ')) 242 | return p; 243 | } 244 | } 245 | return p; 246 | } 247 | 248 | int tcpAvailable() { return imap_ctx->client ? imap_ctx->client->available() : 0; } 249 | int tcpRead() { return imap_ctx->client ? imap_ctx->client->read() : -1; } 250 | void stop(bool forceStop = false) 251 | { 252 | stopImpl(forceStop); 253 | clear(line); 254 | } 255 | 256 | private: 257 | bool complete = false; 258 | String line; 259 | ReadyTimer resp_timer, idle_timer; 260 | MailboxInfo mailbox_info; 261 | size_t limit = 2048; 262 | IMAPParser parser; 263 | }; 264 | } 265 | #endif 266 | #endif -------------------------------------------------------------------------------- /src/imap/IMAPBase.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 Suwatchai K. 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef IMAP_BASE_H 8 | #define IMAP_BASE_H 9 | #if defined(ENABLE_IMAP) 10 | #include 11 | #include "Common.h" 12 | 13 | namespace ReadyMailIMAP 14 | { 15 | class IMAPBase 16 | { 17 | friend class IMAPClient; 18 | 19 | public: 20 | #if defined(ENABLE_DEBUG) 21 | static void setDebug(imap_context *imap_ctx, const String &info, bool core = false) 22 | { 23 | if (imap_ctx->status) 24 | { 25 | char *p = rd_mem(info.length() + 1); 26 | strcpy(p, info.c_str()); 27 | char *pp = p; 28 | char *end = p; 29 | while (pp != NULL) 30 | { 31 | rd_strsep(&end, "\r\n"); 32 | if (strlen(pp) > 0) 33 | { 34 | if (strlen(pp) == 1 && pp[0] == 32) 35 | { 36 | pp = end; 37 | continue; 38 | } 39 | else 40 | { 41 | imap_ctx->status->text = (core ? "[core] " : " "); 42 | imap_ctx->status->text += pp; 43 | print(imap_ctx); 44 | } 45 | } 46 | pp = end; 47 | } 48 | rd_free(&p); 49 | } 50 | } 51 | #endif 52 | protected: 53 | imap_context *imap_ctx = nullptr; 54 | ReadyTimer err_timer; 55 | 56 | void beginBase(imap_context *imap_ctx) 57 | { 58 | this->imap_ctx = imap_ctx; 59 | imap_ctx->status->errorCode = 0; 60 | imap_ctx->status->text.remove(0, imap_ctx->status->text.length()); 61 | } 62 | 63 | int indexOf(const char *str, const char *find) 64 | { 65 | const char *s = strstr(str, find); 66 | return (s) ? (int)(s - str) : -1; 67 | } 68 | 69 | int indexOf(const char *str, char find) 70 | { 71 | const char *s = strchr(str, find); 72 | return (s) ? (int)(s - str) : -1; 73 | } 74 | 75 | void clear(String &s) { s.remove(0, s.length()); } 76 | 77 | bool tcpSend(bool crlf, uint8_t argLen, ...) 78 | { 79 | String data; 80 | data.reserve(512); 81 | va_list args; 82 | va_start(args, argLen); 83 | for (int i = 0; i < argLen; ++i) 84 | data += va_arg(args, const char *); 85 | va_end(args); 86 | #if defined(ENABLE_CORE_DEBUG) 87 | setDebug(imap_ctx, data, true); 88 | #endif 89 | data += crlf ? "\r\n" : ""; 90 | return tcpSend(rd_cast(data.c_str()), data.length()) == data.length(); 91 | } 92 | 93 | size_t tcpSend(const uint8_t *data, size_t len) { return imap_ctx->client ? imap_ctx->client->write(data, len) : 0; } 94 | 95 | bool setError(imap_context *imap_ctx, const char *func, int code, const String &msg = "") 96 | { 97 | if (imap_ctx->status) 98 | { 99 | imap_ctx->status->errorCode = code; 100 | String buf; 101 | rd_print_to(buf, 100, "[%d] %s(): %s\n", code, func, (msg.length() ? msg.c_str() : errMsg(code).c_str())); 102 | imap_ctx->status->text = buf; 103 | if (!err_timer.isRunning() || err_timer.remaining() == 0) 104 | { 105 | err_timer.feed(2); 106 | print(imap_ctx); 107 | } 108 | } 109 | resetProcessFlag(); 110 | releaseSMTP(); 111 | imap_ctx->server_status->ret = function_return_failure; 112 | return false; 113 | } 114 | 115 | void setProcessFlag(bool &flag) { flag = true; } 116 | 117 | void clearAllProcessFlags() 118 | { 119 | imap_ctx->options.searching = false; 120 | imap_ctx->options.idling = false; 121 | imap_ctx->options.processing = false; 122 | } 123 | 124 | void resetProcessFlag() 125 | { 126 | if ((cState() == imap_state_fetch_envelope && imap_ctx->options.searching) || (cState() != imap_state_search && cState() != imap_state_done && cState() != imap_state_idle)) 127 | imap_ctx->options.processing = false; 128 | 129 | if (cState() == imap_state_search) 130 | imap_ctx->options.searching = false; 131 | 132 | if (cState() == imap_state_done || cState() == imap_state_idle) 133 | imap_ctx->options.idling = false; 134 | } 135 | 136 | void releaseSMTP() 137 | { 138 | #if defined(ENABLE_IMAP_APPEND) 139 | if (imap_ctx->smtp) 140 | delete imap_ctx->smtp; 141 | imap_ctx->smtp = nullptr; 142 | imap_ctx->msg.clear(); 143 | #endif 144 | } 145 | 146 | bool isIdleState(const char *func) 147 | { 148 | if (imap_ctx->options.processing || imap_ctx->options.searching) 149 | return setError(imap_ctx, func, IMAP_ERROR_PROCESSING); 150 | return true; 151 | } 152 | 153 | void deAuthenticate() { imap_ctx->server_status->authenticated = false; } 154 | 155 | void exitState(imap_function_return_code &ret, bool &flag) 156 | { 157 | ret = function_return_exit; 158 | flag = false; 159 | } 160 | 161 | #if defined(ENABLE_DEBUG) 162 | void setDebugState(imap_state state, const String &msg) 163 | { 164 | err_timer.stop(); 165 | tState() = state; 166 | setDebug(imap_ctx, msg); 167 | } 168 | #endif 169 | 170 | bool serverConnected() 171 | { 172 | #if defined(ESP32) 173 | // In ESP32 v3.x, if WiFiClientSecure was used, it can hang when. 174 | // 1. connect() in ssl or plain 175 | // 2. stop() 176 | // 3. setPlainStart() 177 | // 4. connected() <--- hung because of peek_net_receive() until timeed out 178 | 179 | // The work around is to define the fdset for write in the lwIP's select function 180 | // https://github.com/espressif/arduino-esp32/blob/master/libraries/NetworkClientSecure/src/ssl_client.cpp#L455 181 | 182 | // Since we don't know the type of Arduino Client that is assigned to the SMTPClient, 183 | // the status flag (serverStatus) will be used instead of calling Client's 184 | // connected() directly. 185 | return serverStatus(); 186 | #else 187 | return imap_ctx->client && imap_ctx->client->connected(); 188 | #endif 189 | } 190 | 191 | void stopImpl(bool forceStop = false) 192 | { 193 | if (forceStop || serverConnected()) 194 | imap_ctx->client->stop(); 195 | 196 | serverStatus() = false; 197 | imap_ctx->server_status->secured = false; 198 | imap_ctx->server_status->server_greeting_ack = false; 199 | imap_ctx->server_status->authenticated = false; 200 | clearAllProcessFlags(); 201 | cState() = imap_state_prompt; 202 | } 203 | 204 | // current message index 205 | int &cMsgIndex() { return imap_ctx->cur_msg_index; } 206 | 207 | // current message 208 | imap_msg_ctx &cMsg() 209 | { 210 | if (cMsgIndex() >= (int)messagesVec().size()) 211 | { 212 | imap_msg_ctx d; 213 | messagesVec().push_back(d); 214 | } 215 | return messagesVec()[cMsgIndex()]; 216 | } 217 | 218 | // current file index 219 | int &cFileIndex() { return cMsg().cur_file_index; } 220 | 221 | // current message number 222 | uint32_t cMsgNum() { return msgNumVec().size() > 0 ? msgNumVec()[cMsgIndex()] : 0; } 223 | 224 | // Search result 225 | std::vector &msgNumVec() { return imap_ctx->cb_data.msgNums; } 226 | 227 | // messages list 228 | std::vector &messagesVec() { return imap_ctx->messages; } 229 | 230 | // current state 231 | imap_state &cState() { return imap_ctx->server_status->state_info.state; } 232 | 233 | // response type 234 | imap_response_types &cType() { return imap_ctx->resp_type; } 235 | 236 | // target state 237 | imap_state &tState() { return imap_ctx->server_status->state_info.target; } 238 | 239 | // current function return code 240 | imap_function_return_code &cCode() { return imap_ctx->server_status->ret; } 241 | 242 | bool &serverStatus() { return imap_ctx->server_status->connected; } 243 | 244 | static void print(imap_context *imap_ctx) 245 | { 246 | imap_ctx->status->state = (imap_ctx->server_status->state_info.target > 0 ? imap_ctx->server_status->state_info.target : imap_ctx->server_status->state_info.state); 247 | if (imap_ctx->cb.resp) 248 | imap_ctx->cb.resp(*imap_ctx->status); 249 | } 250 | 251 | String errMsg(int code) 252 | { 253 | String msg; 254 | #if defined(ENABLE_DEBUG) || defined(ENABLE_CORE_DEBUG) 255 | msg = rd_err(code); 256 | if (msg.length() == 0) 257 | { 258 | switch (code) 259 | { 260 | case IMAP_ERROR_RESPONSE: 261 | msg = "server returning error"; 262 | break; 263 | case IMAP_ERROR_NO_MAILBOX: 264 | msg = "No mailbox selected"; 265 | break; 266 | case IMAP_ERROR_INVALID_SEARCH_CRITERIA: 267 | msg = "Invalid search criteria"; 268 | break; 269 | case IMAP_ERROR_MODSEQ_WAS_NOT_SUPPORTED: 270 | msg = "Modsequences does not support"; 271 | break; 272 | case IMAP_ERROR_IDLE_NOT_SUPPORTED: 273 | msg = "Mailbox idling does not support"; 274 | break; 275 | case IMAP_ERROR_MESSAGE_NOT_EXISTS: 276 | msg = "Message does not exist"; 277 | break; 278 | case IMAP_ERROR_PROCESSING: 279 | msg = "The last process does not yet finished"; 280 | break; 281 | case IMAP_ERROR_MAILBOX_NOT_EXISTS: 282 | msg = "The selected mailbox does not exist"; 283 | break; 284 | case IMAP_ERROR_NO_CALLBACK: 285 | msg = "No FileCallback and DataCallback are assigned"; 286 | break; 287 | case IMAP_ERROR_COMMAND_NOT_ALLOW: 288 | msg = "This command is not allowed"; 289 | break; 290 | case IMAP_ERROR_FETCH_MESSAGE: 291 | msg = "Fetch message failed"; 292 | break; 293 | default: 294 | msg = "Unknown"; 295 | break; 296 | } 297 | } 298 | #else 299 | (void)code; 300 | #endif 301 | return msg; 302 | } 303 | }; 304 | } 305 | #endif 306 | #endif -------------------------------------------------------------------------------- /src/smtp/SMTPResponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 Suwatchai K. 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | #ifndef SMTP_RESPONSE_H 8 | #define SMTP_RESPONSE_H 9 | #if defined(ENABLE_SMTP) 10 | #include 11 | #include "Common.h" 12 | #include "SMTPBase.h" 13 | #include "SMTPConnection.h" 14 | 15 | namespace ReadyMailSMTP 16 | { 17 | class SMTPResponse : public SMTPBase 18 | { 19 | friend class SMTPConnection; 20 | friend class SMTPSend; 21 | 22 | private: 23 | bool auth_caps[smtp_auth_cap_max_type]; 24 | bool feature_caps[smtp_send_cap_max_type]; 25 | String response; 26 | ReadyTimer resp_timer; 27 | bool complete = false; 28 | 29 | void begin(smtp_context *smtp_ctx) 30 | { 31 | beginBase(smtp_ctx); 32 | response.remove(0, response.length()); 33 | complete = false; 34 | resp_timer.feed(smtp_ctx->options.timeout.read / 1000); 35 | } 36 | 37 | smtp_function_return_code handleResponse() 38 | { 39 | sys_yield(); 40 | if (smtp_ctx->options.accumulate || smtp_ctx->options.imap_mode || readTimeout() || !serverConnected()) 41 | { 42 | cCode() = smtp_ctx->options.accumulate || smtp_ctx->options.imap_mode ? function_return_success : function_return_failure; 43 | return cCode(); 44 | } 45 | 46 | if (cState() == smtp_state_send_data) 47 | { 48 | cCode() = function_return_success; 49 | return cCode(); 50 | } 51 | 52 | cCode() = function_return_undefined; 53 | bool ret = false; 54 | String line; 55 | line.reserve(1024); 56 | int readLen = readLine(line); 57 | if (readLen > 0) 58 | { 59 | response += line; 60 | getResponseStatus(line, smtp_ctx->server_status->state_info.status_code, *smtp_ctx->status); 61 | 62 | if (cState() == smtp_state_connect_command || cState() == smtp_state_send_command) 63 | { 64 | if (smtp_ctx->cmd_ctx.cb) 65 | smtp_ctx->cmd_ctx.resp.text = line.substring(0, line.length() - 2); 66 | else 67 | smtp_ctx->cmd_ctx.resp.text += line; 68 | 69 | if (smtp_ctx->cmd_ctx.cb) 70 | { 71 | if ((smtp_ctx->cmd_ctx.cmd.startsWith("EHLO") || smtp_ctx->cmd_ctx.cmd.startsWith("HELO")) && statusCode() == smtp_server_status_code_250) 72 | { 73 | smtp_ctx->server_status->server_greeting_ack = true; 74 | parseCaps(line); 75 | } 76 | else if (statusCode() == smtp_server_status_code_235) 77 | smtp_ctx->server_status->authenticated = true; 78 | else if (statusCode() == smtp_server_status_code_221) 79 | { 80 | smtp_ctx->server_status->authenticated = false; 81 | smtp_ctx->server_status->server_greeting_ack = false; 82 | } 83 | 84 | smtp_ctx->cmd_ctx.resp.command = cState() == smtp_state_connect_command ? "connect" : smtp_ctx->cmd_ctx.cmd; 85 | smtp_ctx->cmd_ctx.resp.statusCode = statusCode(); 86 | smtp_ctx->cmd_ctx.resp.errorCode = statusCode() >= 400 ? SMTP_ERROR_RESPONSE : 0; 87 | smtp_ctx->cmd_ctx.cb(smtp_ctx->cmd_ctx.resp); 88 | } 89 | 90 | if (statusCode() > 0) 91 | cCode() = statusCode() >= 400 ? function_return_failure : function_return_success; 92 | 93 | return cCode(); 94 | } 95 | 96 | if (cState() == smtp_state_greeting) 97 | parseCaps(line); 98 | 99 | if (statusCode() == 0 && cState() == smtp_state_initial_state) 100 | { 101 | cCode() = function_return_continue; 102 | return cCode(); 103 | } 104 | 105 | #if defined(ENABLE_CORE_DEBUG) 106 | if (statusCode() < 500 && cState() != smtp_state_send_command) 107 | setDebug(line, true, "[receive]"); 108 | #endif 109 | // positive completion 110 | if (statusCode() >= 200 && statusCode() < 300) 111 | setReturn(true, complete, ret); 112 | // positive intermediate 113 | else if (statusCode() >= 300 && statusCode() < 400) 114 | { 115 | if (statusCode() == smtp_server_status_code_334) 116 | { 117 | // base64 encoded server challenge message 118 | char *decoded = rd_b64_dec(rd_cast(smtp_ctx->status->text.c_str())); 119 | if (decoded) 120 | { 121 | if (cState() == smtp_state_auth_xoauth2 && indexOf(decoded, "{\"status\":") > -1) 122 | { 123 | setReturn(false, complete, ret); 124 | setError(__func__, AUTH_ERROR_AUTHENTICATION, decoded); 125 | } 126 | else if ((cState() == smtp_state_auth_login && indexOf(decoded, "Username:") > -1) || 127 | (cState() == smtp_state_login_user && indexOf(decoded, "Password:") > -1)) 128 | setReturn(true, complete, ret); 129 | rd_free(&decoded); 130 | decoded = nullptr; 131 | } 132 | } 133 | else if (statusCode() == smtp_server_status_code_354) 134 | { 135 | setReturn(true, complete, ret); 136 | } 137 | } 138 | else if (statusCode() >= 400) 139 | { 140 | setReturn(false, complete, ret); 141 | statusCode() = 0; 142 | setError(__func__, SMTP_ERROR_RESPONSE, smtp_ctx->status->text); 143 | } 144 | resp_timer.feed(smtp_ctx->options.timeout.read / 1000); 145 | } 146 | 147 | if (readLen == 0 && cState() == smtp_state_send_body && statusCode() == smtp_server_status_code_0) 148 | setReturn(true, complete, ret); 149 | 150 | cCode() = !complete ? function_return_continue : (ret ? function_return_success : function_return_failure); 151 | 152 | return cCode(); 153 | } 154 | 155 | void setReturn(bool positive, bool &complete, bool &ret) 156 | { 157 | complete = true; 158 | ret = positive ? true : false; 159 | } 160 | 161 | bool readTimeout() 162 | { 163 | if (!resp_timer.isRunning()) 164 | resp_timer.feed(smtp_ctx->options.timeout.read / 1000); 165 | 166 | if (resp_timer.remaining() == 0) 167 | { 168 | resp_timer.feed(smtp_ctx->options.timeout.read / 1000); 169 | setError(__func__, TCP_CLIENT_ERROR_READ_DATA); 170 | return true; 171 | } 172 | return false; 173 | } 174 | 175 | int readLine(String &buf) 176 | { 177 | int p = 0; 178 | while (tcpAvailable()) 179 | { 180 | #if defined(ESP8266) 181 | sys_yield(); 182 | if (!smtp_ctx->client || !smtp_ctx->client->connected()) 183 | return -1; 184 | #endif 185 | int res = tcpRead(); 186 | if (res > -1) 187 | { 188 | buf += (char)res; 189 | p++; 190 | if (res == '\n') 191 | return p; 192 | } 193 | } 194 | return p; 195 | } 196 | 197 | int tcpAvailable() { return smtp_ctx->client ? smtp_ctx->client->available() : 0; } 198 | int tcpRead() { return smtp_ctx->client ? smtp_ctx->client->read() : -1; } 199 | void getResponseStatus(const String &resp, smtp_server_status_code statusCode, smtp_response_status_t &status) 200 | { 201 | if (statusCode > smtp_server_status_code_0) 202 | { 203 | int codeLength = 3; 204 | int textLength = resp.length() - codeLength - 1; 205 | 206 | if ((int)resp.length() > codeLength && (resp[codeLength] == ' ' || resp[codeLength] == '-') && textLength > 0) 207 | { 208 | int code = resp.substring(0, codeLength).toInt(); 209 | if (resp[codeLength] == ' ') 210 | status.statusCode = code; 211 | 212 | int i = codeLength + 1; 213 | 214 | // We will collect the status text starting from status code 334 and 4xx 215 | // The status text of 334 will be used for debugging display of the base64 server challenge 216 | if (code < smtp_server_status_code_500 && (code == smtp_server_status_code_334 || code >= smtp_server_status_code_421)) 217 | { 218 | // find the next sp 219 | while (i < (int)resp.length() && resp[i] != ' ') 220 | i++; 221 | 222 | // if sp found, set index to the next pos, otherwise set index to num length + 1 223 | i = (i < (int)resp.length() - 1) ? i + 1 : codeLength + 1; 224 | status.text += resp.substring(i); 225 | } 226 | else 227 | status.text += resp.substring(i); 228 | } 229 | } 230 | } 231 | 232 | void parseCaps(const String &line) 233 | { 234 | if (line.indexOf("AUTH ", 0) > -1) 235 | { 236 | for (int i = smtp_auth_cap_plain; i < smtp_auth_cap_max_type; i++) 237 | { 238 | if (line.indexOf(smtp_auth_cap_token[i].text, 0) > -1) 239 | auth_caps[i] = true; 240 | } 241 | } 242 | else if (line.indexOf(smtp_auth_cap_token[smtp_auth_cap_starttls].text, 0) > -1) 243 | { 244 | auth_caps[smtp_auth_cap_starttls] = true; 245 | return; 246 | } 247 | else 248 | { 249 | for (int i = smtp_send_cap_binary_mime; i < smtp_send_cap_max_type; i++) 250 | { 251 | if (strlen(smtp_send_cap_token[i].text) > 0 && line.indexOf(smtp_send_cap_token[i].text, 0) > -1) 252 | { 253 | feature_caps[i] = true; 254 | return; 255 | } 256 | } 257 | } 258 | } 259 | 260 | void stop(bool forceStop = false) 261 | { 262 | stopImpl(forceStop); 263 | clear(response); 264 | } 265 | 266 | public: 267 | SMTPResponse() 268 | { 269 | for (uint8_t i = 0; i < smtp_auth_cap_max_type; i++) 270 | auth_caps[i] = 0; 271 | for (uint8_t i = 0; i < smtp_send_cap_max_type; i++) 272 | feature_caps[i] = 0; 273 | } 274 | }; 275 | } 276 | #endif 277 | #endif --------------------------------------------------------------------------------