Mailrust
  • Product
  • Features
  • How it works
  • FAQ
  • GitHub
Self-host
v0.3.1 Self-hosted · Open source · Written in Rust · AGPL-3.0

One server for every email you send.

Mailrust is an open-source email platform you run on your own server. Write newsletters for your contacts in a dashboard, and send transactional emails from your app with an API. Every email goes out through your own Amazon SES account.

Self-host Mailrust >Read the docs
git clone https://github.com/irfanfaraaz/mailrust
mailrust serve
Try it: click the drawing
Urgent lane
0 sent
Newsletter
Wave 1 of 8
This wave
100 readers
Bounce rate
0.4%
  • Written in Rust
  • One other service to run: Postgres
  • AGPL-3.0, every line public
One server, two jobs

Newsletters and transactional emails, from one place

Newsletters go out in bulk to a contact list. Transactional emails are personal and triggered by your app: login codes, receipts, alerts. Mailrust sends both from one queue and one block list, so a big newsletter never holds up a login code.

Write a newsletter, send it in waves, read the report

Write in a block editor and type / to add images, buttons and merge tags. Before it sends, Mailrust checks the newsletter. Then it mails your most engaged readers first and grows each wave from there.

  • Block editor with autosave, previews and test sends to your team
  • Signup form with double opt-in, ready to embed on your site
  • Growing waves that stop on their own if bounces climb
  • A report with opens, clicks per link and a split by mailbox provider
Notes from the shop, No. 12
Saved just now · 3.1 KB of Gmail's 100 KB
We moved the kiln

Hi {first_name}, the new studio has a door wide enough for the kiln at last. Here is what the first firing looked like.

/ima
  • Heading H1 H2 H3
  • Image PNG JPG GIF WEBP
  • Button link
  • Merge tag {first_name}
  • Divider ---
Next wave: 400 readers4,812 subscribed · tag: customers

One request per email

Send login codes, receipts and alerts with POST /emails. Mark them urgent and they skip ahead of any newsletter in the queue. Retry with the same idempotency key and the email still goes out once.

  • Send one email or a batch of up to 100
  • Schedule up to 30 days ahead, and cancel before it leaves
  • Attachments, cc, bcc, reply-to and your own headers
  • Signed webhooks for delivered, bounced and complained
POST /emails
# send a login code
curl https://mail.example.com/emails \
  -H "Authorization: Bearer $MAILRUST_KEY" \
  -H "Idempotency-Key: login-ana-4812" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <login@acme.dev>",
    "to": "ana@example.com",
    "subject": "Your login code",
    "text": "Your code is 481 207",
    "priority": "urgent"
  }'
// send a login code
await fetch("https://mail.example.com/emails", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.MAILRUST_KEY}`,
    "Idempotency-Key": "login-ana-4812",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    from: "Acme <login@acme.dev>",
    to: "ana@example.com",
    subject: "Your login code",
    text: "Your code is 481 207",
    priority: "urgent",
  }),
});
# send a login code
requests.post(
    "https://mail.example.com/emails",
    headers={
        "Authorization": f"Bearer {os.environ['MAILRUST_KEY']}",
        "Idempotency-Key": "login-ana-4812",
    },
    json={
        "from": "Acme <login@acme.dev>",
        "to": "ana@example.com",
        "subject": "Your login code",
        "text": "Your code is 481 207",
        "priority": "urgent",
    },
)
// send a login code
reqwest::Client::new()
    .post("https://mail.example.com/emails")
    .bearer_auth(std::env::var("MAILRUST_KEY")?)
    .header("Idempotency-Key", "login-ana-4812")
    .json(&json!({
        "from": "Acme <login@acme.dev>",
        "to": "ana@example.com",
        "subject": "Your login code",
        "text": "Your code is 481 207",
        "priority": "urgent"
    }))
    .send()
    .await?;
200{ "id": "0192f3c1-…", "priority": "urgent" }

Your list stays in your database. Your mail goes out through your SES account. Nothing in between is hidden.

Why Rust

Built in Rust, so the compiler catches what would hurt a send

An email platform runs for months and must never drop a message or send one twice. These are the parts of Mailrust that Rust makes hard to get wrong.

Engine specmailrust v0.3.1
Checked SQL

Every database query is checked against the real schema when the code compiles. A typo in a column name fails the build before it can fail a send.

sqlx query! macros
Memory safety

The engine has zero unsafe blocks. There is no garbage collector, so a large send has no pauses in the middle.

unsafe: 0
One process

One async process runs the API, the send worker, the newsletter waves, webhook delivery, CSV imports and the SES quota sync. Postgres is the queue, so there is no Redis to run.

tokio · SKIP LOCKED
Strict input

The API rejects fields it doesn't know. Send "prority" by mistake and you get an error back, not a silently ignored setting.

deny_unknown_fields
Modern TLS

Connections use rustls, a TLS library written in Rust. There is no OpenSSL in the engine to patch.

rustls
Strict CI

Every change passes Clippy with warnings treated as errors, and cargo-deny checks each dependency against the RustSec advisory list.

clippy -D warnings · cargo-deny
Typed API docs

The OpenAPI reference is generated from the same Rust types the API uses, so the docs match the code.

GET /openapi.json
Everything in it
64

things Mailrust does today

This is the full list for v0.3.1. Nothing here is a roadmap item.

Write

The newsletter editor.

  • Block editor with headings, lists, quotes, images, buttons and dividers
  • Type / to add any block
  • Merge tags with fallbacks, for when a reader has no first name
  • Autosave while you type
  • Desktop and mobile preview
  • Test sends to your team before the real one
  • Image uploads: PNG, JPEG, GIF and WebP up to 5 MB
  • Public archive of past newsletters, served with a strict content policy
  • Postal address and an "ask for first name" switch in settings
  • Delete a draft you no longer need
Audience

Your subscribers, in your database.

  • Contacts with status, first and last name and your own custom fields
  • Tags, to send to part of your list
  • Hosted signup form with double opt-in
  • An embed snippet for the signup form
  • Rate limits per address and per IP on the form
  • The form answers the same for every address, so nobody can probe your list
  • One-link unsubscribe, plus one-click unsubscribe from the inbox (RFC 8058)
  • CSV import of up to 100,000 rows that picks up again after a crash
  • CSV export that defuses spreadsheet formulas
Send safely

The checks between "Send" and the inbox.

  • Waves of 100, 200, 400 and up to 10,000, ten minutes apart
  • Most engaged readers get each newsletter first
  • Send to everyone or to chosen tags, now or on a schedule
  • Checks before every send: verified sender, subject, postal address, size and valid blocks
  • Warns before Gmail's 100 KB clip cuts your newsletter short
  • Auto-pause when bounces pass 2% or complaints pass 0.08%
  • Pause, resume or cancel a send at any point
  • Nobody gets a newsletter twice, even across a restart
  • One block list per account, shared by the newsletter and the API
  • Unsubscribe headers on every newsletter
Sending API

For the email your app sends.

  • POST /emails for one email
  • Batch send of up to 100 emails in one request
  • Scheduled send up to 30 days ahead, with cancel
  • Idempotency keys, so a retry never sends twice
  • Attachments
  • cc, bcc, reply-to, custom headers and tags
  • Priority lanes: urgent, normal and bulk
  • Signed webhooks with retries
  • Webhooks refuse private network addresses (SSRF guard)
  • Open and click tracking
  • OpenAPI reference generated from the code
Deliverability

Your Amazon SES account, handled with care.

  • Each account is its own SES tenant
  • DKIM and custom MAIL FROM records set up for you
  • Domains are checked for you until they verify
  • Send rate follows your SES limits, synced every five minutes
  • Hitting the SES daily cap pauses sending instead of failing emails
  • The last 10% of the daily quota is kept for urgent mail
  • Bounce, complaint, delivery, reject and delay events from SES, signature-checked
  • Tracking links on your own subdomain, with HTTPS set up for you
  • A send that times out is never sent a second time
Measure

Numbers you can trust.

  • Report for each newsletter: delivered, opens, clicks and spam complaints
  • Mail scanner clicks are left out of your open and click rates
  • Clicks for every link in the newsletter
  • Split by mailbox provider, such as Gmail or Outlook
  • Live wave progress and the time of the next wave
  • Searchable log of every email, filtered by priority and date
  • Every time shown in the reader's own time zone
Run it

What it takes on your server.

  • Teams with owners and members, and email invites
  • Passwordless login with magic links
  • Docker image and a production compose file
  • Caddy in front, with automatic HTTPS
  • Nightly Postgres backups
  • A fake sender that writes .eml files, so you can try it without AWS
  • A CLI for accounts, API keys, domains and invites
  • AGPL-3.0 licence
The hard parts

Four things that keep a send from going wrong

Most email trouble starts after you press Send. These are the parts of Mailrust that deal with it, and you can try each one here.

No. 01

It sends in waves, best readers first

A newsletter starts with 100 of your most engaged readers. Each wave doubles, ten minutes after the last one, up to 10,000 at a time. If the first wave goes badly, the rest of your list never sees it.

Wave plan
1002004008001.6k3.2k6.4k10k
8 waves planned for 25,000 readers.
No. 02

It stops itself when mail starts bouncing

Mailrust watches every bounce and complaint SES reports back. Past 2% bounces or 0.08% complaints, it pauses the account before your sender reputation takes the damage. Drag the slider to see where the line is.

Account health
Bounce rate0.6%
0%2% pause line4%
Sending normally. Wave 3 goes out at 14:20.Sending
No. 03

Login codes never wait behind a newsletter

Every email has a lane: urgent, normal or bulk. Urgent mail goes first, and the last 10% of your daily SES quota is kept for it. If a big newsletter uses up the day, your password resets still go out.

Queue
Urgent
Normal
Bulk
Daily SES quotalast 10%: urgent only
No. 04

It checks the newsletter before it lets you send

The Send button stays locked until the newsletter passes every check. Untick one to see what happens.

Ready to send?
All checks pass.
How it works

How an email travels

Your app and your dashboard put email into the same Postgres queue. Mailrust sends it through your SES account and records what happens to each one.

Your app

Calls POST /emails with an API key.

Your editor

You write and press Send in the dashboard.

Mailrust engine

Checks, queues and paces every email.

APIPostgres queuePriority lanesWavesRate limiterBlock list
Your Amazon SES

Signs with your DKIM and sends from your domain.

Inboxes

Gmail, Outlook, iCloud and the rest.

The way back

SES reports every delivery, bounce and complaint. Mailrust checks the signature, updates the block list, pauses the account if a rate gets too high, fills in your reports and tells your app through a signed webhook.

Run it yourself

Run it on your own server

To run Mailrust in production you need a server with Docker, a domain you control and an Amazon SES account. To try it on your laptop you need none of them: until you add SES keys, Mailrust writes each email to a folder as an .eml file.

On your laptop
$ git clone https://github.com/irfanfaraaz/mailrust
$ cd mailrust && cp .env.example .env
$ docker compose up -d postgres
$ cargo run -- migrate
$ cargo run -- serve
No AWS needed
On your server
# Postgres, Mailrust, the dashboard,
# Caddy with HTTPS and nightly backups
$ cd deploy
$ docker compose -f docker-compose.prod.yml \
    --env-file ../.env up -d
One small server
You bring

A server, a domain and an Amazon SES account. New SES accounts start in a sandbox, so ask AWS for production access early.

You set up once

DNS records for DKIM and MAIL FROM. Mailrust gives you the exact records and checks them until they pass.

You keep

Upgrades are a pull and a migrate. Backups run every night on their own.

Built in the open

Every release, and what isn't in it yet

Mailrust is new. Here is exactly what has shipped, and the things it does not do today, so you can decide with the full picture.

What shipped
  • 0.3.1Oct 14, 2026

    Newsletter reports with spam complaints, local time zones everywhere, toasts for every action, and a round of fixes.

  • 0.3.0Oct 14, 2026

    Newsletters: the block editor, waves, checks before send, reports, the public archive and image uploads.

  • 0.2.0Oct 13, 2026

    The dashboard and your audience: contacts, tags, signup form, CSV import and export, unsubscribe.

  • 0.1.0Oct 13, 2026

    The sending API and the engine: queue, SES sender, priority lanes, webhooks, tracking and auto-pause.

AGPL-3.0See it on GitHub >
What it doesn't do yet
  • Paid subscriptions for your newsletter
  • Automated email sequences, such as a welcome series
  • Saved email templates on the server. Your code sends the HTML.
  • Receiving email
  • Podcasts, ads or a website builder
FAQ

Questions people ask first

What do I need to run it?

A server with Docker, a domain you control and an Amazon SES account. Postgres is the only other service, and the production compose file starts it for you along with the dashboard, Caddy and nightly backups.

Do I need my own Amazon SES account?

Yes. Mailrust sends through your SES account, so the sending reputation, the limits and the bill are yours. To try it locally you don't need AWS at all, because the fake sender writes each email to a file.

Can I use only the API, or only the newsletter?

Yes. Each door works on its own. If you use both, they share one block list and one queue, and urgent API mail always goes ahead of a newsletter.

How is this different from Resend or beehiiv?

Resend is a sending API and beehiiv is a newsletter platform, and both run on their own servers. Mailrust does both jobs in one engine that runs on yours, with your SES account and your database.

What happens if a send goes badly?

The first wave is small and goes to your most engaged readers. If bounces pass 2% or complaints pass 0.08%, Mailrust pauses the account and the newsletter stops. When you resume, nobody who already got it gets it again.

Can I bring my subscribers with me?

Yes. Import a CSV of up to 100,000 rows. The import runs in the background and picks up where it stopped if the server restarts. You can export your contacts as CSV at any time.

What does the AGPL licence mean for me?

You can run, change and use Mailrust for free, for any purpose. If you change the code and offer it to other people as a network service, you have to share your changes under the same licence.

Your list. Your server. Your send button.

Clone the repo and send your first email from your laptop. You don't need an AWS account to start.

Self-host Mailrust >Read the docs
MailrustOpen source email, built with Rust and Postgres.
GitHubDocsChangelogAGPL-3.0