Small Plates

6 min read Original article ↗

For over a decade I ran Postfix on a Linux server for my email. It required a lot of setup initially, but it worked perfectly. Would I recommend that pathway to anyone else?

Well, no.

Technical efforts aside (which were substantial) there was a cognitive load in running my own server. What if it was hacked or attacked? What about spam reputation and message deliverability?

Also every account recovery mechanism on the planet is happy to send a password reset link to your email address. A hacked email server permits an attack on all your other accounts too.

So at the start of 2025 I decided to try something different. I moved the responsibility for sending and receiving email out of my little EC2 frying pan and into the AWS Simple Email Service (SES).

Have I traded one set of problems for another?

The new setup is more portable and feels like less of a technical burden than a single server sitting on the public internet, it certainly has less cognitive burden from a security perspective.

Email for maniacs? I do have a tendency to find solutions that are a little unrelatable. And so would I recommend this way of doing things to others?

Well, maybe.

I like it quite a lot. But unless you know the ins-and-outs of AWS, DNS and mail delivery, it is quite a lot. But feel free to check out the HOWTO below.

I will say, it's the best solution I have so far. Very secure, high level of control, "serverless" and aside from my time-spent, basically free to run.

Overview

When someone emails me:

  • Email is received by AWS SES and automatically delivered to an S3 bucket
  • A cron job running on a server at home, polls that bucket every 2 minutes for new mail
  • Then downloads the email and runs it through procmail for any filtering and spam detection
  • Then saves the email to a Maildir folder on my NAS/NFS

When I send email to others:

  • Email is composed locally (mutt)
  • Email client configured to send email through AWS SES SMTP servers
  • AWS SES SMTP delivers the email to the recipients

How do I access my email when I'm working remotely or on my phone? The short answer is VPN (wireguard/tailscale) back home to where the mail is stored. I might leave that part of it for another time.

HOWTO

Ok this looks appalling. But most of it is just DNS setup and you can glean most of it from the AWS SES Identity creation section in the AWS web console. I'll walk you through it anyway using a domain name of mine that I never figured out what to do with: alouy.com.

  1. Get your domain name registration and DNS out of AWS Route53 Just my opinion, but this ensures that DNS names can always be redirected elsewhere in times of serious trouble with AWS. Eg, in the examples below I've screenshotted the Cloudflare DNS manager.
  2. Deactivate the SES limits sandbox This normally takes a support ticket conversation with AWS (and I suspect the process is designed to dissuade you) but basically you want to ensure that for the AWS region you care about, your AWS SES service is not still in the email limits sandbox.
  3. Create a new AWS SES Identity for your domain name
    • In your AWS SES region, click the Create Identity Button
    • Identity Type: Domain
    • Enter your domain name (eg for me: alouy.com)
    • Select the checkbox for "Use a custom MAIL FROM domain"
    • Make up a sub-domain for the MAIL FROM, eg in my case: mail.alouy.com
    • Publish DNS records to Route53? As mentioned above I'd suggest no, it does mean you've got to do all your DNS records manually, but thankfully AWS displays the exact records you need on the SES Identity create page.
  4. Verify your Domain with DKIM
  5. Setup MAIL FROM Remember how we told AWS we wanted to use mail.alouy.com as our custom MAIL FROM? Well we need to setup more DNS records to allow that to happen.
  6. Setup DMARC While we're waiting lets add DNS records in for DMARC (Domain-based Message Authentication, Reporting, and Conformance). Same deal as before, you open up the Publish DNS records section and it'll tell you what new DNS records need to exist. For me it's just a new TXT record for _dmarc.alouy.com.
  7. Verify the verifications Now when you refresh the AWS SES page, you should see all green "Successful" messages there. If not, very carefully check the DNS records you added, wait 10 minutes, then check again.
  8. Sending email
  9. Configure mail delivery, and then fetch it
    • Create an S3 bucket
    • Go to AWS SES Email Receiving
    • Setup a Ruleset with an action to deliver to your Amazon S3 bucket
    • Configure a user with a role that can read and delete the files in the bucket so you can fetch email. Create an access key for the user, and then use those credentials in the script that follows.
    • The simplified version of the script that actually fetches the emails from the S3 bucket. It grabs the email files out of the S3 bucket, then pushes them locally through procmail (a mail filtering program) which dumps them into the pertinent local email (maildir) folders.

      fetch-mail.sh

      #!/bin/bash set -euo pipefail shopt -s nullglob

      # Credentials to download emails from s3 bucket

      export AWS_ACCESS_KEY_ID="MY_KEY_ID" export AWS_SECRET_ACCESS_KEY="MY_SECRET_KEY" temp="/path/to/tmp/" aws s3 mv --quiet --only-show-errors --recursive MY_BUCKET/ ${temp}/ >/dev/null for f in "${temp}"/*; do dos2unix -q "${f}" cat ${f} | /usr/bin/procmail -m /path/to/procmailrc rm ${f} done
    • Being triggered by a crontab job that looks like this:

      crontab

      */2 * * * * /path/to/fetch-mail.sh > /dev/null
      Or also manually triggered by a mutt shortcut key for quick adhoc checking:

      .muttrc

      macro index R "!/path/to/fetch-mail.sh\n<sync-mailbox>"
      The procmailrc looks a bit like:

      procmailrc

      MAILDIR=/path/to/Mail/ LOGFILE=/path/to/Mail/procmail.log

      # just for example, match some words in emails, force them to go to particular mailboxes

      :0HB * stripe.com ${MAILDIR}stripe/ :0HB * tfstate ${MAILDIR}tfstate/ :0HB * dibsonstuff ${MAILDIR}dibsonstuff/

      # filter mail through bogofilter, tagging it as Ham, Spam, or Unsure

      :0fw | bogofilter --bogofilter-dir /path/to/Mail/.bogofilter -e -p

      # file the mail to spam-all if it's spam

      :0: * ^X-Bogosity: Spam, tests=bogofilter ${MAILDIR}spam-all/

      # if it is maybe spam, file the mail to spam-maybe

      :0: * ^X-Bogosity: Unsure, tests=bogofilter ${MAILDIR}spam-maybe/

      # everything else goes to the inbox

      :0 : ${MAILDIR}inbox/