Architecture
This page covers the architecture of the email relay including component roles, email flow, and the attack scenarios it protects against.
Email relay series
- Email relay
- Architecture - You are here
- Manifests
- Flux integration
- Operations
Overview
The email relay runs as a single pod with three containers that form a processing pipeline:
Components
mx-validator
Custom Go application that acts as an SMTP proxy with MX record validation.
| Aspect | Configuration |
|---|---|
| Language | Go 1.21 |
| Port | 25 (requires NET_BIND_SERVICE capability) |
| Metrics port | 9090 |
| Upstream | Mailpit on localhost:1025 |
Responsibilities:
- Accept SMTP connections from clients
- Extract recipient domain from
RCPT TOcommand - Perform DNS MX lookup (with A record fallback per RFC 5321)
- Reject with
550if no MX records found - Forward valid emails to Mailpit
- Expose Prometheus metrics
Mailpit
Email logging and testing tool that captures all emails passing through.
| Aspect | Configuration |
|---|---|
| Image | axllent/mailpit:v1.28.3 |
| SMTP port | 1025 |
| Web UI port | 8025 |
| Metrics port | 9091 |
| Relay | smtp2graph on localhost:2525 |
Responsibilities:
- Accept emails from mx-validator
- Store emails in SQLite database for viewing
- Provide web UI for debugging
- Relay all emails to smtp2graph
- Expose Prometheus metrics
smtp2graph
SMTP-to-Graph-API relay that sends emails via Microsoft 365.
| Aspect | Configuration |
|---|---|
| Image | smtp2graph/smtp2graph:v1.1.4 |
| Port | 2525 |
| Auth | Azure App Registration (OAuth) |
Responsibilities:
- Accept emails from Mailpit
- Authenticate with Microsoft Graph API using OAuth
- Send emails via Graph API (bypasses SMTP AUTH blocks)
Email flow
Attack scenarios protected
1. Fake domain spam
Attack: Spammer books appointment with spammer@nonexistent-domain.xyz
Protection: MX validator rejects at SMTP level before email enters system
RCPT TO: <spammer@nonexistent-domain.xyz>
550 5.1.2 Bad destination mailbox address - domain has no MX record
Result: No email sent, no resources wasted, metrics track rejection
2. Typosquatting domains
Attack: User types user@gmial.com instead of user@gmail.com
Protection: gmial.com has no MX records, rejected
Result: Email not sent to wrong domain, user can correct mistake
3. Disposable email spam
Attack: Bot uses bot@temp-mail-service.xyz with no real infrastructure
Protection: If domain has no MX/A records, rejected
Limitation: Domains with valid MX records (10minutemail.com, etc.) will pass
4. Email verification bypass
Attack: Attacker books with fake email, never verifies, clutters calendar
Protection: Combined with Cal.com "Requires booker email verification" setting
Result: Fake domains cannot complete booking verification
5. Bounce storm prevention
Attack: Send emails to random addresses at fake domains, triggering bounce floods
Protection: Invalid domains rejected before any email sent
Result: No bounces generated, no reputation damage
6. Resource exhaustion
Attack: Flood system with emails to fake domains
Protection:
- MX lookup cached for 1 hour (prevents DNS amplification)
- Rejection happens early in SMTP transaction (minimal resources used)
- Prometheus metrics alert on high rejection rates
7. Sender reputation protection
Attack: Sending to invalid domains hurts sender reputation
Protection: Emails to invalid domains never leave the system
Result: Microsoft 365 sender reputation preserved
8. Microsoft Graph ICS routing bug
Problem: Microsoft Graph sendMail API parses ICS calendar attachments and routes emails to the ATTENDEE mailto address inside the ICS, completely ignoring the SMTP envelope recipient and To header. This causes organizer notifications to be delivered to attendees instead of the organizer.
Protection: mx-validator strips ALL ICS content (both attachments and inline text/calendar parts) for specific recipients configured in STRIP_ICS_RECIPIENTS.
Configuration:
env:
- name: STRIP_ICS_RECIPIENTS
value: "organizer@yourdomain.com" # Comma-separated list
Result: Organizer notifications delivered correctly without ICS interference
This is a documented Microsoft Graph API bug with no official fix. The ICS stripping workaround is required when using Graph API for calendar notification emails.
MX validation logic
The mx-validator follows RFC 5321 for MX record resolution:
- MX lookup: Query DNS for MX records of recipient domain
- A record fallback: If no MX records, check for A records (per RFC 5321)
- Cache result: Store result for 1 hour to reduce DNS load
- Decision: Accept if records found, reject with
550if not
Resource requirements
| Component | Memory | CPU | Storage |
|---|---|---|---|
| mx-validator | 64Mi | 100m | - |
| Mailpit | 256Mi | 200m | 1Gi (email storage) |
| smtp2graph | 128Mi | 100m | - |
Total estimates:
- Memory: ~450Mi
- CPU: ~400m
- Storage: ~1Gi
Namespace layout
The namespace is self-contained with no cross-namespace dependencies except:
monitoringnamespace for Prometheus scrapingingress-nginxnamespace for optional Mailpit UI access