← all builds

From-Scratch Build · Backend Services

Mailing-List Email Daemon

A self-hosted mailing list. Send a message to a single list address and a background service quietly fans it out to everyone subscribed — handling sign-ups, unsubscribes and multiple lists, all from one mailbox. Built from scratch to learn how email actually moves under the hood.

PythonDjangoIMAP / SMTP OAuth2Docker

What it is

One address, many inboxes

The service runs your own group mailing lists. Each list has an alias — a single address people can write to. A long-running daemon watches the mailbox over IMAP, and whenever a message arrives for a list alias, it re-sends that message over SMTP to every active subscriber of that list. Reply to the group, and everyone gets it, without anyone needing a third-party platform.

I built this because email feels like magic until you implement it. I wanted to see, end to end, how a server reads new mail, decides who a message belongs to, and relays it on — and how subscribe and unsubscribe requests fit into the same loop.

The core idea I wanted to learn: a mailing list is just a polling loop with a routing table. Watch one mailbox, match the recipient alias against a list of subscribers, and forward — the whole behaviour falls out of "read, look up, send", done reliably and on a schedule.

The stack

Tools under the hood

This rebuild is a small backend that touches the raw email protocols as well as a web framework. Here is what each piece does.

framework

Django

Holds the data model and admin — lists and subscribers — plus the management command that launches the daemon.

inbound

IMAP

The protocol for reading a mailbox. The daemon polls for new, unseen messages to process.

outbound

SMTP

The protocol for sending mail. Each matched message is relayed out to every subscriber on the list.

auth

OAuth2 tokens

Instead of a password, the daemon authenticates to the mail provider with a refreshable access token.

storage

Database model

Mailing lists and subscribers, with a many-to-many link so one person can belong to several lists.

deployment

Docker + supervisor

A container runs the web app and the daemon side by side, kept alive by a process supervisor.

Architecture

The daemon's loop

The heart of the system is one resilient loop. It wakes on a schedule, drains anything new, and goes back to sleep — simple enough to reason about, sturdy enough to leave running.

  1. Authenticate live

    Refresh the OAuth access token and open IMAP and SMTP connections to the provider.

  2. Poll for new mail live

    Check the mailbox for unseen messages that have arrived since the last pass.

  3. Route by alias live

    Read each message's recipient and match it against a known mailing-list alias.

  4. Fan out live

    Re-send the message over SMTP to every active subscriber of the matched list.

  5. Handle membership live

    Process subscribe and unsubscribe requests, updating the list and confirming by email.

  6. Sleep & repeat live

    Record the checkpoint, wait the polling interval, and run the loop again.

How it runs

Lists, subscribers, confirmations

Around the loop sits the management layer that makes the service usable rather than just functional:

In my rebuild I focused on making the loop dependable — token refresh, a last-checked checkpoint, and clean reconnects are what let it run unattended for days.

Reflection

What rebuilding it taught me