To send newsletters and campaigns with OwnMailServer, run Listmonk for your contacts, lists and campaigns, and point its SMTP settings at us: smtp.ownmailserver.com, port 587, STARTTLS, with an API key as the password. Then slow Listmonk down to our 10 messages a second, cap it at your daily warm-up limit, and forward our bounce webhooks to Listmonk. It takes about an hour.
OwnMailServer is a sending service: it delivers email, handles bounces and complaints, warms up your sending and watches your reputation. It doesn't store contact lists or edit campaigns. Listmonk does, and it's free, open source and self-hosted, so your subscriber list stays in your own database.
What you need before you start
- An approved OwnMailServer account with a verified sending domain. Listmonk's From address must be on that domain, or we refuse the email with
550. The API docs list the DNS records. - An API key, created in your dashboard under API keys. It's the SMTP password.
- A server with Docker to run Listmonk. A small VPS is enough for most lists.
- A domain name for Listmonk with HTTPS, such as
lists.example.com. Unsubscribe links, the subscription form and the archive all point to it, so readers must be able to reach it. - A list you can stand behind: people who signed up and expect your email. We review every account's list source, and bought or scraped lists get accounts paused quickly through bounces and complaints.
Step 1: Install Listmonk with Docker
Listmonk's official Docker Compose file starts Listmonk and its PostgreSQL database:
mkdir listmonk && cd listmonk
curl -LO https://github.com/knadh/listmonk/raw/master/docker-compose.yml
docker compose up -d
Listmonk listens on port 9000. Open http://<your-server>:9000 and create the Super Admin user. As of October 2026, the current stable release is v6.2.0, with v7 in beta. The compose file uses the stable image.
Put Listmonk behind a reverse proxy with HTTPS (nginx, Caddy or Traefik) on lists.example.com before you send anything. Then, in Settings → General:
- Root URL:
https://lists.example.com. Listmonk builds unsubscribe and tracking links from it. Leaving it onlocalhostbreaks every unsubscribe link you send. - 'From' emails: an address on your verified domain, such as
Example News <news@example.com>.
Step 2: Connect Listmonk to OwnMailServer over SMTP
In Settings → SMTP, edit the first server (or add one) and enter:
| Listmonk field | Value |
|---|---|
| Host | smtp.ownmailserver.com |
| Port | 587 |
| Auth protocol | LOGIN (PLAIN also works) |
| Username | ownmailserver (we accept any value) |
| Password | Your API key |
| TLS | STARTTLS |
| Skip TLS verification | Off |
| Max. connections | 2 |
| Retries | 2 |
Save, then use Send e-mail on the SMTP settings page to send yourself a test. If the test fails:
535: the password isn't a valid API key.550: the From address isn't on a verified domain, or the account is still pending approval.- Connection timeout: your server's firewall or hosting provider is blocking outbound port 587.
The SMTP section of our API docs lists every reply code.
Step 3: Match Listmonk's speed to our limits
Listmonk's defaults are built to send as fast as the mail server allows: 10 workers, each sending up to 10 messages a second. That's up to 100 a second. We accept 10 a second per account and answer the rest with a temporary 450. Listmonk counts those as errors and pauses the campaign once there are too many.
In Settings → Performance, set:
| Setting | Value | Why |
|---|---|---|
| Concurrency | 2 |
Two workers sending at once. |
| Message rate | 4 |
2 × 4 = 8 messages a second, under our 10. |
| Batch size | 1000 |
Fine as is; it only controls how many subscribers Listmonk reads from its database at a time. |
| Maximum error threshold | Leave the default | A paused campaign is a useful signal that something is wrong. |
At 8 a second, Listmonk sends about 28,000 emails an hour. If you need more, ask us to raise your account's rate, then raise Message rate to match.
Stay under your warm-up cap
A new account can only reach a limited number of recipients in any 24 hours. The cap starts at a few hundred and grows about 25% after each clean day. Your dashboard shows the current number, and our warm-up guide explains how it grows. Past the cap, we answer with 450 until the window clears.
Listmonk can hold to that number itself. In Settings → Performance, turn on Enable sliding window limit and set:
- Duration:
24h - Max. messages: your current daily cap
Raise Max. messages as your cap grows. A campaign bigger than the cap then goes out over several days instead of stopping halfway. Transactional mail sent through the same account counts toward the same cap, so leave some room for it.
Step 4: Keep one-click unsubscribe working
Gmail and Yahoo require bulk senders to offer one-click unsubscribe (RFC 8058) and to honour it within two days. See our guide to the Gmail and Yahoo sender requirements.
Listmonk handles this for you. In Settings → Privacy, keep Include List-Unsubscribe header on. Listmonk then adds List-Unsubscribe and List-Unsubscribe-Post headers that point at your Listmonk server, and records the unsubscribe on the subscriber when a reader clicks.
Here's how that works with OwnMailServer:
- When an email already has its own
List-Unsubscribeheader, we don't add ours, so readers see exactly one Unsubscribe button. - We treat the email as marketing. If someone unsubscribed through one of our own links in the past, we won't send them Listmonk campaigns, but they still get your receipts and password resets.
- Our DKIM signature covers both unsubscribe headers. Gmail and Yahoo only show the button when it does.
Keep your Root URL on public HTTPS. A one-click unsubscribe is a POST from Gmail's servers to that URL, and it fails if Listmonk can't be reached.
Step 5: Send bounces and complaints back to Listmonk
We already protect your sending: hard bounces and spam complaints go on your suppression list automatically. If Listmonk sends to a suppressed address, we drop that email. It isn't sent, isn't charged and doesn't count toward your cap. Your reputation is safe even if Listmonk never hears about the bounce.
Your Listmonk lists still go stale, though, and your campaign numbers include addresses that will never receive anything. To keep them clean, pass our webhook events to Listmonk's bounce endpoint.
In Listmonk:
- In Settings → Bounces, turn on Enable bounce processing and Enable bounce webhooks.
- Set the bounce actions. A sensible setup: Hard, count 1, Blocklist. Complaint, count 1, Blocklist. Soft, count 2, None.
- In Users, create a user of type API whose role can post bounces, and copy its token.
In OwnMailServer: add a webhook in your dashboard for email.bounced and email.complained, pointing at the small relay below. Copy the signing secret it shows you once.
The relay checks our signature, then posts each event to Listmonk. It uses only Node.js built-ins (Node 18 or later):
// relay.mjs: OwnMailServer webhooks -> Listmonk bounces
import { createServer } from "node:http";
import { createHmac, timingSafeEqual } from "node:crypto";
const SECRET = process.env.OMS_WEBHOOK_SECRET; // whsec_... from the OwnMailServer dashboard
const LISTMONK = process.env.LISTMONK_URL; // https://lists.example.com
const AUTH = "Basic " + Buffer.from(`${process.env.LISTMONK_API_USER}:${process.env.LISTMONK_API_TOKEN}`).toString("base64");
// Standard Webhooks signature: base64 HMAC-SHA256 of "id.timestamp.body".
function verified(raw, h) {
const id = h["webhook-id"], ts = h["webhook-timestamp"], sigs = h["webhook-signature"] ?? "";
if (!id || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const key = Buffer.from(SECRET.replace(/^whsec_/, ""), "base64");
const expected = Buffer.from(createHmac("sha256", key).update(`${id}.${ts}.${raw}`).digest("base64"));
return sigs.split(" ").some((s) => {
const sig = Buffer.from(s.replace(/^v1,/, ""));
return sig.length === expected.length && timingSafeEqual(sig, expected);
});
}
createServer((req, res) => {
let raw = "";
req.on("data", (chunk) => (raw += chunk));
req.on("end", async () => {
if (!verified(raw, req.headers)) return res.writeHead(401).end();
const { type, data } = JSON.parse(raw);
// Our "block" bounces (refused by policy) aren't dead addresses, so Listmonk gets them as soft.
const kind = type === "email.complained" ? "complaint"
: type === "email.bounced" ? (data.bounce?.type === "hard" ? "hard" : "soft") : null;
if (kind) {
const r = await fetch(`${LISTMONK}/webhooks/bounce`, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: AUTH },
body: JSON.stringify({ email: data.recipient, source: "ownmailserver", type: kind,
meta: { email_id: data.email_id, diagnostic: data.bounce?.diagnostic } }),
});
if (!r.ok) return res.writeHead(502).end(); // a non-2xx reply makes us retry later
}
res.writeHead(200).end();
});
}).listen(8787);
Run it next to Listmonk, behind the same HTTPS proxy, for example at https://lists.example.com/oms-webhook. We only deliver webhooks to public HTTPS addresses. If Listmonk is down, the relay replies 502 and we retry for up to 24 hours.
Step 6: Send your first campaign
- Lists: create a list. Choose Double opt-in for anything people join through a form. It costs a few signups and saves you from typos, fake addresses and spam traps.
- Subscribers → Import: upload a CSV of people who agreed to hear from you. If you're moving from another tool, remove its unsubscribed and bounced contacts first.
- Campaigns → New: write the email and pick the list. The From address must be on your verified domain.
- Send a test to yourself and check it lands in the inbox, with the Unsubscribe button next to the sender's name in Gmail.
- Start small. Send the first campaigns to your most engaged readers and grow from there as your cap grows.
After it goes out, the Claude connector or the Emails page in your dashboard shows delivery, bounces and complaints per message. Listmonk shows opens, clicks and unsubscribes per campaign. Use one tool's open and click tracking, not both, so links aren't rewritten twice.
When Listmonk isn't the right fit
Listmonk is a lean tool for teams comfortable running a server. It's a poor fit if:
- Nobody on the team wants to run software. Listmonk needs updates, backups of its database and an HTTPS proxy. A hosted newsletter service is less work.
- You need complex automations. Listmonk handles lists, segments, templates, campaigns and transactional templates. Multi-step journeys with branching are not its strength.
- You can't add the HTTPS domain. Without a reachable Root URL, unsubscribe links break, and Gmail and Yahoo may filter your mail.
Listmonk also works with any SMTP server, including one you run yourself. Before your first campaign, run your domain through our email checker and make sure DMARC is in place.
Sending through OwnMailServer is pay as you go, $0.20 per 1,000 emails. See pricing for the details.