Both LeadMail-related touchpoints now logged in section B (Direct edits
to OSS files). Notes the LeadChat-owned vs upstream distinction so
future merges know which need defense and which are ours to evolve.
Captures the test sequence to run on staging when resuming this work,
including the critical regression check that the LeadmailDelivery payload
patch hasn't broken existing transactional emails.
Microsoft 365 Graph API requires the shared mailbox itself to be licensed
(€5/mo each), which doesn't scale across clients. Phase 1's graph_full
transport stays for licensed mailboxes; this commit adds two free-of-extra-
licensing transports alongside it. Admin picks per channel.
Three transports now:
- graph_full -> Graph send + (Phase 2) Graph webhook receive
- imap_smtp -> M365 xoauth2 SMTP send + xoauth2 IMAP receive
- imap_leadmail -> LeadMail API send + xoauth2 IMAP receive
Backend:
- MicrosoftSharedConcern: SCOPES_BY_TRANSPORT map (Graph and Outlook scopes
are different audiences — separate scope set per transport).
- AuthorizationsController accepts and validates a transport param,
uses transport-specific scopes, encodes transport into signed state.
- CallbacksController reads transport from state, dispatches to the right
access verifier (Graph for graph_full, new ImapAccessVerifier for imap_*),
stores transport in provider_config, sets imap_login=upn for IMAP transports.
- New Microsoft::Shared::ImapAccessVerifier opens a Net::IMAP connection,
authenticates XOAUTH2 with the SHARED mailbox UPN as username and the
delegate's access token, runs CAPABILITY, closes. Confirms the SASL pattern
works for this delegate before creating the channel.
Send routing (overlay on ConversationReplyMailerHelper):
- graph_full -> :microsoft_shared delivery (existing Graph send service)
- imap_smtp -> :smtp with xoauth2, delegate UPN as SASL user, channel.email
as From: header (Microsoft routes via Send As permission)
- imap_leadmail -> :leadmail delivery (existing LeadmailDelivery + the small
extension below to forward threading headers)
Receive routing:
- New Custom::Leadchat::Inboxes::FetchImapEmailsJobExtension overlay routes
microsoft_shared channels with imap_* transport to the existing
Imap::MicrosoftFetchEmailService. That service already does xoauth2 IMAP
with channel.imap_login as the SASL user; we set imap_login=upn at channel
creation so the existing fetch logic reads the shared mailbox's INBOX with
no service-level changes.
LeadmailDelivery: build_payload now forwards in_reply_to, references, and
message_id so conversation reply threading works on the imap_leadmail path.
LeadMail server should preserve these as RFC822 headers on the outbound MIME.
Frontend:
- MicrosoftShared.vue gets a transport radio with 3 options + per-option help.
- microsoftSharedClient already passes payload through (no change).
- en inboxMgmt.json: TRANSPORT.{IMAP_SMTP,IMAP_LEADMAIL,GRAPH}.{TITLE,HELP}.
Token management: existing Microsoft::RefreshOauthTokenService works for
all three transports — refresh tokens come back with the same scopes the
original grant had.
Phase 2 (Graph webhook receive for graph_full) remains pending; only needed
if you ship licensed-mailbox channels.
Adds a new "microsoft_shared" Email Channel provider that uses Microsoft
Graph API end-to-end for sending. Bypasses xoauth2 SMTP, so it works on
tenants with Security Defaults enabled and survives the April 2026 SMTP
Basic Auth retirement.
Flow:
1. Admin picks "Microsoft Shared Inbox" in the Add Inbox wizard
2. Enters the shared mailbox UPN (e.g. sales@company.com)
3. Single OAuth round-trip as a delegate user; UPN is carried in a signed
state parameter so the callback knows which mailbox to bind
4. Callback verifies the OAuth user has access to the UPN via
GET /users/{upn}/mailFolders/inbox before creating the channel
5. Replies route through a custom :microsoft_shared ActionMailer delivery
method that POSTs base64-encoded MIME to /users/{upn}/sendMail
Channel modifications use the custom/ overlay (Custom::Leadchat::*).
Direct OSS edits are wrapped in '# === LeadChat: microsoft_shared ===' markers.
Phase 2 (Graph webhook receive) and Phase 3 (re-auth, subscription
failure surfacing, channel-destroy cleanup) follow.
Azure app prerequisites (one-time, manual): add delegated permissions
Mail.Send.Shared, Mail.ReadWrite.Shared, User.Read, offline_access; grant
admin consent. No new app registration required.
See LEADCHAT.md for the full customization index.
Foundation for LeadChat customizations using Chatwoot's built-in custom/
extension mechanism (ChatwootApp.extensions). Adds:
- LEADCHAT.md: index of every customization touchpoint, updated as work lands
- custom/: overlay tree where Custom::Leadchat::* modules live, autoloaded
alongside enterprise/ via a parallel block in config/application.rb
- Channel::Email#microsoft_shared? predicate as the first overlay (used by
the upcoming Microsoft 365 Shared Inbox provider)
OSS-file additions wrapped in '# === LeadChat: ===' markers so future
upstream-merge conflicts are mechanical.