Self-hosted Mail + Calendar on TrueNAS (finally behaves like iCloud/Exchange)

This isn’t a beginner setup — this is what it looked like after I pushed past the usual “it kinda works” stage and got something that actually behaves like a normal modern system.

My goal was pretty straightforward:

  • Replace Bluehost email
  • Stop relying on iCloud/Google for calendars
  • Have a shared family calendar that just works
  • Get real push sync (no refresh delays)
  • Make it usable for my lady, not just “homelab usable”

Environment

  • TrueNAS (main box)
  • VM running Mailcow
  • Nginx handling SSL (internal traffic is HTTP)
  • UniFi network + AdGuard DNS

Mailcow stack:

  • Postfix / Dovecot
  • Rspamd
  • SOGo

Outbound mail via Amazon SES.


What didn’t work

CalDAV/CardDAV only

  • No real push
  • iOS behavior inconsistent
  • Feels nothing like iCloud

Internal routing / cert weirdness

  • External worked fine
  • Internal sometimes hit wrong endpoints
  • iOS would throw trust errors depending on DNS path

SOGo GAL across domains

  • Multiple domains = separate GALs
  • Cross-domain visibility works, but UX isn’t clean

What actually worked

ActiveSync (the big unlock)

Setting up iPhone as an Exchange account:

  • Email = push
  • Calendar = push
  • Contacts = sync
  • Reminders = integrated

This is what makes it feel like iCloud/Exchange instead of a lab setup.


Hybrid approach (key detail)

I ended up splitting responsibilities:

ActiveSync

  • Personal email
  • Personal calendar
  • Contacts
  • Push sync

SOGo (CalDAV)

  • Shared family calendar

Reason:

  • ActiveSync in Mailcow doesn’t handle shared calendars well
  • SOGo handles shared calendars correctly

Making it behave like iCloud

  • Set the shared family calendar as default on both phones
  • So any new event automatically goes to the shared calendar

Result:

  • Everything stays in sync
  • No “wrong calendar” issues
  • Works exactly how you expect

Deliverability (critical)

Outbound through Amazon SES:

  • Avoids IP reputation issues
  • Mail lands reliably
  • No constant spam problems

Spam filtering

Rspamd is doing:

  • Connection-level blocking
  • Greylisting
  • Bayesian + neural filtering

Most spam never even gets delivered.


End result

This now behaves like:

  • iCloud (shared calendar UX)
  • Exchange (push sync everywhere)
  • Fully self-hosted

The biggest test: my lady uses it without issues.


What I’d do differently

  • Skip trying to make CalDAV-only behave like iCloud
  • Use ActiveSync from the start
  • Plan for SES early
  • Expect DNS/internal routing to be the most annoying part

Why I’m posting this

Most setups stop at:

“it works”

This is the first time I’ve had:

“this actually feels normal to use”

If you’re trying to replace Bluehost/iCloud/Google with something self-hosted that doesn’t suck, this combination got me there.


If anyone wants more detail on config (ActiveSync vs SOGo split, etc.), I’m happy to share.

1 Like

Follow-up: Mailcow rejecting my own outbound mail + TLS issues (resolved)

Wanted to close the loop in case this helps someone else, or in case I forget later I at least wrote it down.


Issue 1: Mailcow rejecting my own outbound mail

Rspamd was rejecting messages submitted via authenticated SMTP with high spam scores.

Root cause:
Authenticated users were still being evaluated against normal spam thresholds.

Fix:
Added an authenticated_user settings block in rspamd.conf.local to disable reject/greylist actions for authenticated sessions:

settings {
  authenticated_user {
    priority = high;
    authenticated = yes;
    apply {
      actions {
        reject = null;
        add_header = null;
        greylist = null;
      }
    }
  }
}

Restarted rspamd-mailcow after validation (rspamadm configtest).


Issue 2: TLS errors on iPhone / Apple Mail

Clients were reporting certificate errors intermittently.

Root cause:
Split DNS via AdGuard:

  • LAN: mail.domain → Mailcow (192.168.x.x) → expired cert
  • WAN: mail.domain → Nginx Proxy Manager → valid cert

So the same hostname returned different certificates depending on where the client was.

Fix:
Synced the valid cert from NPM into Mailcow:

  • Copied fullchain.pemcert.pem
  • Copied privkey.pemkey.pem
  • Restarted postfix, dovecot, nginx containers

Verified all ports (443, 993, 465, 587) now present the same valid cert.


Automation

Implemented a cron-based cert sync:

  • Hash check of NPM cert
  • Only deploys if changed
  • Persistent state (survives reboot)
  • Avoids unnecessary service restarts

Takeaway

If you’re using:

  • AdGuard / Pi-hole (split DNS)
  • Reverse proxy (NPM, Traefik, etc.)
  • Mailcow directly on LAN

Make sure Mailcow itself has a valid certificate, not just your proxy.


Everything is now stable across LAN + WAN + mobile clients.