- Shipped
- August 5, 2026 at 5:10 PM UTC
- Author
- kamo
- Commit
- 39082c4
Sending a quick reply to a KamoCRM notification put the literal text `{"error":"Invalid Addresses"}` in the reply box. The mail server had rejected NoReply@KamoCRM.com at RCPT TO with 550 5.1.1 (no such mailbox), and both send call sites rendered the server's response body verbatim. Replies now go where a reply belongs. replyToAddresses() prefers Reply-To over From per RFC 5322 §3.6.2 and backs all five reply paths — reading pane, thread view, conversation list, quick reply and reply-all. Until now every one of them addressed From unconditionally, which mailed campaign replies to the campaign's unrouted NoReply@ sender instead of the Reply-To it explicitly set. Where every reply target is send-only, the quick-reply composer is replaced by a notice naming the address, and Reply / Reply All (and the r / a shortcuts) are disabled. Leaving them live would only move the same 550 one screen further in. isNoReplyAddress is deliberately narrower than the existing isAutomatedSender: notifications@ and alerts@ are frequently monitored and stay repliable — that set exists to keep a human face off a robot's message and can afford to be liberal, this one decides whether anyone can answer at all. Nothing from the server is shown raw any more. parseSendError unwraps the structured {error, code, rejectedRecipients} body EmailService now returns, so the UI names the refused addresses in the reader's own language; an unrecognised body yields no message at all and the caller falls back to its own translated wording. The no-reply check reads the loaded message rather than the list envelope, since the search index has no Reply-To column — but only when it is that message, because handleMessageSelect leaves the previous body in place while the next one loads.