Self-hosting a cold email platform
What it takes to self-host cold email software: one install command, where your data lives, what it needs (no cloud account), backups, updates and warmup.
Self-hosting means running the whole cold email platform on machines you control, so mailbox credentials, contacts and message content stay with you. AY Inbox's self-host is the same code as the hosted service and installs with one command from published release images. It needs Docker, no cloud account and no paid license.
A self-hosted cold email platform is the full stack behind outreach (the API, the workers that send and sync mail, the database, the tracking service and the dashboard) running on your own server instead of a vendor's. Your mail still leaves through your own mailbox providers. What moves is where the credentials, the contacts and the stored messages live.
Hosted versus self-hosted
The hosted service and a self-host run the same application code from the same release images. What changes is who operates it.
| Hosted | Self-hosted | |
|---|---|---|
| Who runs the servers | AY Inbox | You |
| Where data sits | AY Inbox's infrastructure | Your disk, or stores you point it at |
| Updates | Applied for you | One button in the admin panel, or docker compose pull |
| Plans and billing | Plans set what each workspace can use | None by default: every feature is available |
A self-host runs with no billing provider by default, so no plan gates a feature. The per-mailbox sending caps still apply. It needs no AWS account, no Stripe, no Kafka and no hosted control plane. The default stack runs on local, open-source pieces (Postgres, Redis, NATS and filesystem blob storage), and each external dependency is an environment variable you can switch later. AI features need a model provider you configure (AI_PROVIDER, including a local Ollama); leave every AI variable unset and AI endpoints answer 503 while everything else works, with warmup drawing on its built-in conversation library.
Install
curl -fsSL https://github.com/walidboulanouar/ayinbox/releases/latest/download/install.sh | shNo clone and no compiler. The script checks Docker, creates one directory (/opt/ayinbox by default), generates every secret, pulls the pinned release images, starts the stack and prints the link that claims your instance. It pins the newest release in .env, never latest, and checks the pulled images against the digests published with the release before starting anything.
What it needs:
| Requirement | Notes |
|---|---|
| Linux or macOS, amd64 or arm64 | On Windows, run it inside WSL |
| Docker 20.10+ with Compose v2 | The script offers to install Docker on Linux, never quietly |
| About 2 GB of disk, 4 GB of RAM | Nothing is built on the machine |
| Free ports | 8080, 5173, 5174, and 3000, 4000, 8090 with every component |
Add --wizard and it asks nine steps of questions first: where it lives, the hostname and how it is served (plain HTTP, a bundled Caddy with automatic HTTPS, or behind your own proxy), where the data sits, the encryption keys, retention, backups, the first owner, which components to run, and a final review. Pressing enter through every default gives the same install as the one-liner.
Every answer is also a flag and an AYINBOX_* variable, so Ansible, cloud-init or an agent can run it unattended with --yes. --dry-run prints the files it would write and --print-env prints the .env, both without changing anything. The script is idempotent: a second run adopts the existing .env, never regenerates a secret and never moves the data root. See Install for every flag.
Where data lives and for how long
Every store is one variable in .env. An install from the script puts all of them under one data root:
| Store | Holds |
|---|---|
| Postgres | Workspaces, users, mailboxes (credentials sealed), contacts, campaigns, the audit trail |
| Blobs | Message bodies, attachments, avatars and images |
| NATS, Redis, worker and updater state | Event bus state, cache and cursors a fresh container rebuilds |
Only Postgres and the blobs hold anything you cannot rebuild. Postgres, Redis and blob storage can each point at an external service instead.
Two keys seal what matters: CREDENTIALS_ENCRYPTION_KEY (mailbox credentials) and KMS_LOCAL_MASTER_KEY (the per-workspace data keys that open everything else). Both are unrecoverable. A database backup without them restores an instance whose mailboxes authenticate against nothing. The wizard prints both, writes them to keys-backup.txt, and waits until you confirm you have a copy somewhere else.
Retention is set in the admin panel under Instance > Configuration > Settings, with these defaults:
| Data | Default |
|---|---|
| Initial mailbox import | 90 days back, up to 5,000 messages per mailbox |
| Open and click logs | 365 days |
| Form funnel events | 180 days |
| Audit log | 90 days |
| Warmup mail in mailboxes | 30 days |
| Warmup records | 365 days |
A minimal preset sets opens, forms and audit to 30 days, warmup mail to 7 and warmup records to 30. Data control covers every window.
There is no telemetry. Error reporting and usage analytics send nothing until you configure a destination, and the release update check, off unless you turn it on, is the one outbound call the instance makes on its own.
Backups and moving an instance
ayinboxctl backup writes one bundle with the three things that only restore together: a pg_dump of the database, the blob root when blobs are on the filesystem, and the encryption keys unless you pass --no-keys. The wizard can schedule it with a systemd timer and a retention count. Keep a copy off the machine: a backup that only exists on the host it protects is not one.
To move the whole instance:
- Install AY Inbox on the new host.
- Put the old encryption keys in the new
.envand recreate the containers. - Run
ayinboxctl restorewith the bundle. It refuses to continue when the bundle's keys differ from the destination's. - Run
ayinboxctl statusand confirm mailboxes are connected rather than asking to reconnect.
To move one workspace rather than the instance, use workspace export and import, which re-seals its secrets for the destination's keys.
Scaling out
One machine runs everything. When it is not enough, add workers on machines you own. On the instance, ayinboxctl fleet join-token issues a token. On the new machine (Docker and systemd), one join.sh command enrols it as a worker or a consumer, writes its config and installs a service and an update timer.
Nothing connects back to the machine: it needs no inbound port, no SSH key and no cloud account. A node pulls everything, including the version it should run, from its heartbeat. Once workers run on more than one machine, blob storage has to be S3-compatible (MinIO, R2, B2 or AWS), because a worker on another host cannot read a body written to the control plane's disk. Split deployment covers running the control plane, fleet and storage in separate places.
Warmup on a self-host
Warmup works on a self-host and draws its partners from the other mailboxes on the same instance, so a small instance has a small pool. The AY Inbox Cloud guide describes linking a self-host to a hosted warmup pool. That link is switched off in this build: its routes are not registered and its settings are hidden, so nothing leaves your instance for warmup.
Whatever the pool size, the sending defaults are the same as hosted: warmup starts at 10 emails a day per mailbox and ramps by 1 a day to 40, and campaigns default to 50 emails a day per mailbox with 600 seconds between sends.
Updates and keeping current
On an install from the script, the version pill in the admin panel shows when a newer release exists, once the update check is on. Update and restart writes the new tag into .env, pulls and recreates only the containers whose image changed. Nothing is built. By hand:
docker compose -p ayinbox pull && docker compose -p ayinbox up -dEach application container gets 60 seconds to finish its work in progress, and anything still unfinished is picked up by the new containers. Upgrade the backend first and let its migrations complete; worker nodes follow the version the backend hands them. See Updates and the self-hosting guide for building from source.
Frequently asked questions
Is AY Inbox open source?
Yes. AY Inbox is released under the Apache License 2.0, and the self-host runs the same published release images as the hosted service.
What do I need to self-host AY Inbox?
A Linux or macOS machine on amd64 or arm64 (Windows through WSL) with Docker 20.10 or newer and Compose v2, about 2 GB of disk and 4 GB of RAM. No cloud account, no paid license and no GitHub account are needed.
Does a self-hosted instance send data to anyone?
Not by default. There is no telemetry, and error reporting and usage analytics are off until you configure a destination. The release update check is the one outbound call the instance can make on its own, and the installer leaves it off unless you turn it on.
How do I back up or move a self-hosted instance?
ayinboxctl backup writes one bundle with the database, filesystem blobs and, unless you exclude them, the encryption keys. ayinboxctl restore on another host replays it and refuses to continue when the destination's keys do not match the bundle's.
Does email warmup work on a self-hosted instance?
Yes, against the other mailboxes on the same instance. Linking a self-host to a hosted warmup pool is switched off in this build, so a small instance has a small warmup pool.
Running outbound from Claude Code
Run cold email outreach from Claude Code with the AY Inbox MCP server or CLI: import leads, draft and check a campaign, triage replies and draft answers.
Glossary
Plain-English definitions of cold email and deliverability terms, from SPF and DKIM to inbox placement, warmup, and complaint rate.