Skip to main content
Version: 0.60.0-rc.2

Notification Providers

Notification providers are runtime-configured integrations used by the notification module. The first supported provider kind is SMTP, which powers customer and admin email delivery.

This page documents the operational model introduced by the runtime provider work: SMTP settings and credentials are no longer expected to come from static application configuration or deployment-time secret injection. A new environment does not send email until an authorized admin creates an SMTP notification provider.

Current Model

  • Provider records are created and updated through the Admin Panel route /notification-providers or the Admin GraphQL API.
  • The codebase currently knows one provider kind, SMTP.
  • Only one provider row can exist per provider kind. A second SMTP provider returns DUPLICATE_PROVIDER.
  • Email jobs resolve the SMTP provider at send time.
  • There is no bootstrap or seed path for the first provider in this branch.

The relevant Admin GraphQL operations are:

  • notificationProviderCreate
  • notificationProviderConfigUpdate
  • notificationProviders
  • notificationProvider

The GraphQL provider enum is SMTP.

Permissions

Notification provider access is controlled by the notification permission sets:

  • NotificationProviderViewer: can read and list notification providers.
  • NotificationProviderWriter: can create and update notification providers.

Create authorization is checked against all notification providers. Update authorization is checked against the specific provider id being changed.

Creating The SMTP Provider

Use the Admin Panel when possible:

  1. Open /notification-providers.
  2. Create an SMTP provider.
  3. Enter the relay, port, TLS mode, admin panel URL, sender details, username, and password.
  4. Save the provider before relying on notification email in that environment.

The same setup can be done through Admin GraphQL:

mutation CreateSmtpNotificationProvider(
$input: NotificationProviderCreateInput!
) {
notificationProviderCreate(input: $input) {
... on NotificationProviderCreatePayloadSuccess {
notificationProvider {
notificationProviderId
name
provider
}
}
... on NotificationProviderCreatePayloadError {
code
}
}
}

Example variables:

{
"input": {
"smtp": {
"name": "Primary SMTP",
"relay": "smtp.example.com",
"port": 587,
"insecure": false,
"adminPanelUrl": "https://admin.example.com",
"fromEmail": "no-reply@example.com",
"fromName": "Lana Bank",
"username": "<smtp-username>",
"password": "<smtp-password>"
}
}
}

Do not put real secrets in docs, screenshots, support tickets, or shared shell history.

Rotating SMTP Credentials

Rotate SMTP credentials by updating the existing provider config:

mutation UpdateSmtpNotificationProviderConfig(
$input: NotificationProviderConfigUpdateInput!
) {
notificationProviderConfigUpdate(input: $input) {
... on NotificationProviderConfigUpdatePayloadSuccess {
notificationProvider {
notificationProviderId
name
provider
}
}
... on NotificationProviderConfigUpdatePayloadError {
code
}
}
}

The update input uses the same SMTP config fields as create, without name. The provider kind must match the existing provider. Updating an SMTP provider with a different config kind returns PROVIDER_TYPE_MISMATCH.

The process that creates or updates the provider invalidates its local SMTP sender cache. Other application replicas that already cached a sender refresh from storage after the cache TTL, currently 30 seconds. Missing providers are not negatively cached, so a replica without a cached sender checks storage on the next email send.

Secret Handling

SMTP username and password are GraphQL Secret inputs. They are write-only at the API boundary and are never returned by provider queries.

Inside the notification module, the provider config is stored in encrypted event payloads. SmtpConfig debug output redacts username and password, and email send spans do not record full email payloads, recipients, subjects, bodies, or secrets.

Notification provider key rotation participates in application startup. When a deprecated encryption key is configured, provider configs are decrypted with the deprecated key, re-encrypted with the active key, and written back. The startup path records a system audit entry for the notification provider update.

Missing Provider Behavior

If no SMTP provider is configured, notification email delivery is skipped and the job still completes. This preserves the current runtime behavior for environments that have not configured SMTP yet.

The skip is observable:

  • span: notification.send_rendered_email
  • email_type: stable low-cardinality email type, such as under_margin_call, overdue_payment, or deposit_account_created
  • provider_configured=false
  • delivery_outcome=skipped
  • skip_reason=no_smtp_provider

Provider resolution, template rendering, and SMTP send failures still fail the job and keep the existing retry behavior. Failed deliveries record delivery_outcome=failed and failure_stage as one of:

  • resolve_provider
  • render_template
  • smtp_send

Startup And Runtime Visibility

At startup, notification.init checks whether the SMTP provider exists after notification key rotation. The init span records:

  • smtp_provider_configured
  • smtp_provider_id, when configured

The service also emits one startup log with the message SMTP notification provider status at startup.

SMTP provider resolution uses the notification.smtp_sender span. It records:

  • provider_configured
  • provider_id, when configured
  • cache_hit

Use these fields to answer operational questions such as:

  • Did this environment have SMTP configured when it started?
  • Did this email skip because the provider was missing?
  • Did the sender come from cache or storage?
  • Which email type was skipped or failed?

Decision Record

Runtime notification providers replace the previous compile-time SMTP configuration model for notification email delivery.

The practical advantages are:

  • SMTP credentials can be created and rotated without a redeploy.
  • Secret inputs go through the Admin GraphQL Secret scalar instead of static config files.
  • Provider reads and writes use the existing authorization and audit paths.
  • Encrypted provider config can participate in application key rotation.

The accepted tradeoffs are:

  • A freshly created environment has no SMTP provider until an authorized admin creates one.
  • Missing SMTP provider is not a startup failure.
  • Missing SMTP provider skips notification email and completes the job.
  • There is no automated first-provider seed path in this branch.

This model is intentionally "runtime-configured instances of code-known provider kinds." Adding a new provider kind still requires code changes for its config shape, validation, delivery behavior, GraphQL input, and UI.