mail: Bounce-Abruf liest RFC 6533, All-Inkl-Freitext und ganze INBOX
Befund aus 151 echten Rücklaeufern: 2 SmarterMail-DSNs (message/global-delivery-status) und 2 Freitext-Hinweise des eigenen Relays (X-KASMail-Error, formal auto-replied) fielen durch. Policy- und Quota-Codes zaehlen weich statt hart. Abruf ohne UNSEEN-Filter, weil das Verschieben die Zustandsmarke ist. Dazu Schutz in tests/TestCase.php: Suite bricht ab, wenn ein Config-Cache sie auf die Dev-Datenbank umlenkt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6e159ecf06
commit
4cefdf7f5c
9 changed files with 323 additions and 15 deletions
|
|
@ -77,6 +77,61 @@ test('free-text bounces are not guessed but parked for a human, auto-replies are
|
|||
->and(ActivityEvent::query()->where('type', ActivityType::MailSuppressed->value)->where('description', 'like', '%nicht lesbar%')->exists())->toBeTrue();
|
||||
});
|
||||
|
||||
test('the utf-8 dsn variant of rfc 6533 is read like a classic report', function () {
|
||||
/** @var TestCase $this */
|
||||
// SmarterMail liefert `message/global-delivery-status` und die
|
||||
// Original-Header als `message/global-headers` (Bestand 15.09.2026).
|
||||
$report = app(DsnParser::class)->parse(bounceFixture('global-delivery-status'));
|
||||
|
||||
expect($report)->not->toBeNull()
|
||||
->and($report->recipients)->toHaveCount(1)
|
||||
->and($report->recipients[0]['email'])->toBe('weiterleitung@example.net')
|
||||
->and($report->recipients[0]['status'])->toBe('5.0.0')
|
||||
->and($report->reference)->toBe('user:4711');
|
||||
|
||||
$result = app(BounceMailProcessor::class)->process(bounceFixture('global-delivery-status'));
|
||||
|
||||
expect($result['folder'])->toBe(BounceMailProcessor::Processed)
|
||||
->and(EmailBounce::query()->first()->kind)->toBe(EmailBounceKind::Hard);
|
||||
});
|
||||
|
||||
test('the free-text notice of our own relay is read and never mistaken for an auto-reply', function () {
|
||||
/** @var TestCase $this */
|
||||
// All-Inkl schickt „Auto-Submitted: auto-replied" mit — als Abwesenheit
|
||||
// ignoriert, wären das genau die Adressen, die nie gesperrt würden.
|
||||
expect(app(DsnParser::class)->looksLikeAutoReply(bounceFixture('kas-antispam')))->toBeFalse();
|
||||
|
||||
$result = app(BounceMailProcessor::class)->process(bounceFixture('kas-antispam'));
|
||||
$suppressions = app(EmailSuppressionService::class);
|
||||
|
||||
expect($result['folder'])->toBe(BounceMailProcessor::Processed)
|
||||
->and($result['bounces'])->toBe(2)
|
||||
->and($suppressions->isSuppressed('presse@example.net'))->toBeTrue()
|
||||
->and($suppressions->isSuppressed('info@example.org'))->toBeTrue();
|
||||
});
|
||||
|
||||
test('policy rejections and full mailboxes count soft, not hard', function () {
|
||||
/** @var TestCase $this */
|
||||
// 5.7.1 „Relay access denied": die Adresse existiert, ihr Server nimmt
|
||||
// nur nichts an — erst der Schwellwert sperrt.
|
||||
$result = app(BounceMailProcessor::class)->process(bounceFixture('policy-5.7.1'));
|
||||
|
||||
expect($result['folder'])->toBe(BounceMailProcessor::Processed)
|
||||
->and(EmailBounce::query()->first()->kind)->toBe(EmailBounceKind::Soft)
|
||||
->and(EmailBounce::query()->first()->reference)->toBe('pm:99:company:7')
|
||||
->and(app(EmailSuppressionService::class)->isSuppressed('pr@example.net'))->toBeFalse();
|
||||
|
||||
$quota = str_replace(
|
||||
['Status: 5.7.1', '554 5.7.1 <pr@example.net>: Relay access denied', '<20260915120000.ABC@'],
|
||||
['Status: 5.0.0', '552 Requested mail action aborted: exceeded storage allocation', '<20260915120001.ABC@'],
|
||||
bounceFixture('policy-5.7.1'),
|
||||
);
|
||||
app(BounceMailProcessor::class)->process($quota);
|
||||
|
||||
expect(EmailBounce::query()->count())->toBe(2)
|
||||
->and(EmailBounce::query()->where('kind', EmailBounceKind::Hard->value)->count())->toBe(0);
|
||||
});
|
||||
|
||||
test('the fetch command does nothing without a configured mailbox', function () {
|
||||
/** @var TestCase $this */
|
||||
config()->set('imap.accounts.bounces.host', null);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue