# 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](https://linkedin.com/in/martin-alexander-k) Dieses Repository ist von [`kriiv/inbound-email`](https://github.com/kriiv/inbound-email) geforkt und wird hier als eigenständige, gehärtete Weiterentwicklung gepflegt. Das Repository liegt unter: ```text https://git.hacker.schule/mherrlein/inbound-email ``` ## Funktionsweise Der Zustellweg lautet: ```text 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 ```bash 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: ```bash 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: ```bash 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 | | `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: ```bash 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: ```bash 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: ```bash 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](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. - `.env` produktiv restriktiv schützen, z. B. Besitzer `root`, Dienstgruppe als Gruppe und Modus `640`. - TLS-Schlüssel außerhalb des Repositories aufbewahren. - SMTP AUTH ist deaktiviert. Bei leerer `SMTP_ALLOWED_IPS` ist 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: ```bash 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: ```bash npm ci ``` Syntaxchecks: ```bash 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`](https://github.com/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](AGENTS.md). ## Lizenz Dieses Projekt steht unter der MIT-Lizenz. Siehe [LICENSE](LICENSE).