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