Project deep dive
CABC Email Router
A production Stripe webhook processor and email routing system built for a real booster club — not a demo, not a tutorial. Designed, architected, and shipped to handle real financial transactions and automate purchase fulfillment notifications end to end.
Event pipeline
Stripe Checkout
checkout.session.completed
Webhook Handler
record → idempotency guard
Route Match
product_id → recipient config
Email Dispatch
per-recipient + attempt log
Admin Dashboard
fulfillment tracking + CSV export
Overview
The Compass Athletic Booster Club needed a way to automatically route Stripe purchase notifications to the right people. A parent buys a spirit pack, a corporate sponsor submits a contribution, a student registers for an event — each of those needs to land in a different inbox, with the right context, immediately. No manual forwarding. No checking dashboards.
CABC Email Router sits between Stripe and the people who need to act. It ingests webhook events, normalizes messy customer data from a dozen possible Stripe sources, matches each purchase to a configured route, and dispatches templated emails to the right recipients — then tracks every attempt and surfaces everything in an admin dashboard built for non-technical operators.
The system handles real money and real people. That meant idempotency, auditability, and operational observability weren't afterthoughts — they were requirements from the start.
Core Features
What it actually does
Technical Highlights
Under the hood
Architecture
- —Stripe webhook → two-phase record/process pipeline → route match → per-recipient email dispatch → attempt logging → fulfillment tracking
- —Dual-status model separating delivery outcome from business fulfillment state
- —Service client / user client separation in Supabase: service role for backend writes, anon key for auth-dependent operations
- —revalidatePath cache invalidation on all admin mutations for fresh server-rendered data
Backend
- —Single expanded Stripe API call per event: customer, payment_intent.latest_charge, and line_items.data.price.product resolved in one request
- —Fuzzy custom field matching normalizes keys across any form design variation
- —Customer name deduplication: if resolved name equals business name, returns null rather than repeating data
- —Database-layer duplicate detection via Postgres error code 23505 — no pre-query needed
- —Resend sender normalization handles accidental env var prefix duplication and quoted values
Admin UI
- —Server Components throughout with server actions for mutations — no client-side data fetching
- —Zod schema validation on every server action before any database write
- —Raw Stripe event JSON viewer on notification detail pages for debugging
- —Route editor with expandable inline forms — no page navigation required to manage configuration
Auth
- —Supabase magic link with PKCE flow: code verifier stored in server action cookies for proper exchange at callback
- —Callback route handles both ?code= (returning users) and ?token_hash=&type= (first-time confirmation) — two different URL formats Supabase sends depending on whether the user exists
- —requireAdmin() enforces email allow-list check on every protected route after session verification
Engineering Challenges
Where it got interesting
Extracting clean customer data from Stripe's fragmented model
Stripe surfaces customer information in at least ten places across a checkout session, and the right value for "customer name" depends on which fields the merchant configured and which ones the customer filled in. The normalization layer resolves a deterministic priority order across custom fields (with fuzzy label matching), the Stripe customer object, session customer_details, collected_information, and billing details — handling deleted objects, polymorphic fields, and the edge case where a sole proprietor's customer name and business name are identical.
Webhook idempotency without pessimistic locking
Stripe can and does send duplicate events. The solution uses two layers: a unique constraint on stripe_event_id catches duplicate webhook deliveries immediately, and a separate unique constraint on session_id + price_id catches cases where the same purchase appears through different events. Both are enforced at the database layer — no pre-query, no race condition.
The two-path auth callback
Supabase sends different URL formats for first-time versus returning users: ?code= for users who already exist, ?token_hash=&type= for new accounts going through email confirmation. The original callback only handled one path. Discovering the discrepancy required reading Supabase audit logs to compare user_recovery_requested versus user_confirmation_requested events, then updating the callback to branch on whichever parameter is present.
PKCE verifier storage in a server action
signInWithOtp generates a PKCE code verifier that needs to be stored in a cookie so the callback can exchange the code. Called from a Next.js server action using a client with a no-op setAll, the verifier was silently lost — causing "no auth token" at the callback for any user who requested a new link. The fix required creating the Supabase client inline in the action with a setAll implementation that actually writes to the cookie store, which is valid in server actions even though it isn't in Server Components.