The modern, self-hosted email platform for indie hackers and creators. Marketing campaigns, transactional emails, and AI-powered automation—all in a single binary.
- Single binary deployment — No PHP, no external database. Just download and run.
- SQLite with WAL mode — Zero configuration, handles 100k+ subscribers.
- Marketing + Transactional — One platform for newsletters and application emails.
- AI-native MCP integration — Build email sequences by talking to Claude.
- Built-in backup system — One-click download or automated S3 backups.
- Full source code included — No obfuscation, modify anything you need.
- SMTP ingress server — Drop-in replacement for any SMTP setup. Point Postfix, your app, or any mail client at Outlet.
# Download the binary for your platform
wget https://outlet.sh/download/outlet-linux-amd64
chmod +x outlet-linux-amd64
# Set required environment variables
export JWT_SECRET="your-secret-key-here"
export JWT_REFRESH_SECRET="your-refresh-secret-here"
export ADMIN_EMAIL="admin@example.com"
export ADMIN_PASSWORD="your-admin-password"
# Run it
./outlet-linux-amd64Open http://localhost:9888 and log in with your admin credentials.
Create a .env file:
# .env
JWT_SECRET=your-secret-key-here
JWT_REFRESH_SECRET=your-refresh-secret-here
ENCRYPTION_KEY=your-32-byte-hex-key
PRODUCTION_MODE=true
APP_DOMAIN=mail.yourdomain.com
APP_BASE_URL=https://mail.yourdomain.comCreate compose.yaml:
services:
outlet:
image: ghcr.io/outlet-sh/outlet:latest
ports:
- "443:443"
- "80:80"
- "587:587"
volumes:
- ./data:/app/data
env_file: .env
restart: alwaysdocker compose up -dCreate /etc/systemd/system/outlet.service:
[Unit]
Description=Outlet.sh Email Platform
After=network.target
[Service]
Type=simple
User=outlet
WorkingDirectory=/opt/outlet
ExecStart=/opt/outlet/outlet
Restart=always
RestartSec=5
EnvironmentFile=/opt/outlet/.env
[Install]
WantedBy=multi-user.targetsudo systemctl enable outlet
sudo systemctl start outlet- CSV import/export
- Custom fields and tags
- Segmentation and filtering
- Double opt-in flow
- Automatic bounce/complaint handling
- GDPR compliance tools
- Rich text and HTML editor
- Template library
- Broadcast and scheduled campaigns
- RSS-to-email campaigns
- Open/click tracking
- Per-campaign analytics
- REST API for application emails
- Template-based messages with merge fields
- Dedicated sending queue (separate from marketing)
- Per-template analytics
- Webhook callbacks for delivery events
- Auto-generated SDKs (TypeScript, Python, Go, PHP)
Send emails through Outlet using standard SMTP protocol — a 100% drop-in replacement for any existing SMTP setup. Point your application, Postfix relay, or any mail client at Outlet and get all the platform features automatically.
Authentication:
- Username: Your brand slug (e.g.,
my-company) - Password: Your brand's API key
The username must match the brand slug associated with your API key. This enforces brand isolation and prevents cross-brand sending.
Custom Headers for advanced control:
| Header | Purpose | Example |
|---|---|---|
X-Outlet-List |
Associate with a list | newsletter |
X-Outlet-Tags |
Comma-separated tags | welcome,onboarding |
X-Outlet-Template |
Use a template | order-confirmation |
X-Outlet-Type |
marketing or transactional |
transactional |
X-Outlet-Track |
opens,clicks or none |
opens,clicks |
X-Outlet-Meta-* |
Custom metadata | X-Outlet-Meta-OrderID: 12345 |
Example with swaks:
swaks --to user@example.com \
--from hello@yourdomain.com \
--server mail.yourdomain.com:587 \
--auth PLAIN \
--auth-user my-brand \
--auth-password "your-api-key" \
--tls \
--header "X-Outlet-List: customers" \
--header "X-Outlet-Tags: purchase,vip" \
--body "Thanks for your order!"Example with Python:
import smtplib
from email.mime.text import MIMEText
msg = MIMEText("<h1>Welcome!</h1><p>Thanks for signing up.</p>", "html")
msg["Subject"] = "Welcome to our platform"
msg["From"] = "hello@yourdomain.com"
msg["To"] = "user@example.com"
msg["X-Outlet-List"] = "newsletter"
msg["X-Outlet-Tags"] = "welcome,new-user"
with smtplib.SMTP("mail.yourdomain.com", 587) as server:
server.starttls()
server.login("my-brand", "your-api-key")
server.send_message(msg)Configuration via Environment Variables (recommended for Docker):
SMTP_ENABLED=true # Enable SMTP server
SMTP_PORT=587 # Standard submission port (default: 587)
SMTP_ALLOW_INSECURE_AUTH=true # Allow AUTH without TLS (for testing/internal use)
SMTP_TLS_CERT=/path/to/cert.pem # TLS certificate (optional)
SMTP_TLS_KEY=/path/to/key.pem # TLS private key (optional)Docker Compose example with SMTP:
Create a .env file:
# .env
JWT_SECRET=your-secret-key-here
JWT_REFRESH_SECRET=your-refresh-secret-here
ENCRYPTION_KEY=your-32-byte-hex-key
PRODUCTION_MODE=true
APP_DOMAIN=mail.yourdomain.com
APP_BASE_URL=https://mail.yourdomain.com
# SMTP Configuration
SMTP_ENABLED=true
SMTP_PORT=587
SMTP_ALLOW_INSECURE_AUTH=false
SMTP_TLS_CERT=/app/certs/cert.pem
SMTP_TLS_KEY=/app/certs/key.pemCreate compose.yaml:
services:
outlet:
image: ghcr.io/outlet-sh/outlet:latest
ports:
- "443:443"
- "80:80"
- "587:587" # SMTP submission port
volumes:
- ./data:/app/data
- ./certs:/app/certs
env_file: .env
restart: alwaysConfiguration via YAML (for direct binary):
# In etc/outlet.yaml
SMTP:
enabled: "true"
port: "587"
tlscert: /path/to/cert.pem
tlskey: /path/to/key.pem
allowinsecureauth: "false" # Set to "true" only without TLSTesting with curl:
curl -v --url 'smtp://mail.yourdomain.com:587' \
--mail-from 'hello@yourdomain.com' \
--mail-rcpt 'recipient@example.com' \
--user 'my-brand:your-api-key' \
--upload-file - <<< "Subject: Test Email
This is a test email sent via Outlet SMTP."Note: For production use, always configure TLS certificates. The SMTP_ALLOW_INSECURE_AUTH=true option should only be used for testing or when running behind a TLS-terminating proxy.
- Autoresponder sequences
- Trigger rules (tag added, link clicked, date-based)
- Transactional-to-marketing handoff
Outlet.sh includes a built-in MCP server, enabling AI assistants like Claude to manage your email operations:
You: "Create a 7-day welcome sequence for my SaaS. Day 1 introduces
the product, Day 3 covers the main feature, Day 5 shares a case
study, Day 7 offers a discount."
Claude: [Creates sequence, adds emails with proper delays, writes
copy, sets triggers — all via MCP tools]
MCP capabilities include:
- List and subscriber management
- Campaign creation and scheduling
- Sequence building with email content
- Analytics queries and insights
- Transactional email sending
- Dashboard download — One-click database backup
- S3 automated backup — Scheduled backups with retention policies
- CLI backup —
outlet backuporoutlet backup --s3 - Safe live backups — Uses SQLite backup API (no corruption)
- Easy restore — Replace file and restart, or use dashboard
Configuration uses environment variables. Create a .env file or set them directly:
| Variable | Description |
|---|---|
JWT_SECRET |
Secret key for access tokens |
JWT_REFRESH_SECRET |
Secret key for refresh tokens |
ADMIN_EMAIL |
Super admin email (created on first startup) |
ADMIN_PASSWORD |
Super admin password |
| Variable | Description |
|---|---|
PRODUCTION_MODE |
Set to true for HTTPS/Let's Encrypt |
BEHIND_PROXY |
Set to true when a load balancer or ingress terminates TLS: plain HTTP on port 8888 with the embedded UI, no Let's Encrypt |
APP_DOMAIN |
Your domain (e.g., mail.yourdomain.com) |
APP_BASE_URL |
Full URL (e.g., https://mail.yourdomain.com) |
| Variable | Description |
|---|---|
DATABASE_PATH |
SQLite path (default: ./data/outlet.db) |
ENCRYPTION_KEY |
32-byte hex key for credential encryption |
ANTHROPIC_API_KEY |
Claude AI integration |
STRIPE_SECRET_KEY |
Payment processing |
STRIPE_WEBHOOK_SECRET |
Stripe webhook verification |
| Variable | Description |
|---|---|
SMTP_ENABLED |
Set to true to enable SMTP server |
SMTP_PORT |
SMTP port (default: 587) |
SMTP_TLS_CERT |
Path to TLS certificate file |
SMTP_TLS_KEY |
Path to TLS private key file |
SMTP_ALLOW_INSECURE_AUTH |
Set to true to allow AUTH without TLS (testing only) |
Outlet.sh works best with Amazon SES for cost-effective sending (~$0.10 per 1,000 emails).
- Create SES credentials in AWS Console → SES → SMTP Settings
- Verify your domain in SES → Verified Identities
- Configure in Outlet.sh dashboard → Settings → Email Provider
- Request production access if still in SES sandbox
Outlet.sh provides a REST API with auto-generated clients:
import { Outlet } from '@outlet/sdk';
const outlet = new Outlet('your-api-key', 'https://mail.yourdomain.com');
await outlet.emails.sendEmail({
to: 'user@example.com',
template_slug: 'welcome-email',
variables: {
name: 'John',
activation_link: 'https://...'
}
});import "github.com/outlet-sh/outlet/sdk/go"
client := outlet.NewClient("your-api-key", "https://mail.yourdomain.com")
err := client.Emails.SendEmail(ctx, outlet.SendEmailRequest{
To: "user@example.com",
TemplateSlug: "welcome-email",
Variables: map[string]string{
"name": "John",
"activation_link": "https://...",
},
})┌───────────────────────────────────────────────────────────────────┐
│ Single Binary │
├───────────────────────────────────────────────────────────────────┤
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────────┐ │
│ │ SvelteKit │ │ go-zero │ │ SMTP │ │ MCP Server │ │
│ │ Frontend │ │ REST API │ │ Ingress │ │ (AI Integration)│ │
│ └───────────┘ └───────────┘ └───────────┘ └───────────────┘ │
├───────────────────────────────────────────────────────────────────┤
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────────────┐ │
│ │ Email Workers │ │ Rules Engine │ │ Webhook Dispatcher │ │
│ └───────────────┘ └───────────────┘ └───────────────────────┘ │
├───────────────────────────────────────────────────────────────────┤
│ SQLite (WAL Mode) │
│ ./data/outlet.db │
└───────────────────────────────────────────────────────────────────┘
- Frontend: Embedded SvelteKit 5 static build
- REST API: go-zero framework with auto-generated handlers
- SMTP Ingress: Standard SMTP server (port 587) for drop-in integration
- Database: SQLite with WAL mode, auto-migrations on startup
- Workers: Background email sending with rate limiting
- MCP: Model Context Protocol server for AI assistants
- Go 1.21+
- Node.js 20+
- pnpm
# Clone the repository
git clone https://github.com/outlet-sh/outlet
cd outlet
# Install dependencies
go mod download
cd app && pnpm install && cd ..
# Create .env file
cp .env.example .env
# Edit .env with your values
# Run backend (uses air for hot reload)
make run
# In another terminal, run frontend
cd app && pnpm dev# Build Go binary
make build
# Build frontend
cd app && pnpm build
# The binary embeds the frontend automatically# After modifying outlet.api
make gen # Regenerates handlers, types, and TS client
# After modifying SQL queries
make sqlc-gen # Regenerates type-safe Go from SQL
# All code generation
make gen-all# Go tests
go test ./...
go test ./internal/services/emailval/ # Single package
go test -run TestName ./internal/... # Single test
# Frontend tests
cd app && pnpm testOutlet is open source under the AGPL-3.0 License.
- Self-host freely for personal or commercial use
- Modifications must be shared under AGPL if distributed or offered as a service
- Commercial licensing available for companies needing different terms
- Documentation: https://outlet.sh/docs
- Issues: https://github.com/outlet-sh/outlet/issues
- Email: support@outlet.sh
Built with care for indie hackers who value simplicity, ownership, and modern tooling.