Skip to content
All work

Dubai-based forex broker · 2026

Campaign delivery console

An internal Next.js console replacing a spreadsheet send-ledger — with role-based access, audit logging and per-message funnel reporting.

Simplified for publication. Identifiers, endpoints and customer data removed.

The problem

Campaign state lived in a spreadsheet that the automation both read and wrote. It hit the provider's read-rate ceiling under load, and concurrent runs could send the same customer the same message twice. There was also no way to answer basic questions: which template failed, and why.

What I built

  • 01

    Moved the send ledger into a relational schema with a uniqueness constraint across channel, recipient, campaign and date

  • 02

    Made dispatch claim-by-insert: the workflow inserts first and only sends if the insert created a row, so duplicates are impossible by construction rather than by check-then-act

  • 03

    Separated status events into an append-only table fed by delivery webhooks, giving a real funnel from accepted through delivered, read and replied

  • 04

    Built attribution across three databases in application code rather than cross-database SQL, keeping each connection read-only and independently replaceable

  • 05

    Masked contact details server-side so raw customer data never reaches the browser, including for read-only share links

  • 06

    Added a Claude-powered analyst over the campaign data with real guardrails — an access allow-list, a daily query quota, prompt caching to control cost, and a route that shows users exactly what the model can see

Outcome

  • The spreadsheet rate-limit failure mode was eliminated entirely

  • Duplicate sends became structurally impossible rather than merely unlikely

  • Per-template funnels and a failure-reason breakdown replaced manual reporting

  • Read-only share links let stakeholders see performance without an account and without exposing contact data

  • The analyst answers questions over aggregates only — raw contact rows are never sent to the model

Stack

  • Next.js
  • MySQL
  • NextAuth
  • Claude
  • Docker
  • GCP
  • Recharts

Want something like this built?

Most of this is portable. The dedup, throttling and failure-handling patterns apply to any channel you send on.

Book a call