Data Flow
Outbound email / SMS
Customer → POST /api/v1/emails
↓
withApiAuth:
1. Parse Bearer token
2. validateApiKey() — sha256 hash lookup in api_keys
3. Build ApiContext { orgId, apiKeyId, environment }
4. checkRateLimit(orgId) — Upstash sliding window (100/min)
↓
Route handler:
5. Check Idempotency-Key header → replay cached response if hit
6. checkUsageLimit(orgId, "email") — enforce free-tier quota
7. zod parse sendEmailSchema
8. If environment === "live": sendEmail() via SES v2
else: simulate with providerMessageId = "test_msg_..."
9. INSERT into messages (status: "sent")
10. incrementUsage() — fire-and-forget UPSERT into usage_records
11. storeIdempotency() — cache response for 24h
12. Return { id, channel, status, created_at }
Error path: on provider exception, INSERT message with status: "failed" and return 500.
Scheduled sends
POST /api/v1/emails { scheduled_at: "2026-05-01T12:00:00Z" }
↓
1. Validate + (if live) check usage quota
2. INSERT messages (status: "scheduled", scheduled_at: ...)
3. Return { id, status: "scheduled", scheduled_at }
↓
Cron: GET /api/cron/send-scheduled (every minute)
1. SELECT ≤100 messages WHERE status='scheduled' AND scheduled_at <= now()
2. For each: call provider, UPDATE status, sentAt
3. incrementUsage + fanoutWebhookEvent('message.sent')
Webhook fan-out with retries
event fires → fanoutWebhookEvent(orgId, event, data)
1. Select matching enabled webhook_endpoints
2. For each: INSERT webhook_deliveries (status: pending, attempts: 0)
3. attemptDelivery() — fetch POST + signature headers (10s timeout)
4. On success: UPDATE status=delivered, deliveredAt=now
5. On failure: attempts++, next_attempt_at = now + backoff
- If attempts >= 5 → status=failed, next_attempt_at=null
↓
Cron: GET /api/cron/retry-webhooks (every 5 minutes)
1. SELECT pending rows WHERE next_attempt_at <= now()
2. attemptDelivery() again for each
Overage reporting (monthly)
Cron: GET /api/cron/report-overage (06:00 UTC on day 1)
1. For each organizations WHERE plan_status='pro' with stripeCustomerId:
a. Read usage_records for current period, live env
b. email_overage = max(0, emails - 10_000)
c. sms_overage = max(0, sms - 1_000)
d. stripe.v1.billing.meterEvents.create(...) for each > 0
Inbound delivery status (SES → SNS → app)
SES sends email → publishes event to SNS topic
SNS → POST /api/webhooks/ses
↓
1. If SubscriptionConfirmation → fetch SubscribeURL, ack
2. If Notification:
• Parse Message JSON
• Map notificationType → status:
- Delivery → "delivered" + message.delivered event
- Bounce → "bounced" + message.bounced event
- Complaint → "bounced" + message.bounced event
- Reject → "failed" + message.failed event
• UPDATE messages WHERE provider_message_id = ...
• fanoutWebhookEvent() — POST to every matching webhook_endpoint
with HMAC-SHA256 signature
Billing: free → pro upgrade
User clicks Upgrade → POST /api/internal/billing
↓
1. Find or create Stripe customer
2. Persist stripeCustomerId on organizations
3. stripe.checkout.sessions.create(subscription, STRIPE_PRO_PRICE_ID)
4. Return { url } — client redirects
↓
Customer completes checkout
↓
Stripe → POST /api/webhooks/stripe
• constructEvent verifies signature
• checkout.session.completed → UPDATE organizations SET plan_status = 'pro'
• customer.subscription.deleted → downgrade to 'free'
Registration
POST /api/auth/register { name, email, password }
1. Bcrypt hash password
2. INSERT user
3. INSERT organization (slug = email prefix + org ID tail)
4. INSERT org_member (role: "owner")
POST /api/auth/[...nextauth] (credentials login)
1. Compare bcrypt password
2. jwt callback: look up org_member → embed orgId in token
3. session callback: expose user.id + user.orgId