Address replies at Reply-To, and stop leaking SMTP exceptions to the UI

FixEmailService
Shipped
August 5, 2026 at 5:10 PM UTC
Author
Kamo
Commit
b922a98

A quick reply to a KamoCRM notification failed with `{"error":"Invalid Addresses"}` rendered verbatim in the reply box. That string is Jakarta Mail's own wording from SMTPTransport.rcptTo; the pod log shows what it was hiding: 550 5.1.1 <NoReply@KamoCRM.com>: Recipient address rejected: User unknown in virtual mailbox table Two defects behind it. Replies were addressed to From unconditionally, and nothing in the read path even carried Reply-To — so replying to anything sent from the unrouted NoReply@{org.domain} that NoReplySenderService puts on every transactional email went to a mailbox that does not exist. Campaign sends were hit the same way despite setting a real Reply-To header (CampaignMessageComposer). EmailMessage now carries replyTo, read from the ENVELOPE the listing already fetches so a mailbox listing costs no extra round trip. It collapses to null when it merely matches From, because RFC 3501 §7.4.2 requires a server to mirror From into the envelope's reply-to when the header is absent — an equal value carries no information either way. And both send endpoints answered failures with `{"error": e.getMessage()}`, which is how a serialized Java exception reached a user's screen. SendFailureResponse maps a rejection to a 422 naming the refused addresses and the receiving server's own reason — it walks nextException, where Jakarta Mail keeps the SMTP detail, rather than the cause chain. Addresses that were merely never attempted are reported alongside the rejected one: the transaction aborts as a whole, so they did not receive the message either. Also corrects NoReplySenderServiceTest, which still asserted the lowercasing that d1e7191 deliberately replaced with case preservation, and had been failing on main since.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing