Context
offplan.online нуждается в transactional email infrastructure для Phase 1.2 launch — 7 trial cascade emails (T-7 / T-3 / T-1 / T+0 / T+7 / T+23 / T+29 per plans/onboarding-trial-mode.md Part 2 Step 8) + signup verification + password reset + billing receipts + tier transition notifications, все доставляемые нашему customer'у (Studio / Sales user).
End-state architecture covers 3 sender configurations:
- От нашего домена
offplan.online— customer-facing (наш B2B surface) - От нашего домена
offplanonline.com— buyer-facing fallback (Tier 1 Studios, когда presentation invites уходят к buyers с НАШЕГО домена потому что Studio ещё не имеет Tier 2 custom domain) - От нашего сервиса но с Studio's custom domain (Tier 2+ white-label promise per ADR 0008) — buyer-facing для Tier 2+ Studios
This ADR ratifies architecture для (1) только. Scope intentionally narrow per CONV-39 user decision — Phase 1.2 launch only needs customer-facing transactional (Studio user is recipient; white-label fidelity не relevant). Configurations (2) and (3) parked pending Phase 1.7+ ratification.
CONV-20 historical lean = Resend (recorded in Notion Sessions row 2026-05-07). CONV-39 confirmed Resend ratification + 2-domain audience split + verify-both-now activation phasing.
Decision
Vendor
Resend — Stage 1 transactional ESP for offplan.online.
- Resend free tier (3,000 emails/mo) covers Stage 1 volume forecast: 100 trials × 7 cascade = 700/mo + signup verify + billing receipts + tier transitions + password resets ≈ ~1500-2000/mo total. Pro tier ($20/50k mo) headroom available если volume scales.
- One Resend account holds both verified domains (
offplan.online+offplanonline.com). Single API key serves both — sender domain selected per-message at send time черезfrom:field. - React Email template integration (react.email) — natively supported by Resend SDK; aligns с Sub-plan 2 Step 8 designer email scope (Ilya delivers 7 trial cascade templates as React Email components).
- Webhook events consumed Stage 1:
email.delivered·email.bounced·email.complained·email.delivery_delayed. Bounce + complaint events feed Phase 1.2.3 trial state machine для bad-email user handling (T-7 send bounce → flag user → operator action per Sub-plan 2 Step 13 operator playbook).
Sender domain architecture — 2 own domains, audience split
Primary: offplan.online — customer-facing transactional, Phase 1.2 active sending.
- All 7 trial cascade emails (T-7 / T-3 / T-1 / T+0 / T+7 / T+23 / T+29)
- Signup verification (Phase 1.2.1)
- Password reset (Phase 1.2.x cross-cut)
- Billing receipts + invoice notifications (Sub-plan 2 Part 2 Step 7 — Stripe webhook → Resend send)
- Tier transition notices (Plan & Billing UI confirmations)
- Sender addresses:
[email protected](default),[email protected](Sub-plan 2 Step 13 inbox topology — 24h SLA),[email protected](GDPR DSR — 72h SLA),[email protected](24h SLA) - Strict DMARC policy:
v=DMARC1; p=reject; rua=mailto:[email protected];(transactional reputation isolation — aggressive policy aligns с industry transactional standard)
Secondary: offplanonline.com — buyer-facing Tier 1 fallback, DKIM-verified but dormant Phase 1.2.
- Registered in Resend, DKIM CNAME + SPF TXT + DMARC TXT records published к DNS, verification ✅ — но zero messages sent Phase 1.2
- Reserved for Phase 1.7+ activation when Tier 1 buyer-facing presentation invites ship (Studio without custom domain → invite uses
[email protected]so buyer sees неутральный «offplanonline» brand не «offplan.online» — separates B2B и B2C brand exposure для reputation isolation) - DMARC policy initial:
v=DMARC1; p=quarantine; rua=mailto:[email protected];(less strict до active warm-up Phase 1.7) - Why verify now (D1 phasing) over defer (D2): DNS work one-time, ~15 min extra; Phase 1.7+ activation later становится API-only change (no DNS coordination needed); prevents domain takeover risk on unverified apex; zero cost on free tier; Resend supports multiple domains per account natively
Decision #2 (deferred — future ADR / amendment)
Tier 2+ white-label per-Org client-domain sending NOT ratified в этом ADR. Architectural readiness:
- Resend supports per-domain DKIM verification API natively — adding Studio's custom domain становится Studio admin action в Phase 1.7.10 Custom Domain self-serve flow (Studio adds DNS records → backend calls
resend.domains.create({name: 'studio-custom-domain.com'})→ polls verification → flips Studiotier_2_white_label_email = true) - No vendor lock-in penalty deferring — pattern is industry-standard, alternative providers (SendGrid, Postmark, AWS SES) also support multi-domain. Migration cost preserved.
- Trigger for follow-up ADR: Phase 1.7.10 ratification (
/plansession for Custom domain self-serve flow + DKIM Tier 2+). ETA: post-Phase 1.2 launch.
Alternatives Considered
| Alternative | Status | Reason |
|---|---|---|
(A) Single-domain offplanonline.com |
❌ Rejected | Eliminated by CONV-39 — user confirmed 2 own domains (.online + .com); single-domain breaks audience split + future Tier 2 readiness |
| (B) Per-Org DKIM self-hosted | ❌ Rejected | Eliminated by Resend vendor lock + Decision #2 deferral. Self-hosted DKIM = key rotation ops burden not justified Stage 1 |
| (C) Hybrid Tier 1/Tier 2 | ❌ Rejected | Eliminated by Decision #2 deferral — premature к ratify Tier 2 path |
| (D) Resend white-label delegation (CONV-20 lean) | ✅ Adopted | Selected vendor. White-label delegation IS Decision #2 future path (Resend domains.create API) — natively supported, deferred к follow-up ADR |
| (D1) Verify-both-now phasing | ✅ Adopted | CONV-39 pick. 15 min extra DNS now < cost of doing later under Phase 1.7+ pressure; prevents domain takeover |
| (D2) Verify-primary-only phasing | ❌ Rejected | YAGNI doesn't apply when DNS coordination cost > one-time setup |
Other ESPs scanned (no candidate displaced Resend):
- Postmark — strongest transactional reputation, $15/10k. Resend matches Stage 1 needs at $0; premium not justified.
- AWS SES — cheapest at scale, но cold-start reputation + manual DKIM management = ops burden incompatible с compact format.
- SendGrid — post-Twilio acquisition reputation concerns; Resend's developer-experience advantage wins.
- Mailgun — no advantage over Resend; Resend's React Email integration + simpler API + better free tier wins.
Consequences
- Sub-plan 2 Step 8 (
plans/onboarding-trial-mode.md) — unblocked. Concrete vendor + domain + send pattern ratified для downstream implementation. - Workstream
onboarding-trial-implementation—blocked_bylist updated: removeadr-0011-email-sender. Remaining:adr-0014-mcp-wrapper-auth,legal-entity-lock,phase-1-3-implementation. - Designer scope (Ilya, Sub-plan 2 Step 8) — 7 trial cascade email templates delivered as React Email components (not raw HTML). + 4 standard transactional templates (signup verify / password reset / billing receipt / tier transition). Plus in-app modals separately (2 Trial-period locks + 8-12 post-Trial + 3 success upsell + reactivation).
.env.exampleaddition —RESEND_API_KEY=re_replace_me+ comment block с Resend dashboard setup steps.- DNS work owner — Roman has domain registrar access. ~15 min total: DKIM CNAME ×2 + SPF TXT + DMARC TXT per domain. Pre-launch one-time.
- Phase 1.7.10 / 1.8.6 follow-up — Decision #2 ratification triggered when Phase 1.7.10 reaches /plan. Architecture path-of-least-resistance.
- ADR 0008 cross-ref maintained — Tier 2+ custom-domain white-label promise upheld.
- Operator dashboard touch (Sub-plan 2 Step 13) — bounce / complaint event handling adds operator action
email_bounce_flagк 9-action playbook. - No vendor lock-in penalty — migration к alternative ESP feasible (2-3 days engineering).
Revisit trigger
- Stage 2+ volume > 50k emails/mo sustained → Resend Pro tier ($20/mo); reconsider Postmark / AWS SES if scaling further
- Resend reliability incident (sustained downtime > 4h или deliverability degradation > 5% bounce baseline) → multi-provider failover discussion
- Decision #2 trigger — Phase 1.7.10 ratification approaches; spawn follow-up ADR
- DMARC failure trends — DMARC
ruareports > 2% pass rate failure sustained 7+ days → DKIM/SPF setup review
Implementation task card (compact)
Phase 1.2 implementation breakdown — folded into workstreams/onboarding-trial-implementation.md Phase 1.2.3 (trial cascade scheduling). NO separate plans/email-sender-stage-1.md — этот ADR + workstream task list = complete spec.
T1. Resend account + domain verification (~30 min one-time). Owner: Roman + Sergei.
- Create Resend account (Free tier) — single account для both domains
- Verify
offplan.online— DKIM CNAME ×2, SPF TXT, DMARC TXT records published к DNS - Verify
offplanonline.com— same record set published - Wait for Resend dashboard ✅ verification
- Generate API key (Production scope) → team password manager → distribute via
.env
T2. .env.example + secrets distribution. Owner: Sergei.
- Add
RESEND_API_KEY=re_replace_meblock с inline comments - Sergei + Roman + Roma + Ilya rotate to personal
.env
T3. Send wrapper module. Owner: Roma.
src/email/resend-client.ts— wrapper aroundresend.emails.send()с error handling + structured logging- Sender domain selector:
from: '[email protected]'default Stage 1 с whitelist enforcement (block sends к offplanonline.com Phase 1.2) - React Email template render pipeline —
@react-email/renderдля server-side HTML - Unit test — mock SDK, assert correct
from+subject+html
T4. Webhook receiver. Owner: Roma.
POST /api/webhooks/resend— Resend signs viasvixscheme; verify per docs- Handle:
email.delivered(log) ·email.bounced(flag user → operator queue) ·email.complained(flag + hard suppress) ·email.delivery_delayed(warning log) - Idempotency via Resend
email_id
T5. Trial cascade template integration (Sub-plan 2 Step 8). Owner: Roma + Ilya.
- Ilya: 7 React Email components (T-7…T+29) с design system tokens
- Roma: wire templates к trial state machine (cron / queue / Stripe webhook based)
- Standard transactional templates (signup verify / password reset / billing receipt / tier transition) — Roma drafts, Ilya design-reviews
T6. Smoke test. Owner: Roma.
- Send test:
RESEND_API_KEY=$prod node -e 'resend.emails.send({from:"[email protected]",to:"[email protected]",subject:"smoke",text:"CONV-39 smoke test"})' - Verify в Gmail: <30s delivery · DKIM pass · SPF pass · DMARC pass
- Equivalent test
from: '[email protected]'— confirm dormant domain DKIM passes
T7. (Optional) Monitoring.
- Resend dashboard suffices Stage 1
- Bounce rate > 2% sustained 24h → manual operator investigation
Cross-references
- CONV-20 Notion Session (2026-05-07) — historical Resend lean
- Sub-plan 2 —
plans/onboarding-trial-mode.mdPart 2 Step 8 (cascade email consumer) - Workstream —
workstreams/onboarding-trial-implementation.md(blocked_by updated CONV-39) - ADR 0008 — Tier model + Tier 2+ custom-domain promise (Decision #2 scope)
- ADR 0009 — Tenancy (Organisation = email sender domain owner для Decision #2)
- ADR 0017 — Payment provider Stripe Stage 1 (line 70 cross-ref ADR 0011 separation)
- Phase 1.7.10 — Custom domain self-serve + DKIM Tier 2+ (Decision #2 trigger)
- Phase 1.8.6 — Transactional email infrastructure (consumes this ADR)
- Sub-plan 2 Step 13 — Operator playbook (bounce/complaint flag operator action)