Skip to main content
This page covers the Brevo mailer implementation, including transactional email plus CRM contact create, update (join/leave), and destroy flows.

How it works

The backend loader selects Brevo using tools.mailer.client, then delegates all mailer operations to the Brevo package.

Brevo tools.mailer.from

Config matches SendGrid and Local: always { email, name } from MAILER_FROM_EMAIL and MAILER_FROM_NAME (see apps/backend/src/configuration/development.ts and production.ts). You do not put Brevo’s numeric sender id in env or Zod config.
💡 Tip: After changing mailer env vars, recreate the backend container (docker compose up -d --force-recreate backend) so Compose reloads env_file. docker compose restart alone does not refresh injected env.

Extending this system

  1. Add/update shared types in apps/shared/src/mailer/types.ts (e.g. BrevoMailerConfigType).
  2. Add/update shared validation in apps/shared/src/mailer/schema.ts (brevoSchema).
  3. Implement Brevo behavior in apps/tools/mailer/brevo/src/index.ts.
  4. Add tests in apps/tools/mailer/brevo/__tests__/buildMailer.spec.ts.
  5. Verify loader behavior in apps/backend/src/tools/mailer/__tests__/loader.spec.ts.
  6. Run:

What not to do

❌ Never accept overlapping tags in join and leave. ❌ Never use destroy for list-unsubscribe semantics; destroy is hard delete. ❌ Never expose provider details in GraphQL/API responses.

File conventions

Brevo implementation files go in apps/tools/mailer/brevo/src/ with a single package entrypoint at src/index.ts. Tests belong in apps/tools/mailer/brevo/__tests__/.

Step-by-step setup

  1. Add dependency in apps/backend/package.json:
  1. Run dependency sync:
  1. Configure Brevo in apps/backend/src/configuration/production.ts (and match the same patterns in other env files as needed). from is always { email, name } from MAILER_FROM_* (production uses mailerEnvOrDefault fallbacks when those env vars are empty):
Local dev wiring lives in apps/backend/src/configuration/development.ts: same shape; MAILER_FROM_EMAIL and MAILER_FROM_NAME are required there (no hardcoded defaults).
  1. Add environment variables (see also .env.example at repo root):
  1. Public endpoint checklist when invoking CRM operations:
    • IP-based rate limiting
    • strict input validation
    • optional challenge token
    • masked PII logging
  2. Verify:

Live API smoke (optional)

This is for local development only — do not wire smoke:api into CI (it requires a real API key and can send email or create/delete contacts). Manually verify Brevo v3 (auth, optional transactional send, and CRM contact lifecycle) using the same request shapes as apps/tools/mailer/brevo/src/index.ts. Run the tooling-mailer-brevo package script inside the backend container. Never commit API keys. When Brevo is your mailer in local dev, BREVO_API_KEY is already loaded into the backend service from .env.development (same env_file as Compose uses for the app). You do not need docker compose exec -e BREVO_API_KEY=... unless you are overriding the key for a one-off run. Without BREVO_SMOKE_*, only GET /account runs (still validates the key). For the full suite, either add BREVO_SMOKE_* to .env.development and recreate/restart backend if needed, or pass them only on exec:
Typical one-liner (key already in container; optional BREVO_SMOKE_* in .env.development):
You can also docker compose exec backend sh, cd /app, and run the same pnpm line. The script runs: GET /account, optional POST /smtp/email, then (when list + email are set) contact preclean delete, create, duplicate create, update, delete, and delete missing — and exits non-zero if any expected status is wrong.

What’s next?