diff --git a/src/lib/notifications/senders/__tests__/email.test.ts b/src/lib/notifications/senders/__tests__/email.test.ts index 83f12cec3..b785b1130 100644 --- a/src/lib/notifications/senders/__tests__/email.test.ts +++ b/src/lib/notifications/senders/__tests__/email.test.ts @@ -237,3 +237,43 @@ describe("sendViaEmail — the failure names itself for the test button", () => expect(result.smtpCode).toBeUndefined(); }); }); + +describe("sendViaEmail — transport and envelope faults are named too", () => { + it.each([ + ["a TLS handshake failure", "ETLS", undefined, "connection_failed"], + ["a protocol error", "EPROTOCOL", undefined, "connection_failed"], + [ + "an envelope refused without a code", + "EENVELOPE", + undefined, + "upstream_rejected", + ], + [ + "a message refused without a code", + "EMESSAGE", + undefined, + "upstream_rejected", + ], + [ + "an envelope refused with a temporary code", + "EENVELOPE", + 451, + "upstream_error", + ], + ])("%s", async (_label, code, responseCode, failureCode) => { + loadEmailConfigMock.mockReturnValue(transport); + sendMailMock.mockRejectedValue( + Object.assign(new Error(String(code)), { + code, + ...(responseCode !== undefined ? { responseCode } : {}), + }), + ); + + const result = await sendViaEmail( + { recipient: "you@example.com" }, + payload(), + ); + + expect(result.failureCode).toBe(failureCode); + }); +}); diff --git a/src/lib/notifications/senders/email.ts b/src/lib/notifications/senders/email.ts index 2b518638d..22011e45e 100644 --- a/src/lib/notifications/senders/email.ts +++ b/src/lib/notifications/senders/email.ts @@ -106,13 +106,20 @@ function classifySmtpError(err: unknown): { : code === "ECONNECTION" || code === "ESOCKET" || code === "EDNS" || - code === "ECONNREFUSED" + code === "ECONNREFUSED" || + // STARTTLS or TLS handshake failure (typically port 465 set up + // as 587 or the reverse) and a server that does not speak SMTP. + code === "ETLS" || + code === "EPROTOCOL" ? "connection_failed" : smtpCode !== undefined && smtpCode >= 500 ? "upstream_rejected" : smtpCode !== undefined && smtpCode >= 400 ? "upstream_error" - : undefined; + : // An envelope or message refused without a reply code. + code === "EENVELOPE" || code === "EMESSAGE" + ? "upstream_rejected" + : undefined; const extras = { ...(failureCode ? { failureCode } : {}), ...(smtpCode !== undefined ? { smtpCode } : {}),