Services
Provider Model
Status quo — one adapter
Today the service supports exactly one provider: brevo. The send
path in email-service.ts is:
- Provider config —
dal.findonemailsender.providersWHERE provider = 'brevo'; admins create this row via the FE (POST/PUT /api/v1/entities/provider) withapi_key,api_endpoint,from_email,from_name,reply_to. Missing → explicit error ("admin must configure the Brevo provider"). - Client —
new BrevoClient(api_key, api_endpoint ?? "https://api.brevo.com/v1")constructed inline (injectable for tests). - Template —
email_templatesrow matched on(code, language_iso)from the request; Handlebars renders subject + body with the request'sparams. - Send —
POST {endpoint}/smtp/emails,api-keyheader. sender_log— one row per attempt, success and failure; rendered content and template uuid are hoisted outside the try so a failed send still logs the interpolated message for forensics.
Inbound triggers
| Subject | Durability | Flow |
|---|---|---|
emailsender.send | fire-and-forget pub/sub | subscribeToEmailSendRequests → sendEmail(request, actorId) |
webhook.emailsender.received | JetStream WEBHOOK stream | subscribeToWebhooks — external provider callbacks routed through the webhook ingress |
Request fields on the NATS contract are camelCase (templateCode,
languageIso); DB columns stay snake_case — the service maps at the
boundary, which is the only legal place for the translation.
Adding a second provider — the real extension point
There is no provider interface today — adding one means:
- a new
providers/<name>.tsclient (same shape asBrevoClient:(apiKey, apiEndpoint)→sendEmail(request)); - a provider branch in
email-service.sendEmailwherebrevois currently hardcoded — resolve the config row byrequest.provider(or a default) and construct the matching client; ProviderEntity.provideralready carries the discriminator — the config table needs no schema change.
Keep the sender_log contract identical for every provider: log before
returning, include rendered content on failure, never log api_key.
Last modified on