fix: SMTP-Fehler nur bei Empfaengerablehnung endgueltig
Bisher setzte jeder SMTP-5xx die Mail sofort auf FAILED, auch Anmelde- und Richtlinienfehler (EAUTH, EMESSAGE, 552). Ein kaputtes SMTP-Secret haette alle Buchungs- und Zahlungsmails verworfen, ohne Rueckweg (dedupeKey verhindert erneutes Einreihen). Endgueltig ist jetzt nur EENVELOPE mit 550, 551, 553, 556 oder 5xx mit Status 5.1.x. Alles andere bleibt PENDING und wird bis MAX_EMAIL_ATTEMPTS wiederholt. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
e6fa383934
commit
34ccbc3b9a
2 files changed
+48
-7
No files matched your search
@@ -13,8 +13,8 @@ vi.mock("nodemailer", async (importOriginal) => {
|
||||
return { ...actual, default: { ...actual.default, createTransport } };
|
||||
});
|
||||
|
||||
function smtpError(message: string, responseCode?: number) {
|
||||
return Object.assign(new Error(message), { code: "EENVELOPE", responseCode });
|
||||
function smtpError(message: string, responseCode?: number, code = "EENVELOPE") {
|
||||
return Object.assign(new Error(message), { code, responseCode });
|
||||
}
|
||||
|
||||
async function queueMail(to = "[email protected]") {
|
||||
@@ -79,4 +79,38 @@ describe("Postausgang: Fehler beim SMTP-Versand", () => {
|
||||
expect(await db.emailMessage.findUniqueOrThrow({ where: { id } })).toMatchObject({ status: "PENDING", attempts: 1 });
|
||||
}
|
||||
});
|
||||
|
||||
test("Server- und Absenderfehler (Anmeldung, Richtlinie) bleiben PENDING statt FAILED", async () => {
|
||||
const auth = await queueMail("[email protected]");
|
||||
const policy = await queueMail("[email protected]");
|
||||
sendMail
|
||||
.mockRejectedValueOnce(smtpError("535 5.7.8 Authentication failed", 535, "EAUTH"))
|
||||
.mockRejectedValueOnce(smtpError("554 5.7.1 Message rejected as spam", 554, "EMESSAGE"));
|
||||
vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
|
||||
expect(await flushEmails(db)).toEqual({ sent: 0, failed: 2 });
|
||||
for (const id of [auth.id, policy.id]) {
|
||||
expect(await db.emailMessage.findUniqueOrThrow({ where: { id } })).toMatchObject({ status: "PENDING", attempts: 1 });
|
||||
}
|
||||
});
|
||||
|
||||
test("EENVELOPE mit 550/551/553/556 oder 5.1.x ist endgueltig, 552 und 4xx nicht", async () => {
|
||||
const cases: [number, string, boolean][] = [
|
||||
[550, "550 5.1.1 User unknown", true],
|
||||
[551, "551 User not local", true],
|
||||
[553, "553 Mailbox name not allowed", true],
|
||||
[554, "554 5.1.2 Bad destination system address", true],
|
||||
[552, "552 Mailbox full", false],
|
||||
[451, "451 4.1.1 Try later", false],
|
||||
];
|
||||
vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
for (const [index, [code, response, permanent]] of cases.entries()) {
|
||||
await resetDb();
|
||||
const mail = await queueMail(`r${index}@example.org`);
|
||||
sendMail.mockRejectedValueOnce(Object.assign(smtpError(response, code), { response }));
|
||||
await flushEmails(db);
|
||||
const after = await db.emailMessage.findUniqueOrThrow({ where: { id: mail.id } });
|
||||
expect(after.status, response).toBe(permanent ? "FAILED" : "PENDING");
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user