PrimeBrickPrimeBrick
  • Docs
  • Contact
  • MIT License
  • Documentation
  • MCP Server
  • API Catalog
  • Services
  • Libraries
PrimeBrickPrimeBrick

© 2026 PrimeBrick. MIT License. v3.8.0

github
Backend
Frontend
Microservices
    Microservices OverviewArchitectureConventions
    Services
      EmailSender Microservice
powered by Zudoku
Services

EmailSender Microservice

The EmailSender microservice handles email provider configuration, template rendering with Handlebars, and email dispatch via the Brevo provider. It receives send requests over NATS (not HTTP) and exposes provider/config management via HTTP entity CRUD routes proxied through the Backend.

  • Package: primebrick-emailsender
  • Service code: EMAILSENDER
  • Default port: 3003
  • Runtime: Bun (dev: bun --hot, prod: bun dist/index.js)
  • Database schema: emailsender (isolated per-service schema)

Architecture

HTTP Routes

All HTTP routes follow the standardized API path conventions (see Conventions). The Backend proxies requests via /ws/emailsender/*.

The EmailSender service exposes three route groups:

  • Providers (entity CRUD) — manage email provider configurations (Brevo API keys, sender settings). JWT auth + RBAC.
  • Config entries (entity CRUD) — module configuration key-value store. JWT auth + RBAC.
  • Webhook — inbound delivery status callbacks from email providers. API key auth + RBAC.

System endpoints: GET /health (public), GET /api/v1/openapi.json (public).

See the EmailSender API Catalog for the full interactive API reference — every path, method, operationId, parameter, request body, and response schema with live try-it functionality.

NATS Subjects

Email sending is triggered via NATS, not HTTP. The Backend publishes send requests and the microservice processes them asynchronously.

SubjectDirectionRBAC PermissionDescription
emailsender.sendSubscribeEMAILSENDER_SENDReceive SendEmailRequest, render template, send via Brevo, log to sender_log
emailsender.response.{requestId}Publish—Reply with SendEmailResponse (success/failure + providerMessageId)

SendEmailRequest

FieldTypeRequiredDescription
requestIdstringyesUnique request identifier (used in response subject)
templateCodestringyesTemplate code to look up in email_templates
languageIsostringyesTemplate language ISO code (e.g. en, it)
tostring[]yesRecipient email addresses
ccstring[]noCC recipients
bccstring[]noBCC recipients
variablesRecord<string, unknown>noHandlebars variables for template rendering
entityTablestringnoSource entity table (for logging)
entityIdbigintnoSource entity ID (for logging)
entityUuidstringnoSource entity UUID (for logging)

SendEmailResponse

FieldTypeDescription
requestIdstringMatches the request
successbooleanWhether the email was sent successfully
providerMessageIdstringBrevo message ID (on success)
errorstringError message (on failure)
logIdbigintsender_log row ID (on success)

Entities

providers (schema: emailsender)

Email provider configurations. Auditable + soft-deletable.

FieldTypeNullableDescription
idbigintnoPrimary key
uuiduuidnoUnique identifier
providerstring(50)noProvider name (e.g. brevo)
api_keystringnoAPI key for the email provider
api_endpointstringyesCustom API endpoint URL
from_emailstringyesDefault sender email
from_namestringyesDefault sender display name
reply_tostringyesDefault reply-to email
versionintegernoOptimistic lock version
created_attimestampnoCreation timestamp
updated_attimestampyesLast update timestamp
deleted_attimestampyesSoft-delete timestamp

email_templates (schema: emailsender)

Email templates with Handlebars-rendered subject/body. Auditable.

FieldTypeNullableDescription
idbigintnoPrimary key
uuiduuidnoUnique identifier
codestring(100)noTemplate code (looked up by NATS send request)
language_isostring(10)noLanguage ISO code
subjectstringyesHandlebars template for subject
body_htmlstringyesHandlebars template for HTML body
body_textstringyesHandlebars template for plain-text body
mjml_sourcestringyesMJML source (if template was designed in MJML)
variablesjsonbyesVariable schema/metadata
versionintegernoOptimistic lock version

sender_log (schema: emailsender)

Communication log for every email sent. NOT auditable (no created_at/ updated_at/version/deleted_at).

FieldTypeNullableDescription
idbigintnoPrimary key
entity_idbigintyesSource entity ID
entity_uuidstringyesSource entity UUID
typestring(50)noCommunication type (always email)
provider_message_idstringyesBrevo message ID (used for webhook matching)
provider_uuiduuidyesProvider config UUID
statusstring(50)noDelivery status (sent, delivered, opened, clicked, bounced, spam, blocked, deferred, failed)
template_uuidstringyesTemplate UUID used
sendersjsonbnoSender info ({ from: email })
recipientsjsonbnoRecipient info ({ to, cc, bcc })
interpolated_sent_messagestringyesFinal rendered HTML/text
error_messagestringyesError message (on failure)
sent_attimestampyesWhen the email was sent
status_changed_attimestampyesLast status change (updated by webhook)

config (schema: emailsender)

Module configuration key-value store. Auditable + soft-deletable.

FieldTypeNullableDescription
idbigintnoPrimary key
uuiduuidnoUnique identifier
keystring(50)noConfig key (unique)
valuestringyesConfig value
label_keystring(100)yesi18n key for display label
description_keystring(100)yesi18n key for description
versionintegernoOptimistic lock version

service_registry (schema: public)

Shared table in the public schema — not owned by emailsender. The microservice reads/writes its own registration row here via the SDK ServiceRegistrar. A copy of this entity exists in primebrick-be-v3.

Provider Integrations

Brevo

The BrevoClient class (src/providers/brevo.ts) sends emails via the Brevo REST API (POST /smtp/emails). The client is constructed per-request from the providers table config (API key + endpoint loaded from DB, not from env vars).

Brevo delivery events are received via POST /webhook?provider=brevo and processed by WebhookService, which maps Brevo event names to internal statuses:

Brevo eventInternal status
sentsent
delivereddelivered
openedopened
clickedclicked
bounce / hardbounce / softbouncebounced
spamspam
blockedblocked
deferreddeferred
invalid / errorfailed

Service Actions

EmailService.sendEmail(request, actorId)

Called by the NATS emailsender.send handler. Flow:

  1. Load Brevo provider config from providers table (filter by provider = 'brevo')
  2. Load email template from email_templates by code + language_iso
  3. Render subject/HTML/text with Handlebars using request.variables
  4. Send via BrevoClient.sendEmail()
  5. Log to sender_log with status sent + provider_message_id
  6. On failure: log to sender_log with status failed + error message

WebhookService.handleWebhook(provider, payload, actorId)

Called by the POST /webhook route. Flow:

  1. Validate provider is brevo
  2. Extract message-id and event from payload
  3. Map Brevo event to internal status via BrevoClient.mapStatus()
  4. Update sender_log row by provider_message_id with new status

Deployment

Environment variables

VariableRequiredDefaultDescription
DATABASE_URLyes—PostgreSQL connection string
DB_SCHEMAnoemailsenderDatabase schema name
SERVICE_BASE_URLnohttp://localhost:3003Exposed URL for BE proxy routing (dynamic in Docker)

Additional config (NATS URL, HTTP port, service code) is loaded from the config table at startup via ConfigLoader — not from env vars.

The BREVO_API_KEY is NOT an env var. It is stored in the providers table and set up by admin users via the FE (POST /api/v1/entities/providers).

Docker

The Dockerfile uses a two-stage build with oven/bun:1.1.0-alpine:

  • Builder: installs deps, compiles TypeScript
  • Production: installs prod deps only, copies dist/, runs bun dist/index.js
  • Port: 3003
  • Health check: GET /health every 30s

Dev compose (docker-compose.dev.yml) runs bun --hot src/index.ts with file watch for hot reload.

Database migration

TerminalCode
cd emailsender && pnpm run db:migrate

Uses @primebrick/sdk's applyPatches() runner via bun scripts/database-patch-apply.ts.

Health & Lifecycle

The service uses the SDK's ServiceRegistrar to register itself via NATS:

  • Registration: publishes a register event with service code, base URL, endpoints (webhook, health), version, and metadata (name, description, icon)
  • Heartbeat: periodic health checks published via NATS; checks DB connectivity and NATS connection status
  • Graceful shutdown: on SIGTERM/SIGINT, the SDK GracefulShutdown coordinator runs cleanup in order: stop heartbeat → unregister service → close NATS → close DB pool → close HTTP server

Next steps

  • Architecture — NATS bus, BE proxy, SDK lifecycle
  • Conventions — API path conventions, data model rules
Last modified on July 26, 2026
Conventions
On this page
  • Architecture
  • HTTP Routes
  • NATS Subjects
    • SendEmailRequest
    • SendEmailResponse
  • Entities
    • providers (schema: emailsender)
    • email_templates (schema: emailsender)
    • sender_log (schema: emailsender)
    • config (schema: emailsender)
    • service_registry (schema: public)
  • Provider Integrations
    • Brevo
  • Service Actions
    • EmailService.sendEmail(request, actorId)
    • WebhookService.handleWebhook(provider, payload, actorId)
  • Deployment
    • Environment variables
    • Docker
    • Database migration
  • Health & Lifecycle
  • Next steps