join/leave), and destroy flows.
How it works
The backend loader selects Brevo usingtools.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 reloadsenv_file.docker compose restartalone does not refresh injected env.
Extending this system
- Add/update shared types in
apps/shared/src/mailer/types.ts(e.g.BrevoMailerConfigType). - Add/update shared validation in
apps/shared/src/mailer/schema.ts(brevoSchema). - Implement Brevo behavior in
apps/tools/mailer/brevo/src/index.ts. - Add tests in
apps/tools/mailer/brevo/__tests__/buildMailer.spec.ts. - Verify loader behavior in
apps/backend/src/tools/mailer/__tests__/loader.spec.ts. - Run:
What not to do
❌ Never accept overlapping tags injoinandleave. ❌ 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 inapps/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
- Add dependency in
apps/backend/package.json:
- Run dependency sync:
- Configure Brevo in
apps/backend/src/configuration/production.ts(and match the same patterns in other env files as needed).fromis always{ email, name }fromMAILER_FROM_*(production usesmailerEnvOrDefaultfallbacks when those env vars are empty):
apps/backend/src/configuration/development.ts: same shape; MAILER_FROM_EMAIL and MAILER_FROM_NAME are required there (no hardcoded defaults).
- Add environment variables (see also
.env.exampleat repo root):
-
Public endpoint checklist when invoking CRM operations:
- IP-based rate limiting
- strict input validation
- optional challenge token
- masked PII logging
- Verify:
Live API smoke (optional)
This is for local development only — do not wiresmoke: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:
BREVO_SMOKE_* in .env.development):
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.
