Skip to main content

Architecture

info

This page covers the architecture of the email relay including component roles, email flow, and the attack scenarios it protects against.

Email relay series

  1. Email relay
  2. Architecture - You are here
  3. Manifests
  4. Flux integration
  5. 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.

AspectConfiguration
LanguageGo 1.21
Port25 (requires NET_BIND_SERVICE capability)
Metrics port9090
UpstreamMailpit on localhost:1025

Responsibilities:

  • Accept SMTP connections from clients
  • Extract recipient domain from RCPT TO command
  • Perform DNS MX lookup (with A record fallback per RFC 5321)
  • Reject with 550 if no MX records found
  • Forward valid emails to Mailpit
  • Expose Prometheus metrics

Mailpit

Email logging and testing tool that captures all emails passing through.

AspectConfiguration
Imageaxllent/mailpit:v1.28.3
SMTP port1025
Web UI port8025
Metrics port9091
Relaysmtp2graph 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.

AspectConfiguration
Imagesmtp2graph/smtp2graph:v1.1.4
Port2525
AuthAzure 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

Known Microsoft bug

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:

  1. MX lookup: Query DNS for MX records of recipient domain
  2. A record fallback: If no MX records, check for A records (per RFC 5321)
  3. Cache result: Store result for 1 hour to reduce DNS load
  4. Decision: Accept if records found, reject with 550 if not

Resource requirements

ComponentMemoryCPUStorage
mx-validator64Mi100m-
Mailpit256Mi200m1Gi (email storage)
smtp2graph128Mi100m-

Total estimates:

  • Memory: ~450Mi
  • CPU: ~400m
  • Storage: ~1Gi

Namespace layout

The namespace is self-contained with no cross-namespace dependencies except:

  • monitoring namespace for Prometheus scraping
  • ingress-nginx namespace for optional Mailpit UI access