ESP32 E-Mail senden (SMTP): Alarm-Mails, Tagesbericht & CSV-Anhang direkt vom Mikrocontroller
Push-Nachrichten brauchen einen Dienst, Telegram braucht einen Bot, eine eigene App braucht Entwicklungszeit. E-Mail dagegen hat jeder, funktioniert auf jedem Gerät, lässt sich filtern, archivieren und weiterleiten – und der ESP32 kann sie ganz allein verschicken. Kein Cloud-Zwischendienst, kein Abo, kein Server im Keller. In diesem Artikel richtest du SMTP mit App-Passwort ein, baust eine Alarmmail mit Cooldown, verschickst einen HTML-Tagesbericht mit Min-/Max-Werten und hängst am Ende ein CSV-Log von der SD-Karte an.
Warum überhaupt E-Mail?
Es gibt viele Wege, eine Nachricht vom Mikrocontroller aufs Handy zu bekommen. Jeder hat seine Berechtigung – E-Mail hat allerdings drei Eigenschaften, die kein anderer Weg in dieser Kombination bietet:
| Weg | Braucht | Anhänge | Archiv & Suche | Gut für |
|---|---|---|---|---|
| Telegram-Bot | Bot-Token, Telegram-Konto | ja | begrenzt | Sofortmeldungen, Rückkanal |
| MQTT | Broker | nein | nur mit Datenbank | Dauerdaten ins Smart Home |
| Eigener Webserver | Gerät im Netz | ja | nein | Live-Ansicht |
| E-Mail (SMTP) | ein Postfach | ja | ja, unbegrenzt | Alarme, Berichte, Logs |
💡 Der größte Vorteil ist die Ablage. Eine Alarmmail von vor drei Monaten findest du in zehn Sekunden über die Volltextsuche im Postfach. Eine Telegram-Nachricht von damals suchst du deutlich länger – und ein MQTT-Topic ohne Datenbank ist schlicht weg. Wenn du Ereignisse nicht nur melden, sondern auch nachvollziehen willst, ist E-Mail die einfachste Datenbank, die du bekommen kannst.
1Das Postfach vorbereiten – App-Passwort statt Kennwort
Kein Anbieter lässt dich heute noch mit deinem normalen Kontopasswort per SMTP anmelden, wenn Zwei-Faktor-Authentifizierung aktiv ist. Stattdessen erzeugst du ein App-Passwort: ein zufälliges, langes Kennwort, das nur für dieses eine Gerät gilt und das du jederzeit einzeln widerrufen kannst.
| Anbieter | SMTP-Server | Port (SSL) | Port (STARTTLS) | Zugangsdaten |
|---|---|---|---|---|
| Gmail | smtp.gmail.com | 465 | 587 | App-Passwort nötig |
| GMX | mail.gmx.net | 465 | 587 | Versand über externe Programme freischalten |
| Web.de | smtp.web.de | 465 | 587 | Versand über externe Programme freischalten |
| Outlook / Microsoft 365 | smtp.office365.com | — | 587 | je nach Konto eingeschränkt |
| Eigene Domain (Hoster) | meist smtp.deinedomain.de | 465 | 587 | normales Postfachpasswort |
⚠️ Leg ein eigenes Postfach nur für die Basteltechnik an. Das Passwort steht im Klartext im Sketch und damit im Flash des Chips. Wer den ESP32 in die Hand bekommt, kann ihn auslesen. Ein separates Freemail-Konto, das nur senden darf und in dem sonst nichts liegt, kostet nichts und begrenzt den Schaden auf null.
⚠️ Niemals ein Projekt mit Zugangsdaten auf GitHub hochladen. Der Klassiker: Sketch committen, Passwort vergessen zu entfernen, und automatische Scanner finden es innerhalb von Minuten. Leg die Daten in eine secrets.h, die du in .gitignore einträgst.
So sieht diese Datei aus – sie liegt neben deinem .ino-File im selben Ordner:
// ===== secrets.h =====
#pragma once
#define WLAN_SSID "MeinWLAN"
#define WLAN_PASSWORT "geheim123"
#define SMTP_HOST "smtp.gmail.com"
#define SMTP_PORT 465
#define SMTP_USER "meine.basteltechnik@gmail.com"
#define SMTP_PASS "abcd efgh ijkl mnop" // App-Passwort, nicht das Kontopasswort
#define MAIL_VON_NAME "ESP32 Keller"
#define MAIL_AN "ich@example.de"
#define MAIL_AN_NAME "Alex"
Das brauchst du
| Modul | Pin am Modul | ESP32 | Hinweis |
|---|---|---|---|
| HC-SR501 PIR | OUT | GPIO27 | gibt 3,3 V aus, direkt anschließbar |
| HC-SR501 PIR | VCC / GND | 5V / GND | braucht 5 V, nicht 3,3 V |
| DS18B20 | DATA | GPIO4 | 4,7 kΩ Pullup nach 3V3 |
| BMP280 | SDA / SCL | GPIO21 / GPIO22 | I2C-Adresse 0x76 oder 0x77 |
| SD-Modul | CS / MOSI / MISO / SCK | GPIO5 / 23 / 19 / 18 | VSPI, 5 V versorgen |
| Relais KY-019 | IN | GPIO26 | Spule an 5 V |
2Die erste Mail – minimaler Sketch
Installiere über den Bibliotheksverwalter der Arduino IDE die Bibliothek ESP Mail Client von Mobizt. Sie bringt SMTP und IMAP mit, kümmert sich um TLS und um die korrekte MIME-Kodierung – also genau um die Teile, die man von Hand nicht schreiben will.
#include <WiFi.h>
#include <ESP_Mail_Client.h>
#include "secrets.h"
SMTPSession smtp;
void smtpStatus(SMTP_Status status) {
Serial.println(status.info());
if (status.success()) {
Serial.printf("Gesendet: %d, fehlgeschlagen: %d\n",
status.completedCount(), status.failedCount());
}
}
bool mailSenden(const char *betreff, const String &text) {
SMTP_Message message;
message.sender.name = MAIL_VON_NAME;
message.sender.email = SMTP_USER;
message.subject = betreff;
message.addRecipient(MAIL_AN_NAME, MAIL_AN);
message.text.content = text.c_str();
message.text.charSet = "utf-8";
message.text.transfer_encoding = Content_Transfer_Encoding::enc_8bit;
Session_Config config;
config.server.host_name = SMTP_HOST;
config.server.port = SMTP_PORT;
config.login.email = SMTP_USER;
config.login.password = SMTP_PASS;
config.login.user_domain = "";
// Zeit wird fuer die Zertifikatspruefung gebraucht
config.time.ntp_server = "pool.ntp.org,time.nist.gov";
config.time.gmt_offset = 1;
config.time.day_light_offset = 1;
if (!smtp.connect(&config)) {
Serial.printf("Verbindung fehlgeschlagen: %s\n", smtp.errorReason().c_str());
return false;
}
bool ok = MailClient.sendMail(&smtp, &message);
if (!ok) Serial.printf("Versand fehlgeschlagen: %s\n", smtp.errorReason().c_str());
smtp.closeSession();
return ok;
}
void setup() {
Serial.begin(115200);
delay(300);
WiFi.mode(WIFI_STA);
WiFi.begin(WLAN_SSID, WLAN_PASSWORT);
Serial.print("Verbinde");
while (WiFi.status() != WL_CONNECTED) { delay(300); Serial.print('.'); }
Serial.printf("\nIP: %s\n", WiFi.localIP().toString().c_str());
MailClient.networkReconnect(true);
smtp.debug(1); // 0 = still, 1 = Basis, 2 = ausfuehrlich
smtp.callback(smtpStatus);
String body = "Hallo!\n\n";
body += "Diese Mail kommt direkt von einem ESP32.\n";
body += "IP-Adresse: " + WiFi.localIP().toString() + "\n";
body += "Signal: " + String(WiFi.RSSI()) + " dBm\n";
body += "Freier Heap: " + String(ESP.getFreeHeap() / 1024) + " kB\n";
mailSenden("ESP32 ist online", body);
}
void loop() { }
ℹ️ Warum die Uhrzeit wichtig ist: TLS-Zertifikate haben ein Gültigkeitsdatum. Ohne korrekte Systemzeit hält der ESP32 jedes Zertifikat für ungültig und bricht ab. Die Bibliothek holt die Zeit deshalb selbst per NTP – dafür sind die config.time-Zeilen da. Wenn du die Zeit ohnehin schon setzt, siehe ESP32 Uhrzeit per NTP holen, kannst du sie weglassen.
3Alarmmail mit Cooldown – damit dein Postfach überlebt
Ein PIR-Sensor löst bei jeder Bewegung aus – eine Katze, die durchs Zimmer läuft, erzeugt in einer Minute leicht fünfzehn Auslöser. Ohne Sperrzeit bekommst du fünfzehn Mails. Genauso wichtig: Der Versand darf das Hauptprogramm nicht für Sekunden blockieren.
#include <WiFi.h>
#include <ESP_Mail_Client.h>
#include "secrets.h"
#define PIR_PIN 27
#define LED_PIN 2
#define COOLDOWN_MS (5UL * 60UL * 1000UL) // 5 Minuten Sperre
SMTPSession smtp;
volatile bool bewegung = false;
uint32_t letzteMail = 0;
uint32_t unterdrueckt = 0;
void IRAM_ATTR pirIsr() { bewegung = true; }
bool mailSenden(const String &betreff, const String &html) {
SMTP_Message message;
message.sender.name = MAIL_VON_NAME;
message.sender.email = SMTP_USER;
message.subject = betreff.c_str();
message.addRecipient(MAIL_AN_NAME, MAIL_AN);
message.html.content = html.c_str();
message.html.charSet = "utf-8";
message.html.transfer_encoding = Content_Transfer_Encoding::enc_qp;
message.priority = esp_mail_smtp_priority::esp_mail_smtp_priority_high;
Session_Config config;
config.server.host_name = SMTP_HOST;
config.server.port = SMTP_PORT;
config.login.email = SMTP_USER;
config.login.password = SMTP_PASS;
config.time.ntp_server = "pool.ntp.org";
config.time.gmt_offset = 1;
config.time.day_light_offset = 1;
if (!smtp.connect(&config)) return false;
bool ok = MailClient.sendMail(&smtp, &message);
smtp.closeSession();
return ok;
}
String zeitstempel() {
struct tm t;
if (!getLocalTime(&t)) return String("unbekannt");
char buf[32];
strftime(buf, sizeof(buf), "%d.%m.%Y %H:%M:%S", &t);
return String(buf);
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
pinMode(PIR_PIN, INPUT);
attachInterrupt(digitalPinToInterrupt(PIR_PIN), pirIsr, RISING);
WiFi.mode(WIFI_STA);
WiFi.begin(WLAN_SSID, WLAN_PASSWORT);
while (WiFi.status() != WL_CONNECTED) delay(300);
configTime(3600, 3600, "pool.ntp.org");
MailClient.networkReconnect(true);
smtp.debug(0);
// Der PIR braucht nach dem Einschalten bis zu 60 s zum Einpegeln
Serial.println("PIR kalibriert sich...");
delay(60000);
bewegung = false;
Serial.println("Scharf.");
}
void loop() {
if (!bewegung) return;
bewegung = false;
uint32_t jetzt = millis();
if (letzteMail != 0 && jetzt - letzteMail < COOLDOWN_MS) {
unterdrueckt++; // In der Sperrzeit nur mitzaehlen
Serial.printf("Bewegung (unterdrueckt, %lu)\n", unterdrueckt);
return;
}
digitalWrite(LED_PIN, HIGH);
String html;
html = "<div style='font-family:sans-serif'>";
html += "<h2 style='color:#c0392b;margin:0 0 8px'>Bewegung erkannt</h2>";
html += "<p><b>Zeitpunkt:</b> " + zeitstempel() + "</p>";
html += "<p><b>Standort:</b> Keller</p>";
html += "<p><b>Signal:</b> " + String(WiFi.RSSI()) + " dBm</p>";
if (unterdrueckt > 0)
html += "<p>Seit der letzten Mail wurden " + String(unterdrueckt) +
" weitere Ausloeser unterdrueckt.</p>";
html += "<p style='color:#888;font-size:12px'>Naechste Meldung fruehestens in 5 Minuten.</p>";
html += "</div>";
if (mailSenden("[ALARM] Bewegung im Keller", html)) {
letzteMail = jetzt;
unterdrueckt = 0;
Serial.println("Alarmmail raus.");
} else {
Serial.println("Versand fehlgeschlagen - versuche es beim naechsten Ausloeser erneut.");
}
digitalWrite(LED_PIN, LOW);
}
💡 Der Zähler unterdrueckt macht aus der Sperrzeit einen Vorteil. Statt „irgendwann war mal Bewegung" steht in der Mail „seit der letzten Meldung gab es 23 weitere Auslöser" – das ist der Unterschied zwischen einer streunenden Katze und jemandem, der sich länger im Raum aufhält. Kostet zwei Zeilen und liefert echte Information.
⚠️ Der Versand blockiert 3 bis 8 Sekunden. TLS-Handshake, Login, Übertragung – das dauert. Solange läuft dein loop() nicht weiter. Für einen Bewegungsmelder ist das egal. Wenn nebenher etwas zeitkritisches laufen muss, schieb den Versand in einen eigenen FreeRTOS-Task auf Core 0, siehe ESP32 FreeRTOS Tutorial.
4Der tägliche Bericht – einmal um 08:00 Uhr
Der zweite typische Anwendungsfall: kein Alarm, sondern eine Zusammenfassung. Der ESP32 sammelt über den Tag Min- und Max-Werte und schickt morgens eine Übersicht – als HTML-Tabelle, damit sie im Postfach ordentlich aussieht.
#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_BMP280.h>
#include <ESP_Mail_Client.h>
#include "secrets.h"
SMTPSession smtp;
Adafruit_BMP280 bmp;
const uint8_t BERICHT_STUNDE = 8;
const uint8_t BERICHT_MINUTE = 0;
float tMin = 999, tMax = -999, tSumme = 0;
float pMin = 9999, pMax = 0;
uint32_t messungen = 0;
int letzterBerichtTag = -1;
uint32_t letzteMessung = 0;
String htmlZeile(const char *name, float wert, const char *einheit, const char *farbe) {
char buf[160];
snprintf(buf, sizeof(buf),
"<tr><td style='padding:6px 12px;border-bottom:1px solid #eee'>%s</td>"
"<td style='padding:6px 12px;border-bottom:1px solid #eee;"
"text-align:right;font-weight:bold;color:%s'>%.1f %s</td></tr>",
name, farbe, wert, einheit);
return String(buf);
}
bool berichtSenden() {
if (messungen == 0) return false;
struct tm t;
getLocalTime(&t);
char datum[16];
strftime(datum, sizeof(datum), "%d.%m.%Y", &t);
String html;
html = "<div style='font-family:sans-serif;max-width:520px'>";
html += "<h2 style='margin:0 0 4px'>Tagesbericht Gewaechshaus</h2>";
html += "<p style='color:#666;margin:0 0 16px'>Stand " + String(datum) + "</p>";
html += "<table style='border-collapse:collapse;width:100%'>";
html += htmlZeile("Temperatur Minimum", tMin, "C", "#2980b9");
html += htmlZeile("Temperatur Mittel", tSumme / messungen, "C", "#333333");
html += htmlZeile("Temperatur Maximum", tMax, "C", "#c0392b");
html += htmlZeile("Luftdruck Minimum", pMin, "hPa", "#333333");
html += htmlZeile("Luftdruck Maximum", pMax, "hPa", "#333333");
html += "</table>";
html += "<p style='color:#888;font-size:12px;margin-top:16px'>";
html += String(messungen) + " Messungen, Laufzeit " + String(millis() / 3600000UL) + " h, ";
html += "Signal " + String(WiFi.RSSI()) + " dBm</p></div>";
SMTP_Message message;
message.sender.name = MAIL_VON_NAME;
message.sender.email = SMTP_USER;
message.subject = (String("Tagesbericht ") + datum).c_str();
message.addRecipient(MAIL_AN_NAME, MAIL_AN);
message.html.content = html.c_str();
message.html.charSet = "utf-8";
message.html.transfer_encoding = Content_Transfer_Encoding::enc_qp;
Session_Config config;
config.server.host_name = SMTP_HOST;
config.server.port = SMTP_PORT;
config.login.email = SMTP_USER;
config.login.password = SMTP_PASS;
config.time.ntp_server = "pool.ntp.org";
config.time.gmt_offset = 1;
config.time.day_light_offset = 1;
if (!smtp.connect(&config)) return false;
bool ok = MailClient.sendMail(&smtp, &message);
smtp.closeSession();
return ok;
}
void werteZuruecksetzen() {
tMin = 999; tMax = -999; tSumme = 0;
pMin = 9999; pMax = 0;
messungen = 0;
}
void setup() {
Serial.begin(115200);
Wire.begin();
if (!bmp.begin(0x76) && !bmp.begin(0x77))
Serial.println("BMP280 nicht gefunden");
WiFi.mode(WIFI_STA);
WiFi.begin(WLAN_SSID, WLAN_PASSWORT);
while (WiFi.status() != WL_CONNECTED) delay(300);
configTime(3600, 3600, "pool.ntp.org");
MailClient.networkReconnect(true);
smtp.debug(0);
werteZuruecksetzen();
}
void loop() {
// --- Alle 60 Sekunden messen ---
if (millis() - letzteMessung >= 60000UL) {
letzteMessung = millis();
float t = bmp.readTemperature();
float p = bmp.readPressure() / 100.0f;
if (t > -50 && t < 100) {
if (t < tMin) tMin = t;
if (t > tMax) tMax = t;
tSumme += t;
if (p < pMin) pMin = p;
if (p > pMax) pMax = p;
messungen++;
}
}
// --- Einmal taeglich zur festgelegten Uhrzeit senden ---
struct tm t;
if (getLocalTime(&t)) {
if (t.tm_hour == BERICHT_STUNDE &&
t.tm_min == BERICHT_MINUTE &&
t.tm_mday != letzterBerichtTag) {
if (berichtSenden()) {
letzterBerichtTag = t.tm_mday; // verhindert mehrfachen Versand
werteZuruecksetzen();
Serial.println("Tagesbericht versendet.");
}
}
}
delay(500);
}
ℹ️ Warum letzterBerichtTag gebraucht wird: Die Bedingung tm_min == 0 ist eine ganze Minute lang wahr. Ohne die Tagesmerkung würde der ESP32 in dieser Minute so viele Mails schicken, wie loop() Durchläufe schafft. Der Tagesvergleich sorgt dafür, dass es bei einer bleibt – und funktioniert auch dann noch, wenn der Versand mal zwei Minuten braucht.
5CSV-Log als Anhang mitschicken
Die Kür: Der ESP32 schreibt seine Messwerte laufend auf eine Micro-SD-Karte und hängt die Datei einmal am Tag an die Berichtsmail. Damit hast du deine Rohdaten im Postfach und kannst sie in Excel oder Python direkt weiterverarbeiten.
#include <WiFi.h>
#include <SPI.h>
#include <SD.h>
#include <ESP_Mail_Client.h>
#include "secrets.h"
#define SD_CS 5
SMTPSession smtp;
const char *LOG_DATEI = "/messwerte.csv";
void logZeileSchreiben(float temperatur, float feuchte) {
struct tm t;
if (!getLocalTime(&t)) return;
File f = SD.open(LOG_DATEI, FILE_APPEND);
if (!f) { Serial.println("SD: Datei nicht offen"); return; }
char zeile[64];
snprintf(zeile, sizeof(zeile), "%04d-%02d-%02d %02d:%02d;%.2f;%.1f",
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
t.tm_hour, t.tm_min, temperatur, feuchte);
f.println(zeile);
f.close();
}
bool berichtMitAnhang() {
SMTP_Message message;
message.sender.name = MAIL_VON_NAME;
message.sender.email = SMTP_USER;
message.subject = "Tagesbericht mit Messdaten";
message.addRecipient(MAIL_AN_NAME, MAIL_AN);
message.html.content =
"<div style='font-family:sans-serif'>"
"<h2>Messdaten im Anhang</h2>"
"<p>Format: Datum;Temperatur;Feuchte – Trennzeichen Semikolon, "
"laesst sich in Excel direkt oeffnen.</p></div>";
message.html.charSet = "utf-8";
message.html.transfer_encoding = Content_Transfer_Encoding::enc_qp;
// --- Anhang von der SD-Karte ---
SMTP_Attachment att;
att.descr.filename = "messwerte.csv";
att.descr.mime = "text/csv";
att.file.path = LOG_DATEI;
att.file.storage_type = esp_mail_file_storage_type_sd;
att.descr.transfer_encoding = Content_Transfer_Encoding::enc_base64;
message.addAttachment(att);
Session_Config config;
config.server.host_name = SMTP_HOST;
config.server.port = SMTP_PORT;
config.login.email = SMTP_USER;
config.login.password = SMTP_PASS;
config.time.ntp_server = "pool.ntp.org";
config.time.gmt_offset = 1;
config.time.day_light_offset = 1;
if (!smtp.connect(&config)) {
Serial.printf("Verbindung: %s\n", smtp.errorReason().c_str());
return false;
}
bool ok = MailClient.sendMail(&smtp, &message);
if (!ok) Serial.printf("Versand: %s\n", smtp.errorReason().c_str());
smtp.closeSession();
// Nach erfolgreichem Versand neu anfangen
if (ok) {
SD.remove(LOG_DATEI);
File f = SD.open(LOG_DATEI, FILE_WRITE);
if (f) { f.println("Zeitpunkt;Temperatur_C;Feuchte_Prozent"); f.close(); }
}
return ok;
}
void setup() {
Serial.begin(115200);
if (!SD.begin(SD_CS)) {
Serial.println("SD-Karte nicht gefunden");
while (true) delay(1000);
}
if (!SD.exists(LOG_DATEI)) {
File f = SD.open(LOG_DATEI, FILE_WRITE);
if (f) { f.println("Zeitpunkt;Temperatur_C;Feuchte_Prozent"); f.close(); }
}
WiFi.mode(WIFI_STA);
WiFi.begin(WLAN_SSID, WLAN_PASSWORT);
while (WiFi.status() != WL_CONNECTED) delay(300);
configTime(3600, 3600, "pool.ntp.org");
MailClient.networkReconnect(true);
smtp.debug(1);
// Beispiel: ein paar Werte loggen und sofort senden
logZeileSchreiben(21.4, 58.2);
logZeileSchreiben(21.6, 57.9);
berichtMitAnhang();
}
void loop() { }
⚠️ Anhänge werden Base64-kodiert und dadurch rund ein Drittel größer. Eine 300-kB-CSV wird zu 400 kB im Mailstrom. Der ESP32 streamt das zwar häppchenweise von der Karte, aber der TLS-Puffer braucht trotzdem Heap. Bleib bei Anhängen unter etwa 1 MB – darüber steigt die Ausfallrate spürbar. Bei täglichem Versand und einer Messung pro Minute kommst du auf rund 40 kB pro Tag, das ist völlig unkritisch.
Troubleshooting
⚠️ SMTP: authentication failed oder 535 Username and Password not accepted: Du benutzt dein Kontopasswort statt eines App-Passworts, oder der Anbieter erlaubt den externen Versand noch nicht. Bei GMX und Web.de musst du „Zugriff über externe Programme" im Webinterface aktiv freischalten.
⚠️ connection refused oder Timeout beim Verbinden: Falscher Port. 465 ist SSL von Anfang an, 587 ist STARTTLS – beide brauchen unterschiedliche Einstellungen. Fang mit 465 an, das ist am unkompliziertesten.
⚠️ certificate verify failed: Die Systemzeit stimmt nicht. Ohne gültige Zeit ist jedes Zertifikat abgelaufen oder noch nicht gültig. Setz configTime() vor dem ersten Versand und warte, bis getLocalTime() ein Jahr > 2020 liefert.
⚠️ Der ESP32 startet beim Senden neu (Heap-Panic): TLS braucht rund 40 bis 50 kB freien Heap. Wenn parallel ein Webserver, ein großer Display-Puffer und ein WebSocket laufen, wird es eng. Prüf mit ESP.getFreeHeap() vor dem Verbinden und gib vorher Ressourcen frei. Der ESP32-S3 mit PSRAM hat hier deutlich mehr Luft.
⚠️ Umlaute erscheinen als Fragezeichen: charSet auf "utf-8" setzen und als transfer_encoding bei Text enc_8bit, bei HTML enc_qp verwenden. Die Arduino IDE speichert Sketches standardmäßig als UTF-8, das passt.
⚠️ Die Mails landen im Spam: Absenderadresse und SMTP-Benutzer müssen identisch sein – eine fremde Absenderadresse ist das stärkste Spam-Signal überhaupt. Hilft das nicht, leg im Postfach eine Regel an, die Mails von dieser Adresse in einen Ordner sortiert und als „nicht Spam" markiert.
⚠️ Der ESP8266 kommt nicht mit: Auf einem D1 Mini funktioniert die Bibliothek grundsätzlich, aber der knappe Heap macht TLS zur Glücksache – besonders mit Anhängen. Für Mailversand ist der ESP32 die deutlich robustere Wahl.
Praxistipps
-
Betreff strukturieren. Ein Präfix wie
[ALARM],[BERICHT]oder[WARNUNG]macht Filterregeln im Postfach trivial – und du siehst schon in der Benachrichtigung, ob es eilt. -
Eine Startmail beim Booten schicken. Wenn dein Gerät nach einem Stromausfall neu startet, willst du das wissen. Zwei Zeilen in
setup(), und du erfährst von jedem unerwarteten Neustart. - Grenzwerte mit Hysterese versehen. Ein Temperaturalarm bei genau 25,0 °C schickt bei 24,9 / 25,1 / 24,9 drei Mails. Löse bei 25 °C aus und setz die Sperre erst bei 24 °C zurück.
- Nicht bei jedem Wert senden. E-Mail ist für Ereignisse und Zusammenfassungen da. Fortlaufende Messwerte gehören über MQTT ins Smart Home oder in ein Datenlog auf SD.
- Stabile Stromversorgung nicht unterschätzen. WLAN-Sendespitzen ziehen kurzzeitig über 300 mA. An einem schwachen USB-Netzteil bricht die Spannung ein und der Chip startet mitten im TLS-Handshake neu. Ein LM2596S und ein Stützkondensator lösen das zuverlässig.
- Bei Batteriebetrieb Deep Sleep kombinieren. Aufwachen, messen, bei Überschreitung eine Mail senden, wieder schlafen – siehe ESP32 Deep Sleep Tutorial. Rechne mit rund 8 bis 12 Sekunden Wachzeit pro Mail inklusive WLAN-Aufbau.
- Testadresse benutzen. Beim Entwickeln schickst du schnell dreistellige Mailmengen. Nimm dafür eine zweite Adresse – und denk daran, dass Freemail-Anbieter Sendelimits pro Tag haben.
🚀 Bauteile für dein Mail-Projekt
- ESP32 DEV KIT WROOM32 – genug Heap für TLS, die Basis für alles hier
- ESP32-S3 N16R8 – mit PSRAM, wenn nebenher noch viel läuft
- HC-SR501 PIR Bewegungsmelder – der Auslöser für die Alarmmail
- DS18B20 Temperatursensor – Grenzwertüberwachung an schwer zugänglichen Stellen
- BMP280 Sensor – Werte für den Tagesbericht
- Micro-SD-Kartenmodul – CSV-Log für den Anhang
- 1-Kanal Relais KY-019 – melden und gleich reagieren
- LM2596S Step-Down – stabile Versorgung für WLAN-Spitzen
- Breadboard + Dupont-Kabel Set – Aufbau ohne Löten
Fazit
E-Mail ist der unauffälligste und zugleich robusteste Meldeweg, den ein Mikrocontroller haben kann: kein Dienst dazwischen, kein Konto bei irgendwem, keine App, die irgendwann eingestellt wird. Ein Postfach, ein App-Passwort, rund fünfzig Zeilen Code – und dein Projekt kann sich melden, wann immer es etwas zu sagen hat.
Die drei Bausteine, die den Unterschied zwischen Spielerei und Dauerbetrieb ausmachen, sind alle in diesem Artikel: eine Sperrzeit, damit dein Postfach nicht überläuft, eine Tagesmerkung, damit der Bericht wirklich nur einmal kommt, und getrennte Zugangsdaten in einer secrets.h, damit dein Passwort nicht versehentlich öffentlich wird. Wenn du zusätzlich noch einen Rückkanal willst – also nicht nur melden, sondern auch schalten – dann kombinier das Ganze mit einem Telegram-Bot und du hast beide Richtungen abgedeckt.