send AI verification code through the conversation's inbox

The OTP email went out over the notification email channel, which uses a separate SMTP config and a different sender. Now email conversations send the code through their own inbox, reusing the conversation subject so it threads into the same email thread instead of a new one. Livechat still falls back to the notification channel since it can't send email.
This commit is contained in:
Abhinav Raut
2026-07-20 21:30:25 +05:30
parent 085880d296
commit 7760974e07
2 changed files with 42 additions and 8 deletions
+21 -8
View File
@@ -249,14 +249,27 @@ func (t *sendEmailVerificationTool) Execute(ctx context.Context, args string) (s
return "", err
}
body := t.m.i18n.Ts("ai.agent.verificationEmailBody", "code", code)
if err := t.m.notifier.Send(notifier.Message{
RecipientEmails: []string{email},
Subject: t.m.i18n.T("ai.agent.verificationEmailSubject"),
Content: body,
Provider: notifier.ProviderEmail,
}); err != nil {
t.m.lo.Error("error sending verification code email", "conversation_uuid", t.conv.UUID, "error", err)
return "", err
// Livechat inboxes can't send email, so those fall back to the notification email channel.
var sendErr error
if t.conv.InboxChannel == channelEmail {
// Reuse the conversation's subject so the code threads into the same email thread instead of
// landing in a separate one the customer might reply to.
subject := t.conv.Subject.String
if subject == "" {
subject = t.m.i18n.T("ai.agent.verificationEmailSubject")
}
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{
RecipientEmails: []string{email},
Subject: t.m.i18n.T("ai.agent.verificationEmailSubject"),
Content: body,
Provider: notifier.ProviderEmail,
})
}
if sendErr != nil {
t.m.lo.Error("error sending verification code email", "conversation_uuid", t.conv.UUID, "error", sendErr)
return "", sendErr
}
t.m.lo.Debug("ai agent sent verification code", "conversation_uuid", t.conv.UUID)
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
+21
View File
@@ -1140,6 +1140,27 @@ func (m *Manager) BuildEmailThreadingHeaders(conversationID int, selfSourceID st
return references, inReplyTo
}
// SendTransientEmail sends a one-off email through the conversation's inbox, threaded like a reply, without recording it as a conversation message.
func (m *Manager) SendTransientEmail(inboxID, conversationID int, conversationUUID string, to []string, subject, htmlContent string) error {
inb, err := m.inboxStore.Get(inboxID)
if err != nil {
return fmt.Errorf("fetching inbox: %w", err)
}
if inb.Channel() != inbox.ChannelEmail {
return fmt.Errorf("cannot send email through non-email inbox %d", inboxID)
}
references, inReplyTo := m.BuildEmailThreadingHeaders(conversationID, "")
return inb.Send(models.OutboundMessage{
From: inb.FromAddress(),
To: to,
Subject: subject,
Content: htmlContent,
ConversationUUID: conversationUUID,
References: references,
InReplyTo: inReplyTo,
})
}
// NotifyAssignment sends notifications (in-app, WebSocket, email) for an assigned conversation.
func (m *Manager) NotifyAssignment(userIDs []int, conversation models.Conversation) error {
agent, err := m.userStore.GetAgent(userIDs[0], "")