mail: Rückläufer aus dem Bounce-Postfach lesen (Roadmap 1b.6, Schritt 3)

webklex/laravel-imap als Transport, eigener DSN-Parser nach RFC 3464 auf
der rohen Nachricht: Final-Recipient, Action, Status, Diagnostic-Code und
unser X-Pk-Ref aus der Original-Nachricht. Maßgeblich ist Action —
Zustellbestätigungen sperren niemanden, Freitext-Rückläufer wandern nach
Unparsed vor einen Menschen, Abwesenheitsnotizen nach Ignored.
mail:fetch-bounces alle zehn Minuten; ohne Konto tut es nichts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-15 13:55:30 +00:00
parent 0a658bb34b
commit e80006589c
16 changed files with 1039 additions and 2 deletions

View file

@ -1055,3 +1055,36 @@ Handliste in die zentrale Sperre kommt.
**Offen:** Schritt 3 (IMAP-Abholung, `webklex/laravel-imap`, freigegeben
15.09.2026) und Schritt 4 (Markierung im Admin, Claim-Seite, Freigabe).
**Schritt 3 — Abholung aus dem Postfach** (15.09.2026)
- Abhängigkeit `webklex/laravel-imap` ^6.2 (freigegeben 15.09.2026). Bringt
ein eigenes IMAP-Protokoll mit — keine PHP-Extension nötig.
- `DsnParser`: eigener Leser für RFC-3464-Zustellberichte auf der rohen
Nachricht. Zerlegt MIME (auch verschachtelt), nimmt aus dem
`message/delivery-status`-Teil je Empfänger `Final-Recipient`, `Action`,
`Status`, `Diagnostic-Code`, aus dem Original-Teil unseren `X-Pk-Ref`.
Maßgeblich ist `Action`: `failed` → hart (bei 4.x.x weich), `delayed` →
weich, `delivered`/`relayed` → ignoriert. Kein `message/delivery-status`
→ null, kein Raten.
- `BounceMailProcessor`: entscheidet je Mail den Zielordner — `Processed`,
`Ignored` (Zustellbestätigung, Abwesenheitsnotiz per `Auto-Submitted`/
Betreff) oder `Unparsed` (Freitext-Rückläufer; Eintrag im Aktivitätslog,
damit ein Mensch ihn ansieht). Testbar mit .eml-Fixtures in
`tests/Fixtures/bounces/`.
- `mail:fetch-bounces [--account=bounces] [--limit=200] [--dry-run]`: alle
zehn Minuten im Scheduler, liest UNSEEN aus INBOX, verschiebt jede Mail in
ihren Ordner (legt ihn an, falls er fehlt). Verschieben statt Flag: Bricht
der Lauf ab, liest der nächste dieselbe Mail erneut — dank
Message-ID-Unique ohne Doppelzählung. Ohne konfiguriertes Konto beendet
sich der Befehl sofort; Verbindungsfehler lösen einen Betriebs-Alert aus.
- `config/imap.php` (Paketvorlage, Konto `bounces` aus `BOUNCE_IMAP_*`).
Tests: `tests/Feature/BounceMailProcessingTest.php` (6) mit fünf Fixtures
(Postfix-Hardbounce mit Fortsetzungszeilen und Kleinschreibung, delayed,
Zustellbestätigung, Freitext, Abwesenheit).
**Deploy Schritt 3:** `composer install`, `config:cache`, dann in der `.env`
`BOUNCE_IMAP_HOST=w01eb4f6.kasserver.com`, `BOUNCE_IMAP_USERNAME`/`PASSWORD`
des Postfachs bounce@pressekonto.com, `MAIL_RETURN_PATH=bounce@pressekonto.com`.
Erster Lauf von Hand: `php artisan mail:fetch-bounces --dry-run`.