T8 EVAL: SMTP relay options (comail-first, Lettermint EU fallback)

closed
#05830c8 opened by agent Sep 13

Parent: bug 75966cc (cell architecture tracking). Lean 3rd-party to start (avoid warmup pain); comail-first to support the AT ecosystem. Special relay roles (outbound.eu...) land after this decides.

Goal

Pick the outbound relay all cells send through (single warm IP).

Scope (timeboxed eval)

  • Candidates: comail (ATProto-native, shared warmed pool, dual-DKIM, DID labels, AGPL, warming tiers) vs Lettermint (EU-only, GDPR posture, own ASN/IPs, SMTP+API) + 1-2 EU alternates.
  • Criteria: deliverability/warmup model, DKIM/SPF/DMARC shape (sign-at-cell vs sign-at-relay), bounce/FBL handling, EU residency, cost/volume, API + webhooks, IP-allowlist fit.

Acceptance

  • Decision + Stalwart Relay-route config plan + DNS record plan recorded; relay-host build issue filed as follow-up (not in this issue).

1 Comment

agent 02528c3 Sep 15

T8 relay eval — API automation recon: Lettermint vs SMTP2GO (docs-only, no code)

Scope per update: Brevo + Scaleway dropped (plan fit), comail out (stalled, no new domains). Question: can the full per-domain flow run automated — create domain, get DNS records, activate sending, bounce/spam feedback — for dozens of hosted domains at low volume, with sign-at-relay-only?

Short answer: yes for both, endpoint-for-endpoint. Details below, all from current API docs (Lettermint Team API https://api.lettermint.co/v1, SMTP2GO v3 eu-api.smtp2go.com/v3).

1. Per-domain lifecycle matrix

Step Lettermint (Team token Bearer) SMTP2GO (header X-Smtp2go-Api-Key, EU base URL)
Create domain POST /domains {"domain"} → 201 + DomainData incl. dns_records[] (type/hostname/fqdn/content/purpose/required_for_verification/status) POST /domain/add {"domain", tracking_subdomain?, returnpath_subdomain?, auto_verify?, requisition_ssl?} → dkim_selector+dkim_value (dkim.smtp2go.net), rpath_selector+rpath_value (return.smtp2go.net), tracker CNAMEs, setup_link for DNS automation
Get DNS records GET /domains/{id}?include=dnsRecords,projects POST /domain/view {"domain"?} → same fields + dkim_verified/rpath_verified/cname_verified
Poll status GET /domains filter status=verified\|partially_verified\|pending_verification\|failed_verification; per-record status/verified_at/last_checked_at POST /domain/view poll the three *_verified booleans
Trigger verify POST /domains/{id}/dns-records/verify (all) or POST .../{recordId}/verify (single) POST /domain/verify {"domain"} (else auto-verify every ~7 min; auto_verify:true on add skips the call)
Activate sending PUT /domains/{id}/projects {"project_ids":[...]} to scope domain to project(s); SMTP must be enabled per project (dashboard toggle — one-time, not per domain) Domain verify is sufficient; sending credential is separate: POST /users/smtp/add {"username", email_password?, custom_ratelimit?, ...} (unlimited users — one per cell possible). Also POST /domain/edit-tracking-domain, /domain/edit-return-path-domain, /domain/edit-subaccount-access if needed
Delete DELETE /domains/{id} POST /domain/remove
Stalwart SMTP fit smtp.lettermint.co:587 STARTTLS (or 465 implicit-TLS recommended), auth user lettermint + Project API token; PLAIN/LOGIN/CRAM-MD5; IP allowlist on token; routing via X-Lettermint-Route, tags via X-LM-Tag(s), metadata via X-LM-Metadata-* mail-eu.smtp2go.com / mail-eu2.smtp2go.com EU-only hosts (vs generic mail.smtp2go.com geo-routed); ports 25/2525/8025/587/80 TLS-or-plain, 465/8465/443 SSL; auth = per-user SMTP creds; 40 concurrent conns, 5k mails/conn, 200k/hr default; IP/auth alternatives on paid plans

DNS shape (both = no apex SPF edit, fits sign-at-relay-only with Stalwart dkimSignDomain=false): - Lettermint: DMARC TXT + lm1/lm2._domainkey CNAME → *.dkim.lmta.net (rotation without DNS change) + bounce CNAME → bounces.lmta.net. - SMTP2GO: DKIM CNAME (sXXXX._domainkey → dkim.smtp2go.net) + return-path CNAME (emXXXX → return.smtp2go.net) + optional tracking CNAME. SPF alignment via return-path subdomain.

2. Bounce / spam / suppression feedback

Need Lettermint SMTP2GO
Webhook create (API) POST /webhooks {name,url,events[],scope:team\|project\|route, project_ids?} + update/delete/test-dispatch (POST /webhooks/{id}/test) + deliveries inspection (GET /webhooks/{id}/deliveries[/{deliveryId}]) + secret regen; signed payloads POST /webhook/add {url, events[], usernames?, output_format:json, auth_header_type/value?, headers?} + view/edit/remove; bearer-or-basic auth header supported
Bounce events message.hard_bounced, message.soft_bounced, message.failed, message.delivered, suppression.added/removed bounce (payload flag hard/soft), delivered, processed, reject, spam, unsubscribe
Spam events message.spam_complaint (+ suppression.added reason spam_complaint) spam (auto-adds address to suppressions)
Poll fallback (no webhook) GET /messages?filter[domain_id/status/...], GET /messages/{id}/events, GET /messages/{id}; GET /stats/... team usage POST /activity/search {event_types:[hard-bounced,soft-bounced,spam,...], search_sender/search_recipient/search_usernames, limit≤1000, continue_token} (60 req/min); email_bounces/email_spam/email_unsubscribes/email_history/email_summary reports
Suppressions API GET /suppressions (filter scope/project/reason/date), POST /suppressions (1–1000/batch, reason hard_bounce\|spam_complaint\|unsubscribe\|manual, scope team/project/route), DELETE /suppressions/{id} (spam-complaint removal = 202 review-ticket flow, stays active until approved) POST /suppression/add|view|remove (address or domain)
Webhook limits Team/project/route scopes, per-event filtering; no count limit found in docs Free: 1 webhook; paid: up to 10. Retries 35×/48h, 10s timeout, failed-notification view in App

3. Multi-domain fit (dozens, low volume)

  • Lettermint: domain caps are plan-gated (Free 1 / Starter 5 / Growth 30 / Pro unlimited). Dozens → Growth (€13/mo 10k incl.) minimum, Pro (€15/mo 10k incl.) realistic; overage €1.50⁄1.15⁄1.10 per 1k by tier. EU-resident (NL, own infra, ISO 27001), GDPR posture strongest.
  • SMTP2GO: free = max 5 verified senders (domains + single addresses combined); paid = unlimited. Starter \(10/mo 10k + 30d reporting, overage ~\)1/k, up to 3× plan in overage spikes. Non-EU company; EU path = eu-api.* + mail-eu* endpoints + Amsterdam-DC placement (auto if signup from Europe — still to prove on a trial account). FBL/blacklist monitoring + spamtrap detection claimed; dedicated IP from Professional/100k+ ($19/mo extra).

4. Gaps / watch items (no blocker found)

  1. Lettermint SMTP enable is a per-project dashboard toggle (one-time, not per-domain) — confirm it is set once during provisioning and never per-domain.
  2. Lettermint dns_records[].purpose enum values (expect return_path + DKIM + DMARC) — capture one live GET domain details sample before freezing the poller logic.
  3. SMTP2GO EU-DC placement proof — verify on trial account that activity/reporting region = EU and stays there.
  4. SMTP2GO setup_link (DNS-automation URL in add/view responses) — inspect whether it is usable by our future provisioner or dashboard-only.
  5. Spam-complaint suppression removal differs: Lettermint protected (202 + review), SMTP2GO direct remove — record in abuse runbook either way.

5. Recommendation for next step

Both pass the automation test. Proposed: keep Lettermint as primary (EU residency + no-SPF-change DNS + managed DKIM rotation), SMTP2GO as EU-DC fallback (unlimited domains on paid, per-cell SMTP users, mature activity/suppression APIs). Next actions when approved: trial accounts → single scratch-domain walkthrough per provider (docs calls only, no Stalwart send yet) → record exact payloads → then Stalwart Relay-route + DNS-plan follow-up.