A shared email gateway in Rust
A Rust API I built around Amazon SES v2 to manage email sending and quotas for several company products.
My role: Sole author. I wrote 53 of 54 commits.
October 2024 to April 2026
- Rust
- Rocket
- Amazon SES v2
- Tokio
Why we needed one shared service
Several of our products need to send emails through the same Amazon SES account. SES applies its sending rate and rolling 24-hour quota to the complete account, not to each application separately.
If each product integrates SES by itself, it has no idea how much quota the others have already used. One product can then cause the whole account to be throttled or suspended, which also stops every other product from sending.
What I built
I built a Rust service with Rocket that sits between our products and SES. It has six authenticated endpoints for sending, bulk sending, reading and clearing logs, checking the quota, and health checks. The other products call this API instead of talking to SES directly.
Bulk sending and quotas
The bulk engine asks SES for the current maximum send rate at runtime, splits the recipient list into matching chunks, and throttles each chunk to the per-second limit. This value can change when AWS updates the account’s sending reputation, so storing it in configuration would eventually be wrong.
Before each send, the service also checks the remaining 24-hour quota. If a bulk request is too large, it sends what can fit and clearly reports the skipped recipients. The response uses HTTP 207 Multi-Status with a result for each address: sent, failed or skipped. A single 200 or 500 would not tell the caller which emails need to be retried.
Logging without slowing down a send
Every send is recorded, but I did not want log writes to sit between the caller and SES. The service dispatches them as Tokio background tasks into a mutex-protected ring buffer, which is limited to 10,000 entries and exposed through a paginated endpoint. Without that limit, a long-running service would eventually use all of its memory just to keep old logs.
Testing against the real provider
Alongside the unit tests, I wrote a 973-line integration suite that runs against real SES. Mocks are useful to check our own code, but they do not confirm that the throttling, quota accounting and per-recipient results behave like the real service. This is especially important for an API that can get the company account suspended if it sends incorrectly.
The rest includes Bearer-key authentication through a Rocket request guard,
base64 attachments with MIME and size validation, and typed errors built with
thiserror.
In numbers
I wrote 53 of 54 commits. The project contains roughly 1,900 lines of service code and 1,350 lines of tests and examples, with around 40 tests in total. It is used by the campaigns and maintenance products.
Get in touch
I am available for remote freelance work. The easiest way to reach me is by email.