T8 EVAL: SMTP relay options (comail-first, Lettermint EU fallback)
closedParent: 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
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 v3eu-api.smtp2go.com/v3).1. Per-domain lifecycle matrix
Bearer)X-Smtp2go-Api-Key, EU base URL)POST /domains {"domain"}→ 201 +DomainDataincl.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_linkfor DNS automationGET /domains/{id}?include=dnsRecords,projectsPOST /domain/view {"domain"?}→ same fields +dkim_verified/rpath_verified/cname_verifiedGET /domainsfilterstatus=verified\|partially_verified\|pending_verification\|failed_verification; per-recordstatus/verified_at/last_checked_atPOST /domain/viewpoll the three*_verifiedbooleansPOST /domains/{id}/dns-records/verify(all) orPOST .../{recordId}/verify(single)POST /domain/verify {"domain"}(else auto-verify every ~7 min;auto_verify:trueon add skips the call)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)POST /users/smtp/add {"username", email_password?, custom_ratelimit?, ...}(unlimited users — one per cell possible). AlsoPOST /domain/edit-tracking-domain,/domain/edit-return-path-domain,/domain/edit-subaccount-accessif neededDELETE /domains/{id}POST /domain/removesmtp.lettermint.co:587STARTTLS (or 465 implicit-TLS recommended), auth userlettermint+ Project API token;PLAIN/LOGIN/CRAM-MD5; IP allowlist on token; routing viaX-Lettermint-Route, tags viaX-LM-Tag(s), metadata viaX-LM-Metadata-*mail-eu.smtp2go.com/mail-eu2.smtp2go.comEU-only hosts (vs genericmail.smtp2go.comgeo-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 plansDNS shape (both = no apex SPF edit, fits sign-at-relay-only with Stalwart
dkimSignDomain=false): - Lettermint: DMARC TXT +lm1/lm2._domainkeyCNAME →*.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
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 payloadsPOST /webhook/add {url, events[], usernames?, output_format:json, auth_header_type/value?, headers?}+ view/edit/remove; bearer-or-basic auth header supportedmessage.hard_bounced,message.soft_bounced,message.failed,message.delivered,suppression.added/removedbounce(payload flag hard/soft),delivered,processed,reject,spam,unsubscribemessage.spam_complaint(+suppression.addedreasonspam_complaint)spam(auto-adds address to suppressions)GET /messages?filter[domain_id/status/...],GET /messages/{id}/events,GET /messages/{id};GET /stats/...team usagePOST /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_summaryreportsGET /suppressions(filter scope/project/reason/date),POST /suppressions(1–1000/batch, reasonhard_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)3. Multi-domain fit (dozens, low volume)
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)
dns_records[].purposeenum values (expectreturn_path+ DKIM + DMARC) — capture one liveGET domain detailssample before freezing the poller logic.setup_link(DNS-automation URL in add/view responses) — inspect whether it is usable by our future provisioner or dashboard-only.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.