Inbound Email (SMTP) to Webhook
Diese Version ist eine gehärtete Weiterentwicklung des ursprünglichen Projekts. Sie nimmt E-Mails über SMTP an, parst Inhalt und Header, lädt Anhänge optional nach Amazon S3 und sendet die Nachricht an einen HTTP(S)-Webhook.
Originalentwickler: Martin Krivosija – LinkedIn
Dieses Repository ist von kriiv/inbound-email
geforkt und wird hier als eigenständige, gehärtete Weiterentwicklung gepflegt.
Das Repository liegt unter:
https://git.hacker.schule/mherrlein/inbound-email
Funktionsweise
Der Zustellweg lautet:
SMTP -> mailparser -> optional S3 -> persistenter Spool -> Webhook
Eine Nachricht wird vor der SMTP-Bestätigung als JSON unter SPOOL_DIR/pending
gespeichert. Erst nach erfolgreicher Webhook-Antwort wird sie gelöscht. Dadurch
überlebt die Zustellung Neustarts und vorübergehende Webhook-Fehler.
Die Zustellung ist „at least once“. Wenn der Prozess nach erfolgreicher Webhook-Annahme, aber vor dem Löschen der Spool-Datei abstürzt, kann die Nachricht doppelt zugestellt werden. Der Webhook sollte deshalb idempotent sein.
Voraussetzungen
- Node.js 18 oder neuer und npm
- ein erreichbarer HTTP(S)-Webhook
- AWS-S3-Zugangsdaten und ein Bucket, wenn Anhänge gespeichert werden sollen
- bei Produktion ein eigener Dienstbenutzer, z. B.
smtpwebhook
Installation
git clone https://git.hacker.schule/mherrlein/inbound-email.git
cd inbound-email
npm ci
cp .env.example .env
${EDITOR:-vi} .env
chmod 640 .env
WEBHOOK_URL muss gesetzt werden. Für einen lokalen Test kann ein nicht
privilegierter Port verwendet werden:
PORT=25252 \
SPOOL_DIR=/tmp/smtpwebhook-spool \
LOG_DIR=/tmp/smtpwebhook-logs \
npm start
Produktive Spool- und Log-Verzeichnisse müssen dem Dienstbenutzer gehören und restriktiv geschützt sein:
install -d -o smtpwebhook -g smtpwebhook -m 700 \
/var/lib/smtpwebhook \
/var/lib/smtpwebhook/spool/pending \
/var/lib/smtpwebhook/spool/failed \
/var/lib/smtpwebhook/logs
Konfiguration
Die Anwendung liest .env aus dem Arbeitsverzeichnis. WEBHOOK_URL ist
zwingend erforderlich und hat keinen öffentlichen Defaultwert.
| Variable | Bedeutung | Standardwert |
|---|---|---|
WEBHOOK_URL |
Ziel-URL für die geparste Nachricht | erforderlich |
WEBHOOK_BEARER_TOKEN |
Optionaler Bearer Token für den Authorization-Header des Webhooks |
nicht gesetzt |
PORT |
SMTP-Port | 25 (Produktion: 2525) |
SMTP_SECURE |
TLS/SMTPS beim Verbindungsaufbau | false |
TLS_KEY_PATH |
TLS-Privatschlüssel bei aktiviertem TLS | keiner |
TLS_CERT_PATH |
TLS-Zertifikat bei aktiviertem TLS | keines |
SMTP_ALLOWED_IPS |
Komma-separierte Quell-IP-Allowlist | leer = alle |
MAX_MESSAGE_SIZE |
Maximale Nachrichtengröße in Bytes; 0 deaktiviert das Limit |
26214400 (25 MiB) |
MAX_FILE_SIZE |
Maximale Größe eines S3-Anhangs in Bytes | 5242880 (5 MiB) |
MAX_SMTP_CLIENTS |
Maximale parallele SMTP-Verbindungen | 50 |
SMTP_SOCKET_TIMEOUT |
SMTP-Socket-Timeout in Millisekunden | 300000 |
SMTP_CLOSE_TIMEOUT |
Timeout für den Verbindungsabbau in Millisekunden | 10000 |
WEBHOOK_CONCURRENCY |
Parallele Webhook-Jobs | 5 |
WEBHOOK_QUEUE_RETRIES |
Queue-Wiederholungen bei Fehlern | 10 |
WEBHOOK_QUEUE_RETRY_DELAY |
Wartezeit zwischen Queue-Wiederholungen in Millisekunden | 60000 |
WEBHOOK_QUEUE_TIMEOUT |
Maximale Dauer eines Queue-Jobs in Millisekunden | 35000 |
SPOOL_DIR |
Basisverzeichnis des persistenten Spools | /var/lib/smtpwebhook/spool |
LOG_DIR |
Verzeichnis der aktuellen Anwendungslogs | /var/lib/smtpwebhook/logs |
AWS_REGION |
AWS-Region | keiner |
AWS_ACCESS_KEY_ID |
AWS-Zugangsschlüssel | keiner |
AWS_SECRET_ACCESS_KEY |
AWS-Geheimschlüssel | keiner |
S3_BUCKET_NAME |
S3-Bucket für Anhänge | keiner |
Wenn MAX_FILE_SIZE=0 gesetzt ist, werden normale Anhänge nicht nach S3
hochgeladen. Für S3 müssen Bucket, Region und Zugangsdaten korrekt gesetzt sein.
Bei SMTP_SECURE=true müssen die TLS-Dateien für den Dienstbenutzer lesbar sein.
Start und Produktion
Für einen Vordergrundstart:
npm start
Die produktive Installation verwendet Supervisor. Der Prozess wird mit
/usr/bin/node /opt/inbound-email/server.js unter dem Benutzer smtpwebhook
gestartet. Die Konfiguration liegt außerhalb des Repositories unter
/etc/supervisor/conf.d/inbound-email.conf.
Nach Änderungen an der Supervisor-Konfiguration:
supervisorctl -c /etc/supervisor/supervisord.conf reread
supervisorctl -c /etc/supervisor/supervisord.conf update inbound-email
supervisorctl -c /etc/supervisor/supervisord.conf status inbound-email
Bei Änderungen an Code oder .env genügt normalerweise:
supervisorctl -c /etc/supervisor/supervisord.conf restart inbound-email
Falls Supervisor nicht verfügbar ist, kann die Anwendung alternativ direkt als
systemd-Unit smtpwebhook.service betrieben werden. Eine gehärtete Beispiel-
Unit mit EnvironmentFile, User=smtpwebhook, Restart=on-failure,
ProtectSystem=strict und persistentem Spool ist in AGENTS.md
dokumentiert. Supervisor und die systemd-App-Unit dürfen nicht gleichzeitig
dieselbe Instanz starten.
Spool und Zustellung
Die Verzeichnisse haben folgende Bedeutung:
| Pfad | Zweck |
|---|---|
SPOOL_DIR/pending |
Noch nicht erfolgreich zugestellte Nachrichten |
SPOOL_DIR/failed |
Ungültige Spool-Dateien zur manuellen Prüfung |
LOG_DIR |
Aktuelle JSON-Anwendungslogs |
Der Spool wird beim Start und etwa jede Minute erneut eingelesen. Bei einem
Webhook-Fehler bleibt die Nachricht in pending; sie wird nicht verworfen.
Unlesbare Spool-Dateien werden nach failed verschoben. Die JSON-Datei wird
erst nach erfolgreicher Webhook-Antwort gelöscht.
Die Zustellung ist „at least once“. Der Webhook muss doppelte Zustellungen vertragen können, insbesondere bei einem Absturz zwischen erfolgreicher Webhook-Annahme und dem Löschen der Spool-Datei.
Einsatzbeispiele
- E-Mail-to-Ticket-Systeme
- Dokumentenverarbeitung mit optionaler S3-Ablage
- Automatisierte Reports und Benachrichtigungen
- DMARC- oder andere maschinell erzeugte Reports
Sicherheit und bekannte Grenzen
.env, Zugangsdaten, private TLS-Schlüssel, Logs und Spool-Dateien niemals committen..envproduktiv restriktiv schützen, z. B. Besitzerroot, Dienstgruppe als Gruppe und Modus640.- TLS-Schlüssel außerhalb des Repositories aufbewahren.
- SMTP AUTH ist deaktiviert. Bei leerer
SMTP_ALLOWED_IPSist der Listener für alle erreichbaren IPs offen; Firewall oder Allowlist bewusst konfigurieren. - Keine zweite Prozessverwaltung wie PM2 parallel zu Supervisor/systemd einsetzen.
- Rate-Limiting ist derzeit nicht implementiert.
Logging und Fehleranalyse
server.js schreibt strukturierte JSON-Logs nach
/var/lib/smtpwebhook/logs/application-YYYY-MM-DD.log. Die Logs rotieren täglich
und ab 20 MiB; die Aufbewahrung beträgt ungefähr 90 Tage. Supervisor führt
zusätzlich /var/log/inbound-email.out.log und
/var/log/inbound-email.err.log.
Die wichtigsten Checks:
supervisorctl -c /etc/supervisor/supervisord.conf status inbound-email
ss -ltnp | grep ':2525'
ps -C node -o user,group,pid,ppid,stat,etime,args
find /var/lib/smtpwebhook/spool -maxdepth 2 -type f -ls
EADDRINUSE bedeutet normalerweise, dass eine zweite oder verwaiste Instanz
den Port belegt. Nicht einfach eine weitere Instanz starten, sondern zuerst den
Portbesitzer identifizieren. Die alten Logs unter /opt/inbound-email/logs
sind historische Daten und nicht der aktuelle Spool.
Entwicklung und Tests
Abhängigkeiten reproduzierbar installieren:
npm ci
Syntaxchecks:
node --check server.js
node --check config.js
git diff --check
Es gibt derzeit keine automatisierten Testfälle. Ein SMTP-EHLO/QUIT-Handshake gegen einen lokalen Testport prüft nur Listener und Protokollannahme; S3 und Webhook werden dadurch nicht getestet. Keine Testmail an ein produktives Webhook-Ziel senden.
Das vorhandene DockerFile klont noch das öffentliche GitHub-Original und
bildet die lokale gehärtete Installation nicht automatisch ab. Vor einem
produktiven Docker-Einsatz Clone-URL, Branch, Spool-Persistenz und Logging
anpassen bzw. prüfen.
Git und weitere Dokumentation
origin zeigt auf das private Repository
https://git.hacker.schule/mherrlein/inbound-email; upstream zeigt auf das
ursprüngliche GitHub-Projekt kriiv/inbound-email.
Keine History per Force-Push überschreiben.
Die detaillierte Referenz für Codex-Instanzen und die vollständige systemd- Beispiel-Unit steht in AGENTS.md.
Lizenz
Dieses Projekt steht unter der MIT-Lizenz. Siehe LICENSE.