Email on a budget: sending, receiving, and a tiny forwarder Lambda
Part of my Krabber series, a Twitter clone in Go. The full source is on GitHub.
Intro
A social site needs email. It has to send activation links and password resets, and it needs an address people can actually write to. The expensive, heavy way is a mail provider and a hosted inbox. The Krabber way is Amazon SES for sending, SES again for receiving, and a tiny Go Lambda to forward the few real messages to my own inbox. The whole arrangement costs cents a month, and I’ll walk through all three parts.
I. Sending, done properly
Krabber only sends transactional mail: the activation link, the password reset, the welcome note. No newsletters, so no unsubscribe flow to build yet. It goes out through SES from no-reply@krabber.net.
The part that actually matters for sending is alignment, so the mail isn’t flagged as spam. That means three records on the domain: Easy DKIM (SES gives you three CNAMEs to add), a custom MAIL FROM subdomain (mail.krabber.net, with its own MX and SPF) so SPF lines up with the sending domain, and a DMARC record that starts at p=none and tightens to p=quarantine after a couple of clean weeks. (Fun aside: that custom MAIL FROM record is the one SES told me had “gone missing” during the outage. It hadn’t, the whole domain had just dropped out of DNS.)
There’s also a daily send cap in the app’s config, so a bug can’t fire off ten thousand emails and wreck both my reputation and my bill.
II. Receiving, without a mailbox
Here’s the trick I like most: Krabber accepts mail at support@krabber.net without running a mail server or paying for a hosted inbox at all.
An MX record on the apex points at SES’s inbound endpoint. SES has a receipt rule for support@ that scans the message for spam and viruses, writes the raw email to a private S3 bucket (with a 30-day lifecycle, which the privacy page promises), and then invokes a Lambda. That’s it. No IMAP, no inbox to secure, no monthly fee for somewhere to put mail that’s mostly going to be the occasional real question.
III. The forwarder Lambda
The Lambda is deliberately tiny: Go on the provided.al2023 runtime, arm64, 128 MB of memory. Its whole job is to take the message SES just stored and get it to me.
It does three things:
- Checks the spam and virus verdicts SES attached. If either is
FAIL, it drops the message and stops. Most junk never reaches my inbox. - Otherwise it re-sends the raw message through SES to my real address, with
From: Krabber support <support@krabber.net>(SES will only send as a verified identity),Reply-Toset to the original sender so I can just hit reply, and the subject prefixed with[support]. - Nothing else. Its IAM role can only read that one prefix in the bucket and send from the
krabber.netidentity, so even this little function is on a short leash.
At Krabber’s volume this costs well under ten cents a month. Bounces and complaints, meanwhile, go to an SNS topic that emails me, and SES’s account-level suppression list quietly stops me from ever sending to an address that bounced or complained, which protects my sender reputation without any code.
Conclusion
Email has a reputation for being a pain, and the hosted-inbox reflex is expensive for a site that gets a handful of real messages. SES plus a 128 MB Lambda gave me proper, aligned sending and a real support address for cents, with no mailbox to run or secure. The pieces are small and each one has exactly the permissions it needs, nothing more. If you’re adding email to a side project, look at receiving-to-Lambda before you pay for an inbox. Thanks for reading, and may your mail always align.