skip to content

1 min

  • tags
  • design-patterns
  • aws-ses
  • resend

a developer's sleight of hand

swapping email providers in under an hour

Kristijan Arsov / Unsplash

There's a very specific kind of joy when an abstraction you spent a few extra minutes on finally pays for itself. Mine showed up right before the weekend, which made it even better.

I swapped my whole email provider from AWS SES to Resend in under an hour. The provider change itself was about 20 lines of code, because everything email-related sits behind a small EmailProvider protocol.

Verification emails, welcome emails, suppression checks: all of it talks to that interface, and none of it knows what's behind it. So I wrote a ResendProvider that exposes the same methods on top of a different SDK, deleted the SESProvider, and rewrote the webhook handler for Resend's payload format.

Then I got to delete a good chunk of Terraform I no longer needed, which is my favourite kind of commit. Templates, rate limiting, the suppression list and token generation didn't change at all. Zero modifications to business logic.

The pattern is simple, and very tempting to break. You define a protocol for what your application needs from the dependency, not what the dependency happens to offer. Send an email. Check the suppression list. Handle a bounce.

python4 lines
class EmailProvider(Protocol):
    def send(self, message: EmailMessage) -> None: ...
    def is_suppressed(self, address: str) -> bool: ...
    def handle_bounce(self, payload: dict) -> None: ...

Then you write a thin adapter that maps the provider's SDK onto that protocol, and the rest of the app only ever imports the protocol.

The hard part is discipline. Keep the adapter thin, forever. No business logic, no special cases, no magic. The moment decisions creep into the adapter, you're coupled to the provider again, just with extra steps.

This goes well beyond email: payment processors, notification services, storage backends, anything where a third party owns the implementation. If swapping it means writing one new class and deleting the old one, you did the work right.