feat(core): email transport seam for provider plugins (G-P4-5)
Merged
EV
10d ago+596+596+012345678901234567890123456789-17-17-01234567890123456789
Plan item G-P4-5 (core) — Gauzy ↔ Ever Works integration plan, 04-inbox-and-email.md §5.5 / 06-gauzy-implementation-plan.md Phase 4.
Owned by 7dae55c0/gw-gauzy-email — other lanes please don't push to this branch.
What
EmailTransportRegistry(packages/core/src/lib/email-send/email-transport.registry.ts): provider plugins (Resend, Postmark, Mailgun, SendGrid, SES — each its own PR, each behind its ownEMAIL_PROVIDER_*_ENABLEDflag, default off) register a resolver that supplies a nodemailer-compatible API transport for a{ tenantId, organizationId, providerAccountId }request. One registry per process (getEmailTransportRegistry(), also provided byEmailSendModule).EmailSendService.getEmailInstanceasks the registry first; when no resolver is registered, or none answers, it runs the existing SMTP chain verbatim (organization Custom SMTP → tenant Custom SMTP →MAIL_*; the old body moved unchanged intosmtpEmailInstance).EmailSendService.sendMessage(message, scope): a non-templated send (From, To/Cc/Bcc, Reply-To,In-Reply-To,References,Message-ID, headers, attachments) through the same chain, answering{ messageId, accepted, rejected, transport }. The Inbox reply / forward routes (/api/inbox/emails/:id/reply|forward) use it; they ship with the inbound-pipeline PR (G-P4-2) because they need theinbox_emailtable it creates.EmailSendModulealso exportsEmailSendService+ the registry; they and their types are public API of@gauzy/core(exported at the end ofindex.ts, after./lib/corehas loaded the module graph, so no new require-cycle edge).
Zero behaviour change
- No resolver is registered unless a provider plugin is loaded, and none is loaded unless its flag is exactly
'true'. With an empty registryproviderEmailInstancereturnsnullbefore touching anything;getEmailInstancethen runs the old code unchanged.getInstance()(platform default for system mail) is untouched. - A resolver that throws is logged (via
describeEmailSendError, addresses masked) and skipped, so a broken provider account falls back to SMTP instead of stopping mail.
Tests
email-transport.registry.spec.ts: order, null/throw/no-transport skipping, duplicate-id refusal, unregister, process singleton.email-send.service.transport-registry.spec.ts: empty registry → SMTP chain; all-null → SMTP chain; provider → never reads Custom SMTP or verifies SMTP;sendMessagethrough a recording nodemailer custom transport keeps From / In-Reply-To / References.- Not run locally (owner rule for this run: review + merge, the full suites run once on
developat the end).prettier --check+cspellclean on the touched files.
🤖 Generated with Claude Code
Summary by cubic
Adds an email transport seam so provider plugins (Resend, Postmark, etc.) can supply nodemailer transports for email sending without changing the existing SMTP chain.
EmailTransportRegistrylets a provider register a resolver that answers per tenant, organization, and provider account; with no resolver registered, all sends take the unchanged SMTP fallback (Custom SMTP →MAIL_*).EmailSendService.sendMessagesends non-templated messages (Inbox replies and forwards) through the same chain, returning theMessage-ID, accepted/rejected recipients, and which transport sent it.- A resolver that throws is logged (addresses masked) and skipped, so a broken provider account falls back to SMTP instead of stopping mail.
- Resolver ids are trimmed on both
registerandunregister, so a padded id can be removed cleanly. - Tests cover registry ordering, fallback, and the trimmed-id fix.