Skip to content

Known Issues & Tech Debt

Things that work but aren't ideal, and things that don't work yet.

Implementation Gaps

thread.merged Webhook Event

The threading engine detects and executes thread merges (when References span multiple threads), but no webhook event is emitted for merges. Only the 4 message lifecycle events exist: message.received, message.delivered, message.bounced, message.complained.

The webhook spec documents a thread.merged event, but it was never implemented. Customers have no way to know a merge happened except by observing that messages moved between threads.

DMARC Reporting (Partial)

GET /domains/{id}/reports/dmarc returns aggregate delivery statistics (sent/delivered/bounced counts) but not a full RFC 7489 DMARC aggregate report. Full compliance requires storing per-message SPF and DKIM authentication results in delivery_events.details, which isn't done yet.

TLS Reporting (Placeholder)

GET /domains/{id}/reports/tls returns an empty structure. Full RFC 8460 TLS-RPT reporting requires tracking TLS connection results per delivery attempt, which the telemetry pipeline doesn't capture.

Outbound Attachments Silently Dropped

The send endpoint (POST /send) accepts attachments in the request but does not include them in the outbound MIME message. Attachment content is silently dropped. Tracked as TB-543.

Domain/Inbox Endpoints Not Wired to Core Flows

11 domain/inbox endpoints exist as management CRUD, but threads and messages are not yet scoped to inboxes (no inbox_id FK). Creating a domain doesn't trigger SES identity creation. DNS records are computed on the fly. POST /send has no domain/inbox awareness. These endpoints are configuration surface without functional effect on core send/receive/thread flows until TB-554/TB-555 lands.

delivery_status Missing from GET /messages/

The GET /messages/{id} handler does not return delivery_status even though the OpenAPI spec includes it in the response schema. The handler doesn't query delivery_repo.get_latest_status().

Database Record TTL

S3 lifecycle rules expire raw email (365 days) and attachments (730 days), but Postgres records accumulate indefinitely. There's no cleanup job to hard-delete records older than a retention period. This will eventually cause unbounded database growth. Tracked in Linear as TB-552.

Tech Debt

N+1 Query in Thread Message Listing

list_by_thread in the Postgres adapter loads messages individually rather than in a single query with JOINs. This is fine for typical thread sizes (< 50 messages) but will be slow for threads with hundreds of messages.

test-support Feature Leak

The bin-worker crate has a test-support feature that includes mock implementations. Test mocks live in adapter crates and are only enabled via dev-dependencies, so the scope is limited, but they're still compiled into the production binary when the feature is active. The fix is to move test mocks into the test-utils crate.

Operational Constants

Webhook dispatch thresholds, worker poll intervals, and attachment size limits are now configurable via WebhookConfig, WorkerConfig, and SendConfig in crates/config/src/lib.rs. Previously these were hardcoded constants scattered across handler and worker code.

Things That Are Intentionally Missing

These aren't bugs or debt — they're conscious non-goals:

  • IMAP/POP3 access — Mailman is API-only by design
  • Spam scoring — customers handle this in their application
  • Email rendering/UI — Mailman is a backend service
  • Multi-region — single-region (us-east-1) is sufficient and simpler
  • Message search — not in scope; customers index what they need