From 1034617b63308fe46e7bee7c19caf59419a9490d Mon Sep 17 00:00:00 2001 From: Abhinav Raut Date: Mon, 20 Jul 2026 23:04:41 +0530 Subject: [PATCH] point livechat verification email replies at a no-reply address Livechat customers verify by pasting the code into chat, not by replying to the email. If the notification sender happens to be the same mailbox as a polled inbox, a stray email reply would get ingested as a new conversation. Setting Reply-To to noreply@ keeps those replies out of any monitored inbox. The email conversation path is unchanged and stays replyable. --- internal/aiagent/tools.go | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/internal/aiagent/tools.go b/internal/aiagent/tools.go index 4300b56e..c5ca7f0e 100644 --- a/internal/aiagent/tools.go +++ b/internal/aiagent/tools.go @@ -260,12 +260,19 @@ func (t *sendEmailVerificationTool) Execute(ctx context.Context, args string) (s } sendErr = t.m.convo.SendTransientEmail(t.conv.InboxID, t.conv.ID, t.conv.UUID, []string{email}, subject, body) } else { - sendErr = t.m.notifier.Send(notifier.Message{ + msg := notifier.Message{ RecipientEmails: []string{email}, Subject: t.m.i18n.T("ai.agent.verificationEmailSubject"), Content: body, Provider: notifier.ProviderEmail, - }) + } + // Livechat verification happens in chat, not email. Point replies at a no-reply address so a + // stray email reply can't be ingested as a new conversation when the notification sender + // doubles as a polled inbox. + if noReply := t.m.noReplyAddress(); noReply != "" { + msg.Headers = map[string][]string{"Reply-To": {noReply}} + } + sendErr = t.m.notifier.Send(msg) } if sendErr != nil { t.m.lo.Error("error sending verification code email", "conversation_uuid", t.conv.UUID, "error", sendErr) @@ -275,6 +282,27 @@ func (t *sendEmailVerificationTool) Execute(ctx context.Context, args string) (s return "A verification code has been emailed to the customer. Tell them you have sent a code to their email and ask them to reply with it.", nil } +// noReplyAddress returns a no-reply address on the notification sender's domain, or "" if unavailable. +func (m *Manager) noReplyAddress() string { + raw, err := m.setting.Get("notification.email.email_address") + if err != nil { + return "" + } + var addr string + if err := json.Unmarshal(raw, &addr); err != nil || addr == "" { + return "" + } + parsed, err := mail.ParseAddress(addr) + if err != nil { + return "" + } + at := strings.LastIndex(parsed.Address, "@") + if at < 0 { + return "" + } + return "noreply@" + parsed.Address[at+1:] +} + // checkEmailVerificationTool verifies the code the customer entered against the pending one. type checkEmailVerificationTool struct { m *Manager