Email Clients & Servers
Python Standard Library
Python has first-class email support baked in:
smtplib— sending email via SMTPimaplib— reading and managing email via IMAPemail— constructing and parsing messages (EmailMessage,email.parser)mailbox— reading local mailbox formats (Maildir, mbox)smtpd— deprecated mock SMTP server (removed in Python 3.12)socketserver— low-level TCP server, useful for rolling a custom mock SMTP
Sending (smtplib)
import smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg["From"] = "[email protected]"
msg["To"] = "[email protected]"
msg["Subject"] = "Hello"
msg.set_content("Body text here")
with smtplib.SMTP("smtp.example.com", 587) as smtp:
smtp.starttls()
smtp.login("user", "password")
smtp.send_message(msg)
Reading (imaplib)
import imaplib, email
with imaplib.IMAP4_SSL("imap.example.com") as imap:
imap.login("user", "password")
imap.select("INBOX")
_, ids = imap.search(None, "ALL")
for uid in ids[0].split():
_, data = imap.fetch(uid, "(RFC822)")
msg = email.message_from_bytes(data[0][1])
print(msg["subject"], msg["from"])
Mock SMTP Server (aiosmtpd — recommended)
from aiosmtpd.controller import Controller
from aiosmtpd.handlers import AsyncMessage
class MyHandler(AsyncMessage):
async def handle_message(self, message):
print(f"Subject: {message['subject']}")
controller = Controller(MyHandler(), hostname="127.0.0.1", port=1025)
controller.start()
JavaScript / TypeScript
JS/TS has no standard library for email. The language was born in the browser where direct TCP socket access is blocked. Node.js has low-level primitives (net, tls) but email was never added to core.
What Node.js provides
| Module | Relevance |
|---|---|
net |
Raw TCP sockets — could implement SMTP by hand |
tls |
TLS wrapping for SMTPS/IMAPS |
crypto |
Hashing for auth mechanisms |
stream |
Handling large message bodies |
The ecosystem answer (npm)
| Need | Package |
|---|---|
| Sending | nodemailer |
| Mock SMTP server | smtp-server (nodemailer family) |
| IMAP / receiving | imapflow |
| Parse raw messages | mailparser |
How Email Threading Works
Every email is an individual message. Threading is layered on top via headers:
Message-ID: <[email protected]> ← unique ID for this message
In-Reply-To: <[email protected]> ← ID of the message being replied to
References: <xyz789@...> <abc123@...> ← full chain of ancestor IDs
- The server stores flat, individual messages — no tree structure
- The client reconstructs threads by building a graph from these headers
- Fallback: if
In-Reply-Tois missing, clients match onSubject(strippingRe:,Fwd:etc.) — this is fuzzy and can cause incorrect grouping - Gmail extension: adds a proprietary
X-GM-THRIDheader so its IMAP server can return all messages in a thread directly, without client-side reconstruction
Third-Party Libraries & Apps (Python)
Libraries
| Library | Purpose |
|---|---|
aiosmtpd |
Modern async SMTP server (replaces deprecated smtpd) |
mailbox (stdlib) |
Read/write Maildir, mbox formats |
flanker |
Robust email address and MIME parsing |
imaplib2 |
Async-capable drop-in for stdlib imaplib |
Local Mail Server Applications
| Tool | Purpose |
|---|---|
| Mailpit | Lightweight SMTP catcher with web UI — dev/test only |
| smtp4dev | Similar to Mailpit, cross-platform |
| Postfix + Dovecot | Production-grade SMTP + IMAP — heavy but full-featured |
| Maildrop | Minimal local delivery agent |
Mailpit
A lightweight, single-binary SMTP catcher for development and testing.
What it is
- Accepts SMTP connections on
localhost:1025 - Captures all mail — nothing is delivered unless explicitly configured
- Provides a web UI at
localhost:8025and a REST API for integration testing - Also offers an optional POP3 server
Sending / Relay Modes
| Mode | Behaviour |
|---|---|
| Default | Captures mail, never sends it anywhere |
| SMTP Relay (manual) | Capture + manually "release" via web UI to a real SMTP server |
| Relay All | Capture + automatically forward everything via a real SMTP server |
| SMTP Forwarding | Capture + send a copy to a fixed address automatically |
Relay requires a config file:
host: smtp.gmail.com
port: 587
starttls: true
auth: plain
username: [email protected]
password: your-app-password
What Mailpit Does NOT Support
- Multiple mailboxes — all captured mail lands in one shared inbox regardless of recipient. This was requested and closed as not planned. Workaround: run separate instances on different ports.
- Fetching external mail — Mailpit has no IMAP client. It cannot connect to Gmail or any external server. It only receives mail sent directly to it.
Mailpit is not an email client
It cannot be used to read a real inbox (Gmail, Outlook, etc.). For that you need:
- A real IMAP client library (
imaplib,imapflow) - Or a desktop client (Thunderbird, Spark, etc.)
- Or a terminal client (aerc, neomutt)
- Or a sync tool (
mbsync,offlineimap) to pull mail to a local Maildir