Start here
How it fits
MailingKit is a library, not a service. It runs inside your process, next to your code, and owns exactly one job: turning "send this email" into a delivered message.
The pipeline
Every send goes through the same steps, in this order:
- Render. For
send_template(), the data is checked against the template's model, then the subject, HTML and text are rendered. - Complete. The default sender is applied and a text part is generated from HTML when missing.
- Validate. Addresses, headers, subject, tags, recipient count and size are checked. Nothing unsafe reaches a provider.
- Redirect. If
redirect_tois set, every recipient is replaced by that one inbox. - Deduplicate. If an idempotency key was given and already used, the earlier result is returned.
- Send. The provider adapter delivers the message. Retryable failures are retried with backoff.
- Report. A
SendResultis returned, hooks receive an event and a log line is written, with no personal data.
Core versus optional
The rule is simple: if most applications need it to send one email correctly, it is core. Everything else is opt-in.
| Core (always installed) | Optional (extras or your own code) |
|---|---|
| Message model and validation | HTTP API providers such as Resend (mailingkit[resend]) |
| Jinja2 templates with typed data | Framework glue (mailingkit.contrib.django, imported only by Django) |
| Console, file, memory and SMTP providers | Shared idempotency stores (Redis, SQL) |
| Error hierarchy, retries, idempotency keys | Metrics and tracing, through hooks |
| Environment configuration, hooks, logging | Queues and background workers (planned for 0.2) |
Library, not service
A central mail service sounds tidy, but it adds a network hop, an API contract, authentication and another thing to deploy before anyone needs it. A library gives every application the same behaviour with none of that. MailingKit is built so the bigger shapes can grow out of it later without changing your code.
| Shape | How it looks | Status |
|---|---|---|
| Library | Each application calls mail.send() and delivers directly. | 0.1, today |
| Library plus worker | Applications call mail.enqueue(); a mailingkit worker process sends from a queue. | Planned for 0.2 |
| Central service | A small HTTP app you write on top of the library, if you ever need one. | An example, never core |
Monoliths and microservices
In a monolith, create one client at startup and share it. In a microservice architecture, each service installs MailingKit and configures it with the same environment variables, so every service validates, retries and reports errors the same way. Shared templates can live in a small internal package that each service depends on.
User service ──┐
Billing service ──┼── MailingKit (same library, same config) ── provider
Notification svc ──┘When you want one place to own credentials and templates, the planned worker mode lets services only enqueue, while a single worker does the sending.
What MailingKit is not
- Not a CRM, ATS, user system or workflow engine. Your application decides when to send.
- Not a database. It returns results and emits events; storing history is up to you.
- Not a marketing or newsletter tool.
- Not a mail server or a hosted service.