deino-courier: Brevo, or a logging no-op
Transactional email that fails safe — the no-op adapter is a first-class citizen.

Transactional email is the kind of thing you do not think about until it fails at 2 a.m. deino-courier ships with two adapters: Brevo (the real provider) and a logging no-op. The no-op is not a placeholder; it is a first-class citizen, and it is the one that runs in every test and every local dev.
Transactional email is the kind of thing you do not think about until it fails at 2 a.m. deino-courier ships with two adapters: Brevo (the real provider) and a logging no-op. The no-op is not a placeholder; it is a first-class citizen, and it is the one that runs in every test and every local dev.
Two adapters, one trait
deino-courier defines a port — a trait for sending transactional email — and two adapters: a Brevo adapter that calls the Brevo API, and a logging adapter that writes the email to a log line and returns Ok. The domain code that sends email depends on the trait, not on either adapter. At composition time, deino-api picks one based on configuration. In production with a Brevo key, it picks Brevo. In a test, in local dev, or on a box with no key, it picks the no-op. Email goes out, or it does not, and the business logic never knows the difference.

Why the no-op is first-class
The no-op is not a stub you remove before shipping. It is a real adapter, shipped in the real crate, chosen by real configuration. The reason is that email is an external dependency with failure modes you do not control — rate limits, downtime, suspended accounts — and the platform has to run without it. The no-op is the adapter that lets the platform run when email cannot: in tests, where you do not want to send real email; in local dev, where you do not have a key; in a degraded production where Brevo is down and you would rather log and continue than crash the flow. A first-class no-op is the adapter that makes email optional, not load-bearing.
The no-op is not a placeholder. It is the adapter that makes email optional, not load-bearing — and that is a first-class property, not a stub.
Failing safe
The discipline is fail safe. If the email adapter errors, the flow does not crash — it logs and continues, because email is a notification, not a transaction. A user registers; the welcome email is a side effect; the registration is the transaction. If the email fails, the registration still succeeded. The no-op is the honest expression of that priority: email is best-effort, the transaction is the guarantee. The logging adapter is what a test uses to verify the email was composed correctly without sending it, which is the kind of test you can only write when the no-op exists.
- One trait, two adapters — Brevo (real) and logging no-op (fail-safe).
- Email is best-effort: a failed email does not roll back the transaction it notified about.
- The no-op lets tests verify composition without sending — and lets the platform run without a key.
What this is not
This is not a claim that email does not matter. It does — a welcome email that never arrives is a real product failure. The claim is narrower: email is not a reason to crash the flow. If Brevo is configured and errors, the adapter logs the error; the flow continues; an operator sees the log. If Brevo is not configured, the no-op runs; no email is sent; no error is raised. The two modes are different, and the trait is what lets the platform treat them the same. We are pre-revenue and building the POC; the courier is built, and the no-op is how we run it everywhere we do not want real email.
Email is a notification, not a transaction. A failed email does not roll back the registration it announced. The no-op is the honest expression of that priority.
— deino-courier design note