Email

Gmail API or Azure Communication Services

Email is the transport that powers verification mails, password resets, magic-link sign-in, invites, monitoring alerts and the notification email channel. Configure it once and every feature that needs to send mail just works.

Two providers are supported: the Gmail API (via a Google service-account JSON key) and Azure Communication Services (via a connection string). You pick one; Nucleus abstracts the rest behind a single EmailService.

Provider selection#

Choose which transport sends your mail. Only the matching sub-block needs to be filled in.

config.nucleus.json — email (Gmail)
1{2  "email": {3    "provider": "gmail",4    "gmail": {5      "enabled": true,6      "json_file_path": "./secrets/gmail-service-account.json",7      "from_email": "[email protected]",8      "from_name": "Acme"9    }10  }11}
provider'gmail' | 'azure'Optional

The active email backend. gmail uses the Gmail API with a service account; azure uses Azure Communication Services. An explicit provider always wins and selects that transport (activeEmailProvider): provider: 'azure' resolves to Azure and provider: 'gmail' resolves to Gmail — there is no fallthrough to the other transport. If provider is omitted, whichever provider is switched on serves, Azure first, then Gmail. The EmailService is built lazily on first use (not at boot) and cached; every send path re-reads the current credentials first, so a key or connection string rotated from the admin panel takes effect on the next email rather than at restart. The chosen provider is shared by every feature that sends mail (verification, password reset, invite, alerts, notifications). Provider selection, the per-provider enabled flags and all credentials resolve live from the encrypted Secrets store when config.secrets is enabled (falling back to env-var-name then literal), so an operator can enable a provider and rotate keys from the admin panel without a redeploy.

  • gmailGoogle Workspace Gmail API via a service-account key file.
  • azureAzure Communication Services via a connection string.

Gmail#

Send through the Gmail API using a Google service account. The service-account JSON key is supplied either by file path (json_file_path) or inline as a string (service_account_json, preferred via /secrets), and must have domain-wide delegation to send as from_email.

gmailobjectOptional

Gmail provider configuration.

enabledbooleanOptional

Activate the Gmail transport.

json_file_pathstringOptional

Path to the service-account JSON key file on disk. One of json_file_path OR service_account_json is required; if neither is set, GmailService initializes nothing (warns 'No service-account JSON or file path provided') and email is silently disabled, so passwordReset/magicLink/invite that depend on it won't send. service_account_json takes precedence when both are present. Treated as sensitive — keep it out of source control and mount it as a secret.

service_account_jsonstringOptional

The service-account JSON key itself, as a string — takes precedence over json_file_path when both are set. Prefer storing it through /secrets (slot email.gmail.service_account_json) so the private key lives encrypted in the DB instead of on a mounted volume, and can be rotated from the admin panel; a literal here is accepted but sits in plaintext in config.json.

from_emailstringOptional

The sender address mail is sent as (requires delegated authority). ALSO required for the Gmail transport to become available: if json_file_path is set but from_email is omitted, GmailService is constructed but initialize() bails (warns 'From email not configured') and isAvailable() stays false — email is silently disabled, exactly as with a missing json_file_path.

from_namestringOptional

Friendly display name shown to recipients — defaults to 'Vorion' when omitted (from_name || 'Vorion'), used verbatim in the From header, so recipients see 'Vorion <from_email>' unless you set your own brand.

Azure Communication Services#

Send through Azure ACS. The connection string and sender address resolve live from the secret store, then an env-var name, then a literal — so they can be set and rotated from the admin panel. Requires the optional @azure/communication-email package.

azureobjectOptional

Azure ACS provider configuration.

enabledbooleanOptional

Activate the Azure transport.

connection_stringstringOptional

ACS connection string. Resolved live in order secret store (slot email.azure.connection_string) → env-var name → literal, so it can be set and rotated from the admin panel. Sensitive — prefer the secret store or an env var over a literal.

sender_addressstringOptional

Verified ACS sender address. Like connection_string, it resolves live in order secret store (slot email.azure.sender_address) → env-var name → literal.

from_namestringOptional

Configured but currently NOT used by the Azure Communication Services transport — the sender is set from sender_address only and no display name is applied, so this value is never shown to recipients. (Gmail's from_name, by contrast, does appear in the From header.)

Under the hood — the EmailService#

Whichever provider you pick is hidden behind one EmailService interface, so every feature that sends mail calls the same method and stays provider-agnostic.

EmailService interfaceisAvailable · sendEmail · 6 typed sendersOptional

The interface exposes eight methods: isAvailable(), the generic sendEmail({ to, subject, html?, text?, from?, replyTo?, attachments? }) — html itself is optional (send text-only mail via text), plus six dedicated senders — sendWelcomeEmail, sendVerificationEmail, sendPasswordResetEmail, sendMagicLinkEmail, sendAlertEmail, sendInvitationEmail. Most flows call the dedicated sender for their feature — sendWelcomeEmail/sendVerificationEmail (email verification + register), sendMagicLinkEmail (magic-link login), sendInvitationEmail (invite), sendAlertEmail (monitoring alerts). One exception: the live password-reset route does NOT use sendPasswordResetEmail — that branded sender is defined on both implementations but is currently uncalled anywhere in the framework; the reset flow instead sends via the generic sendEmail with an inline HTML body. Callers check isAvailable() and no-op cleanly when email isn't configured, rather than throwing — so disabling email never breaks a request.

two implementationsGmail · Azure ACSOptional

Gmail authenticates with a service account and sends via the Gmail API; AzureEmailService uses an Azure Communication Services connection string and verified sender. Selection is symmetric: activeEmailProvider() honours an explicit provider (it always builds that transport, never falling through to the other), and when provider is omitted it picks whichever provider is switched on, Azure first. getEmailService() then builds the selected transport lazily on first use, caches it, and calls reconfigure() on every send so a rotated credential takes effect on the next email.

one transport, many senderssharedOptional

Because verification, password reset, magic link, invites, monitoring alerts and notifications all resolve the same EmailService instance, configuring this one block lights up email across every feature at once.

Related sections