Files
inbound-email/readme.md
T
2026-09-02 13:04:29 +02:00

240 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).