The full story of building a self-hosted email stack across 12 domains and 6 servers at Prachyam Studios — the architecture, the hard lessons, and why I'd do it again.
In 2024, Prachyam Studios needed to send a lot of email. Not newsletter-scale email — marketing at genuine scale, to a user base that cared about the content. The budget for this was approximately nothing, because Prachyam was a volunteer-run operation and "spend ₹4L/month on a managed email service" was not an option.
So I built it from scratch. Postfix, Dovecot, DKIM, SPF, DMARC, Rspamd, 6 servers, 12 domains, IP warmup from zero. Over the next year we sent roughly 20 million emails. The deliverability held up. The cost was a few thousand rupees a month in VPS bills.
This is everything I learned, including the things I wish someone had told me before I started.
The managed email services — AWS SES, SendGrid, Postmark, Mailgun — are genuinely good products. I've used all of them. If you have the budget and don't want to learn the internals, they're the right choice.
For Prachyam, they weren't viable for two reasons.
Cost. At the volumes we needed, managed services price per-email in ways that add up fast. SES at $0.10/1000 emails sounds cheap until you're sending 20M emails and the bill is ₹1.7L. Multiply that by multiple campaigns per month.
Control. Deliverability is not a knob managed services expose to you. When Gmail starts deferring your emails, your options are "open a support ticket and wait" or "change your sending patterns and hope." When you own the stack, you can read the logs, adjust the retry schedule, tune the throttling per-domain, and understand exactly why a specific receiving server is rejecting you.
The second reason is the real one. I wanted to understand how email actually works, not just consume it as a service.
Six VPS nodes across two providers, each running a purpose-specific role:
Node 1 (primary MX): Postfix SMTP relay + Dovecot IMAP
Node 2 (backup MX): Postfix with lower priority MX record
Node 3 (outbound relay): Postfix submission, dedicated for bulk send
Node 4 (filtering): Rspamd + ClamAV, shared by all nodes
Node 5 (monitoring): Graylog + custom dashboards
Node 6 (storage): Mail archive, backups, DMARC report storageThe split between transactional mail (Node 1) and bulk marketing (Node 3) is critical. You never want your marketing IP reputation to affect your transactional mail deliverability. A single spam complaint about a newsletter should not delay a password reset email.
Each domain gets its own DKIM key. Each key is 2048-bit RSA. The keys live in /etc/opendkim/keys/ on the outbound relay with permissions restricted to the opendkim user.
Karanveer Singh Shaktawat
Full Stack Engineer & Infrastructure Architect
Building portfolio, contributing to open source, and seeking remote full-time roles with significant technical ownership.
Pick what you want to hear about — I'll only email when it's worth it.
Did this resonate?
How a bulk marketing campaign starved transactional email at Prachyam Studios — and how I fixed it by understanding Postfix queue internals and building a priority queue on top of Mailcow.
# Generate a key for a domain
opendkim-genkey -t -s mail -d prachyam.org
# Creates: mail.private (private key) and mail.txt (DNS record to publish)For each domain, you need six types of records. I wrote a generation script that outputs them all given a domain name:
MX: @ → mail.prachyam.org (priority 10)
@ → backup-mail.prachyam.org (priority 20)
A: mail.prachyam.org → [Node 1 IP]
backup-mail.prachyam.org → [Node 2 IP]
PTR: [Node 1 IP] → mail.prachyam.org (reverse DNS — critical)
[Node 3 IP] → bulk.prachyam.org
SPF: @ → "v=spf1 mx a ip4:[NODE3_IP] -all"
DKIM: mail._domainkey → "v=DKIM1; k=rsa; p=[PUBLIC_KEY]"
DMARC: _dmarc → "v=DMARC1; p=quarantine; rua=mailto:dmarc@prachyam.org; aspf=r; adkim=r"The PTR record (reverse DNS) is the most commonly forgotten. It maps your IP address back to a hostname. Most hosting providers let you set it in their VPS control panel. Without a PTR record, many receiving servers will reject or heavily score your mail.
A fresh IP address has no reputation — which is treated as a bad reputation by large receiving servers. Gmail, Yahoo, Microsoft 365 all throttle or reject high-volume sending from unknown IPs.
The warmup process is a deliberate ramp over 4–6 weeks:
| Week | Daily Send Volume | |------|-------------------| | 1 | 200 | | 2 | 1,000 | | 3 | 5,000 | | 4 | 20,000 | | 5 | 100,000 | | 6+ | Unrestricted |
The numbers are approximate — the actual curve depends on engagement. If recipients are opening, clicking, and not marking as spam, you can accelerate. If you're seeing deferrals, slow down.
The critical rule: send to your most engaged users first. The warmup period is when receiving servers are forming their impression of your IP. Send to people who will open the email. High open rates and low complaint rates during warmup establish a positive reputation that carries forward.
I built a simple priority queue for warmup sends: users who had previously opened our emails went in the first tier, new subscribers in the second, cold addresses last.
You can't improve what you don't measure. I set up four monitoring sources:
Postfix logs — every delivery attempt, success, and failure, with reason codes:
# Watch live delivery attempts
tail -f /var/log/mail.log | grep -E "(sent|deferred|bounced)"
# Summarize deferral reasons
grep "deferred" /var/log/mail.log | \
grep -oP "(?<=said: ).*" | \
sort | uniq -c | sort -rn | head -20DMARC aggregate reports — Gmail, Microsoft, and Yahoo send XML reports to the rua address in your DMARC record. These show per-IP DMARC pass/fail rates and which authentication mechanism is failing. I wrote a Rust parser that ingested these reports into a time-series database and visualized them in Grafana.
Blacklist monitoring — a cron job that checked each outbound IP against 15 common DNSBLs every 6 hours:
for DNSBL in zen.spamhaus.org bl.spamcop.net dnsbl.sorbs.net; do
host "$REVERSED_IP.$DNSBL" && echo "LISTED on $DNSBL" || echo "Clean on $DNSBL"
doneFeedback loops — registering with Google Postmaster Tools, Microsoft SNDS (Smart Network Data Services), and Yahoo's FBL (Feedback Loop) program. These give you complaint rate data directly from the receiving provider. If your complaint rate exceeds 0.3% at Gmail, you're going to have deliverability problems regardless of your technical setup.
Lesson 1: List hygiene matters more than technical setup.
The best DKIM configuration in the world won't save you if you're sending to stale addresses or purchased lists. Bounces and spam complaints are what damages IP reputation. Technical authentication tells receiving servers who you are. Engagement history tells them whether you should be trusted.
We implemented a sunset policy: any subscriber who hadn't opened an email in 90 days went into a re-engagement campaign. If they didn't engage with that, they were unsubscribed. This felt painful initially — smaller list, lower raw send numbers. The deliverability improvement was immediate and significant.
Lesson 2: The deferred queue is not an emergency.
When emails start deferring, the instinct is to flush the queue aggressively. This is wrong. Rapid retries signal spam behavior and make the throttling worse. Postfix's default retry schedule — 5 minutes, then doubling up to maximal_queue_lifetime (5 days by default) — is conservative for good reason.
Only flush manually when the underlying problem is fixed. If emails are deferring because of an SPF misconfiguration, fix the SPF record first, then flush.
Lesson 3: Test with mail-tester.com before any send.
mail-tester.com gives you a spam score and a complete breakdown of what's configured correctly. It's free for 3 tests per day. I ran every new domain through it before sending a single email. A perfect 10/10 score doesn't guarantee deliverability, but anything below 8 will definitely cause problems.
Lesson 4: Per-domain sending limits.
Gmail throttles per-IP, but also has per-day limits for senders without established reputation. You don't get told what those limits are — you discover them via 421 deferral codes ("Try again later"). The solution is to spread sending across multiple IPs and pace sends throughout the day rather than blasting everything in one window.
Lesson 5: SPF record -all vs ~all.
-all (hardfail) means any server not listed in your SPF record is definitively unauthorized. ~all (softfail) means the same, but with a softer signal — receiving servers may accept it but mark it.
Start with ~all during setup. Switch to -all once you're confident you've listed every legitimate sending source. A -all SPF record where you forgot to include a third-party service (like a transactional email service for password resets) will cause your own legitimate emails to fail SPF.
The DMARC aggregate report parser deserves its own mention because it was genuinely useful and surprisingly simple to build.
DMARC reports are ZIP-compressed XML files sent to your rua address. The schema is standardized (RFC 7489). A report looks like:
<feedback>
<report_metadata>
<org_name>Google Inc.</org_name>
<date_range>
<begin>1704067200</begin>
<end>1704153600</end>
</date_range>
</report_metadata>
<record>
<row>
<source_ip>198.51.100.42</source_ip>
<count>1247</count>
<policy_evaluated>
<disposition>none</disposition>
<dkim>pass</dkim>
<spf>pass</spf>
</policy_evaluated>
</row>
</record>
</feedback>The Rust parser I wrote:
dmarc-reports@ inbox via IMAP using the imap cratequick-xmlTotal code: about 400 lines. The dashboard showed, per day, which of our IPs had DMARC pass/fail rates and from which receiving organizations. When a new IP started failing DMARC alignment, we could see it within 24 hours and fix it before it became a deliverability problem.
After a year of running this stack:
The equivalent managed service cost would have been somewhere between ₹80,000 and ₹2,00,000 per month depending on volume.
Yes, but with different timing. Email infrastructure has a steep learning curve and takes weeks to stabilize. If you're under delivery pressure from day one, you'll make decisions under stress that create problems later.
The right time to build your own mail stack is before you need it at scale. Set it up when volume is low, let the IP reputation mature, fix the edge cases in a low-stakes environment. By the time you need to send at volume, the infrastructure is stable and you understand how it behaves.
The wrong time is when someone asks "when can we send the first campaign?" and you're still configuring DKIM.
The knowledge is valuable independent of whether you run email infrastructure again. Understanding how SPF, DKIM, and DMARC actually interact — not at the "I enabled them" level but at the "I know why they fail in specific ways" level — makes you better at debugging any system where authentication and trust are involved.
Email is the oldest production protocol most engineers have never had to understand. It's worth understanding.
Running a self-hosted email server across 12 domains for a media company — the architecture, challenges, and why I'd do it again.