Self-hosted, open-source, Resend-compatible transactional + marketing email.
Freesend is an email platform you run on your own server, speaking a Resend-compatible REST API — point an existing Resend SDK at your Freesend instance by changing only the base URL and key prefix from re_ to fs_ — and providing a full dashboard for emails, domains (DKIM/SPF/DMARC), API keys, webhooks, templates, and broadcasts. Delivery is bring-your-own SMTP: configure credentials for any provider (Amazon SES, Postmark, Mailgun, self-hosted Postfix) and Freesend DKIM-signs and relays through it. No SaaS subscription, no per-email markup — just Docker + your infrastructure.
- Transactional emails with HTML + text bodies
- Batch sends (up to 100 emails per request)
- Scheduled sending (future delivery via natural language or ISO dates)
- Attachments (base64 inline or remote URL)
- Custom headers and tags for categorization
- Idempotent requests (24h TTL,
Idempotency-Keyheader) - Per-API-key rate limiting with token bucket
- Domain registration and verification
- Automatic RSA-2048 DKIM key generation
- Per-domain DNS records (DKIM, SPF, DMARC) with checklist status
- Hard-block sending from unverified domains
- Per-domain or team-default SMTP credentials
- MJML templates (responsive, SQL-backed)
- Logic-less Handlebars templating with variable support
- Template versioning and drafts
- Dynamic rendering at send time
- Outbound webhook delivery with svix-style signatures + retries
- Inbound provider feedback: Amazon SES (SNS, cert-verified) + a generic token-authed endpoint any provider (Postmark, Mailgun, …) can post to
- Bounce/complaint/delivered/opened/clicked event collection (bounces & complaints auto-suppress)
- Webhook retry with exponential backoff
- Contact audiences with CSV import
- Suppression list (bounces, complaints, unsubscribes)
- Broadcast sends (one call, many recipients)
- Per-contact unsubscribe tracking
- Event timeline per email (sent, delivered, bounced, complained, opened, clicked)
- Open tracking (pixel beacon)
- Click tracking (rewritten links)
- Email + event logs searchable via dashboard
- PostgreSQL-only required stateful service (no Redis by default)
- Postgres-backed job queue (pg-boss) for sends, retries, webhooks
- Optional Redis/BullMQ for high-throughput deployments
- AES-256-GCM envelope encryption of DKIM keys and SMTP passwords
- Single Docker image with role dispatch (
web,worker,migrate) - Auth.js v5 dashboard authentication
- Bearer API keys (hashed HMAC-SHA256, one-time display)
- Multi-team isolation (shared DB, application-enforced scoping)
# 1. Clone the repo
git clone https://github.com/freesendapp/freesend
cd freesend
# 2. Create .env and generate secrets
cp .env.example .env
npm run gen:keys # outputs three secrets; paste them into .env
# 3. Set a strong Postgres password
# Edit .env and set: POSTGRES_PASSWORD=your-strong-password
# 4. Start the full stack (postgres + web + worker)
docker compose up -d
# or with `just` installed:
# just bootstrap
# 5. Open the dashboard and create the first admin
open http://localhost:3000 # redirects to /setup on first runnpm install
npm run gen:keys # paste secrets into .env
docker compose up -d postgres # start just the database
npm run db:migrate # apply migrations
npm run dev # WORKER_IN_PROCESS=true runs the worker in-processOnce you've created an admin account, add SMTP credentials, verify a domain, and create an API key from the dashboard. Then:
curl -X POST http://localhost:3000/api/v1/emails \
-H "Authorization: Bearer fs_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"from": "noreply@yourdomain.com",
"to": "user@example.com",
"subject": "Welcome",
"html": "<p>Hello from Freesend!</p>"
}'import { Resend } from 'resend'
const resend = new Resend('fs_live_xxx', { baseUrl: 'https://mail.example.com/api/v1' })
await resend.emails.send({
from: 'noreply@yourdomain.com',
to: 'user@example.com',
subject: 'Hi',
html: '<p>Hello</p>',
})| Env var | Purpose | Example |
|---|---|---|
POSTGRES_PASSWORD |
Postgres credentials (set in .env, docker-compose expands it) | your-strong-password |
APP_URL |
Public URL (for links, DNS hints, webhook callbacks) | https://mail.example.com |
AUTH_SECRET |
Auth.js session secret (≥32 bytes, from npm run gen:keys) |
...base64... |
FREESEND_ENCRYPTION_KEY |
32-byte base64 master key for DKIM + SMTP secret encryption. Loss = unrecoverable; leak = total compromise. | ...base64... |
API_KEY_PEPPER |
Server-held pepper for API key hashing (from npm run gen:keys) |
...base64... |
QUEUE_DRIVER |
pg (default, no Redis) or redis (requires REDIS_URL) |
pg |
ALLOW_SIGNUP |
Allow public signup (false default) |
false |
SETUP_TOKEN |
Optional token protecting /setup on internet-exposed hosts |
(unset) |
See docs/configuration.md for the full environment reference.
- ARCHITECTURE.md — Design decisions, data model, API surface, security, roadmap, deployment patterns
- docs/configuration.md — Every environment variable and its constraints
- docs/deployment.md — Docker/compose, scaling (worker, web replicas, Redis opt-in), key rotation, backups, migrations, retention policies
- docs/api.md — REST API reference (Resend-compatible endpoints, request/response shapes, auth)
- docs/sending.md — Domain setup + DKIM/SPF/DMARC, sending (HTML/text, attachments, scheduling, batch, idempotency), templates (MJML, Handlebars), tracking
- docs/webhooks.md — Outbound webhook signing + verification, inbound provider feedback (SES SNS + generic token endpoint)
- docs/broadcasts.md — Audiences, contacts, CSV import, broadcasts, suppression, unsubscribe
- Next.js 16 (App Router) + React 19 + TypeScript — API and dashboard in one app
- PostgreSQL 17 — the only required stateful service
- Drizzle ORM with committed SQL migrations
- pg-boss — Postgres-backed job queue (sends, retries, webhooks)
- nodemailer — SMTP delivery + DKIM signing
- Auth.js v5 — dashboard authentication
- AES-256-GCM — envelope encryption for secrets at rest
You run one Docker image across one or more containers (web, worker, migrate roles). The web container listens for API + dashboard requests, validates with your API keys, and queues emails into Postgres. The worker pulls jobs, renders templates, DKIM-signs via nodemailer, relays through your BYO SMTP, and records delivery status. Webhooks from your mail provider update the event timeline and trigger customer webhooks. See ARCHITECTURE.md for the full design.