Building Email Hosting into AskRobots: Django + Mailcow Architecture

By dan • February 27, 2026 • 5 min read

# Building Email Hosting into AskRobots: Django + Mailcow Architecture

Email hosting is one of those features that seems simple — "just give users an email address" — but has a surprising amount of depth once you dig in. DNS verification, mailbox provisioning, quota management, spam filtering, DKIM signing, and the ever-present question of deliverability. Here's how we're building it into AskRobots.

## Why Email Hosting?

Paid AskRobots accounts ($50/month) will include email hosting. Every paid user gets `username@askrobots.com` automatically. Users who bring their own domain can set up custom email (e.g., `you@yourcompany.com`) with full DNS verification.

The bigger play: **AI-powered email processing**. Incoming emails can automatically create tasks, notes, or forward to other addresses based on rules. Send an email to your AskRobots mailbox and it becomes a tracked task — no manual entry needed.

## Choosing Mailcow

We evaluated six open-source mail servers:

| Server | Pros | Cons |
|--------|------|------|
| **Mailcow** | Full REST API, Docker-native, active community, SOGo webmail | GPL v3 (fine for SaaS) |
| Postal | Good API, built for transactional | MariaDB + RabbitMQ dependency mess |
| Stalwart | Modern Rust, JMAP support | Too new, sustainability risk |
| Mailu | Simple setup | Limited API |
| Modoboa | Good admin UI | Smaller community |
| iRedMail | Battle-tested | Manual server install, no Docker |

**Mailcow won** because of its comprehensive REST API. Every operation we need — adding domains, creating mailboxes, managing aliases, rate limiting, DKIM generation — is a single API call. No shelling out, no config file manipulation.

GPL v3 is a non-issue for us: we run it as a service, we don't distribute modified Mailcow code.

## Architecture: Feature-Flagged, Mailcow-Optional

The entire email hosting feature is behind a [django-waffle](https://waffle.readthedocs.io/) feature flag called `email_hosting`. Until the flag is activated, all email routes return 404. This lets us build and test everything on the Django side before Mailcow is even installed.

```
┌─────────────────────┐ ┌──────────────────┐
│ AskRobots Django │────▶│ Mailcow Server │
│ email_hosting app │ API │ mail.askrobots │
│ │◀────│ .com │
│ - Models (Django) │ │ - Dovecot │
│ - Views (CRUD) │ │ - Postfix │
│ - Mailcow Client │ │ - SOGo │
│ - Waffle flag gate │ │ - ClamAV │
└─────────────────────┘ └──────────────────┘
```

The `MailcowClient` gracefully degrades: if `MAILCOW_API_URL` is empty, operations are saved locally in Django but not synced. Once Mailcow is live, a sync job can push everything.

## The Data Model

Four Django models handle the domain:

### EmailDomain
Tracks custom domains with DNS verification status for all four critical records:
- **MX** — Points mail to our server
- **SPF** — Authorizes us to send on behalf of the domain
- **DKIM** — Cryptographic email signing
- **DMARC** — Policy for handling authentication failures

Each domain tracks its Mailcow sync state independently.

### EmailMailbox
The actual email accounts. Key fields:
- `local_part` + `domain` = full email address
- `is_system` flag for auto-provisioned `@askrobots.com` accounts
- `quota` / `quota_used` for storage limits (default 1GB)

### EmailAlias
Simple forwarding: `sales@example.com` → `info@example.com`. Syncs alias IDs back from Mailcow for edit/delete operations.

### EmailForwardingRule
This is where it gets interesting. Rules match incoming email by sender, subject, or recipient and trigger actions:
- **Forward** — Standard email forwarding
- **Create Task** — Parse the email and create an AskRobots task
- **Create Note** — Save email content as a note
- **Ignore** — Silently discard (spam from known sources)

Rules have priority ordering so you can build a processing pipeline.

## The Mailcow API Client

A clean Python wrapper around Mailcow's REST API:

```python
from email_hosting.mailcow_client import MailcowClient

client = MailcowClient()

# Domain management
client.add_domain("example.com")
client.generate_dkim("example.com", key_size=2048)

# Mailbox CRUD
client.add_mailbox("user", "example.com", "password", quota=2048)
client.get_mailboxes(domain="example.com")

# Rate limiting
client.set_domain_ratelimit("example.com", rate="100/h")
client.set_mailbox_ratelimit("user@example.com", rate="50/h")

# Quarantine management
quarantine = client.get_quarantine()
client.release_quarantine(quarantine_id)
```

All API calls go through a single `_request()` method with:
- 30-second timeout
- Automatic error detection (Mailcow returns errors inside success responses)
- Connection/timeout error handling with clear error messages
- Empty URL guard (raises immediately instead of hitting `MissingSchema`)

## What's Built So Far

The Django side is complete and tested:

- **15 views** — Dashboard, domain/mailbox/alias/rule CRUD with proper auth
- **13 templates** — Matching AskRobots earth-tone theme, Bootstrap cards, breadcrumbs
- **4 forms** — With user-scoped querysets (you only see your own domains/mailboxes)
- **13 unit tests** — Model tests + mocked Mailcow client tests, all passing
- **Admin interface** — Full Django admin registration for all models

Everything is feature-flagged. Flip `email_hosting` on in Django admin when Mailcow is ready.

## What's Next

1. **Mailcow installation** on RackBarn VM (Ubuntu reinstall, Docker Compose)
2. **DNS verification service** — Background Celery task using `dnspython` to verify MX/SPF/DKIM/DMARC records
3. **Auto-provisioning** — Create `username@askrobots.com` mailbox when user upgrades to paid
4. **Billing integration** — Gate email features behind subscription check
5. **AI email processing** — Celery workers that poll Mailcow for new mail and apply forwarding rules
6. **MCP tools** — `create_mailbox`, `list_mailboxes`, `add_alias` for AI agent access
7. **Email hosting REST API** — For programmatic access
8. **Landing page** — Public marketing page + sidebar link

## Lessons

**Feature flags are underrated.** We wrote the entire email hosting UI, tested it, and can deploy it to production today — it just won't be visible until we flip the switch. No long-running branches, no merge conflicts.

**Mock your external APIs from day one.** Every Mailcow client test uses `unittest.mock.patch`. We caught an edge case (empty API URL causing `MissingSchema`) in tests before it could ever hit production.

**Django's ecosystem still delivers.** waffle for feature flags, crispy_forms for consistent UI, allauth for auth, django-tables2 for lists — the app came together in a single session because we're standing on solid libraries.