Minimal SMTP client for ESP32 (Arduino core) with support for:
- plain text or HTML email bodies
- attachments from RAM
- attachments from filesystem (
SD,LittleFS, or anyfs::FScompatible filesystem) - streaming Base64 encoding to keep memory usage low
- SMTPS (port 465) as the default mode
- STARTTLS (port 587) as an optional mode using
ESP_SSLClient - optional server-certificate verification using a CA certificate
- optional
Date:header for better compatibility with SMTP servers expecting complete email headers
The goal of the library is to stay small, simple, and practical for Arduino projects that need to send emails without bringing in heavy dependencies.
- SMTP over a secure client
- secure email sending with TLS/SSL
- optional server-certificate verification with a CA certificate
- plain text or HTML body
- multiple attachments
- multi-recipient email : "recipient1@gmail.com;recipient2@outlook.com"
- attachments from RAM and/or filesystem in the same email
- streaming Base64 encoding
- support for:
- implicit TLS / SMTPS on port
465as the default mode - STARTTLS on port
587when enabled
- implicit TLS / SMTPS on port
- optional
Date:header for better compatibility with stricter SMTP servers - simple API:
begin()setSmtpServer()setSMTPtime()sendEmail()getLastError()
- optional debug logging via
SMTP_MINI_DEBUG
- ESP32 board with Arduino core
- Wi-Fi connection
- SMTP server supporting either:
- SMTPS on port
465 - STARTTLS on port
587
- SMTPS on port
- For Gmail, an app password is required
Important
- Default mode is SMTPS on port
465usingWiFiClientSecure.- STARTTLS on port
587is available only whenSMTP_USE_STARTTLSis enabled, usingESP_SSLClient.- Pass a CA certificate to
setSmtpServer()to verify the SMTP server certificate; without one, the connection is encrypted but the server certificate is not verified.- The library can optionally include the
Date:header to improve compatibility with stricter SMTP servers.- The destination email address is sent as a single
RCPT TOunlessMULTIRCPTis enabled.QUITis sent as a best-effort SMTP close command, but a successful message is already considered delivered once the server accepts it afterDATA.
Copy the library folder into your Arduino libraries/ directory, or add it as a local library in PlatformIO.
#include <WiFi.h>
#include <SD.h>
#include <SmtpMini.h>
//Single library instance: TCP/TLS clients are managed internally
SmtpMini mailer;
void setup() {
Serial.begin(115200);
WiFi.begin("YOUR_WIFI", "YOUR_WIFI_PASSWORD");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWi-Fi connected");
if (!SD.begin()) {
Serial.println("SD init failed");
}
mailer.begin("sender@gmail.com", "your_app_password", &SD);
//smtp.gmail.com and port 465 are the defaults... optional
//mailer.setSmtpServer("smtp.gmail.com", 465);
SMTPAttachment attachments[1] = {
{ "/report.csv" }
};
bool ok = mailer.sendEmail(
"recipient@example.com",
"SmtpMini test",
"Hello! This is a test email.",
false,
attachments,
1
);
if (ok) {
Serial.println("Email sent successfully");
} else {
Serial.printf("SMTP error: %d\n", mailer.getLastError());
}
}
void loop() {}This example is only valid if
SMTP_USE_STARTTLSis enabled inSmtpMini.h.
#include <WiFi.h>
#include <SmtpMini.h>
SmtpMini mailer;
void setup() {
Serial.begin(115200);
WiFi.begin("YOUR_WIFI", "YOUR_WIFI_PASSWORD");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
mailer.begin("sender@gmail.com", "your_app_password");
//Set port 587 for automatic STARTTLS communication
mailer.setSmtpServer("smtp.gmail.com", 587);
bool ok = mailer.sendEmail(
"recipient@example.com",
"STARTTLS test",
"Hello from STARTTLS!",
false
);
if (ok) Serial.println("Email sent successfully");
else Serial.printf("SMTP error: %d\n", mailer.getLastError());
}
void loop() {}By default, the connection is encrypted but the server certificate is not verified. To verify the server, provide its trusted CA certificate in PEM format as the third argument to setSmtpServer(). This works with SMTPS and with STARTTLS when SMTP_USE_STARTTLS is enabled.
// Replace this placeholder with the complete CA certificate (PEM) that
// validates the certificate presented by your SMTP server.
static const char smtpRootCA[] = R"CERT(
-----BEGIN CERTIFICATE-----
... paste the CA certificate here ...
-----END CERTIFICATE-----
)CERT";
mailer.setSmtpServer("smtp.gmail.com", 465, smtpRootCA);Keep the certificate string available for the lifetime of the mailer; setSmtpServer() stores its pointer rather than copying the certificate. If the third argument is omitted or nullptr, the library uses setInsecure(), so TLS encryption remains enabled but the server identity is not verified. Use the CA certificate for your SMTP server, not a client certificate.
You can specify multiple recipients by separating them with a semicolon (;).
The library automatically parses the string, handles the individual routing commands to the SMTP server (RCPT TO), and generates a standards-compliant To: header.
To send the same email to multiple addresses, simply pass them as a single string, separated by semicolons:
bool ok = mailer.sendEmail(
"recipient1@example.com;recipient2@gmail.com;recipient3@outlook.com",
"Test with multiple recipients",
"Hello from SmtpMini!",
false
);You can optionally include a friendly name (alias) for the primary recipient, allowing a human-readable name to be displayed instead of just the email address.
bool ok = mailer.sendEmail(
"IT Manager;mario.rossi@gmail.com;recipient2@gmail.com;plain-email@example.com",
"Test with friendly names",
"Hello from SmtpMini with human-readable headers!",
false
);bool begin(const char* email, const char* appPassword, fs::FS* fileSystem = nullptr);Sets sender credentials and, optionally, the filesystem used for file attachments.
mailer.begin(mail, pass, &SD);
mailer.begin(mail, pass, &LittleFS);
mailer.begin(mail, pass); // RAM attachments onlyThe strings are not copied: they must stay valid for the whole program.
void setSmtpServer(const char* host, uint16_t port, const char* certCA = nullptr);Overrides the SMTP host and port. Pass the server's trusted CA certificate in PEM format as certCA to verify its certificate. The host and certificate strings are not copied; they must remain valid while the mailer uses them. If certCA is omitted or nullptr, certificate verification is disabled.
void setSMTPtime(const char* timestr);Sets the optional Date: header value used by the message builder.
Example:
configTime(3600, 3600, "pool.ntp.org");
struct tm t;
static char dateBuf[40];
if (getLocalTime(&t)) {
//static char dateBuf[40]; ..no!
strftime(dateBuf, sizeof(dateBuf), "%a, %d %b %Y %H:%M:%S %z", &t);
mailer.setSMTPtime(dateBuf);
}If setSMTPtime() is never called, no Date: header is sent.
bool sendEmail(const char* mailboxes, const char* subject, const char* body,
bool isHtml = false,
SMTPAttachment* attachments = nullptr,
size_t attachmentCount = 0);| Parameter | Description |
|---|---|
mailboxes |
Recipient (a single RCPT TO) |
subject |
Subject |
body |
Message body |
isHtml |
true = text/html, false = text/plain (UTF-8) |
attachments |
Array of SMTPAttachment (or nullptr) |
attachmentCount |
Number of elements in the array |
Returns true if the server accepted the message (250 after DATA). If an attachment could not be fully encoded, the message is still sent and getLastError() returns -4.
Input filtering: CR, LF and " are removed from mailboxes, the email address, subject and attachment names, and values are truncated (address 64, subject 80, attachment name 50 characters) to prevent header injection. The body is not filtered for these characters.
int getLastError() const;Returns the last error code. On success, it returns 0.
struct SMTPAttachment {
const char* nomeFile = nullptr; // name (RAM) or path (filesystem)
const void* bufferRAM = nullptr; // leave nullptr for filesystem files
size_t lunghezzaRAM = 0; // leave 0 for filesystem files
};SMTPAttachment a("/logs/data.csv"); // requires a filesystem in begin()const char text[] = "Generated in RAM";
SMTPAttachment b("log.txt", text, strlen(text)); // name shown in the email
String strtext = "stringa in formato testo\n";
strtext += "seconda riga della stringa";
SMTPAttachment c("testo.txt", strtext.c_str(), , strtext.length()); - If
bufferRAM != nullptrandlunghezzaRAM > 0→ sent from RAM. - Otherwise, if
nomeFileis not empty → read from the filesystem passed tobegin(). - The name shown in the email is the last path component (
/logs/data.csv→data.csv). nomeFileholds at most 63 characters.- If the file cannot be opened or is empty, it is skipped.
- All attachments are sent as
application/octet-stream. - RAM and filesystem attachments can be mixed in the same email.
getLastError() returns:
| Code | Meaning |
|---|---|
0 |
No error recorded / successful send |
-1 |
Timeout or connection closed while waiting for a server reply |
-2 |
TCP/TLS connection to the server failed |
-3 |
Body transmission failed |
-4 |
Message sent, but an attachment was incomplete or skipped (no name, read/send error) |
| other | SMTP code received instead of the expected one (for example 535, 554) |
Common SMTP codes:
| Code | Meaning |
|---|---|
220 |
Service ready / ready to start TLS |
221 |
Service closing transmission channel |
235 |
Authentication succeeded |
250 |
Command accepted |
334 |
Authentication challenge |
354 |
Start mail input |
421 |
Service not available, closing connection |
454 |
STARTTLS temporarily unavailable |
530 |
TLS activation or authentication required |
535 |
Authentication failed |
550 |
Mailbox unavailable / invalid recipient |
Note: the code is stored whenever the reply does not match the expected one, including server greeting, authentication,
MAIL FROM,RCPT TO,DATA,STARTTLS, and the final message acceptance afterDATA. EnableSMTP_MINI_DEBUGto inspect the server dialogue.
Common BearSSL codes:
| Code | BearSSL | Meaning |
|---|---|---|
0 |
BR_ERR_OK | No error (unexpected if the 'if' condition failed; check the timeout) |
26 |
BR_ERR_BAD_VERSION | The server requires a TLS version that is not supported (e.g., TLS 1.3) |
32 |
BR_ERR_BAD_CERT_SIGN | The certificate signature is invalid, or the CA chain is incorrect |
60 |
BR_ERR_X509_EXPIRED | The server certificate has expired (or the ESP's clock is not synchronized via NTP |
62 |
BR_ERR_X509_NOT_TRUSTED | The root CA certificate is not trusted or is missing from your code |
QUITis sent as a best-effort SMTP close command.- A message is considered successfully sent once the server accepts it after
DATAwith250.
Uncomment in SmtpMini.h:
#define SMTP_MINI_DEBUGWhen enabled, the library prints the SMTP dialogue (C: / S: lines), the authentication result and the attachment progress. Connection and SMTP error messages are printed on Serial regardless of this option.
TLS encrypts the connection. The library verifies the SMTP server certificate only when a CA certificate is supplied to setSmtpServer(); otherwise, it calls setInsecure(), so the server's identity is not verified. Certificate verification is available in both SMTPS and STARTTLS modes.
- Authentication:
AUTH LOGINonly - Single body part (text or HTML)
- ESP32 only
See examples/testsmail.ino: Wi-Fi, SD card, simple email and mixed SD/RAM attachments.
MIT