A company run by 20+ workflows, with a gate in front of every email

Five entry points, one owner per contact, a database-side send gate that refuses unsafe mail, and observability that notices when a job stops.

Role
Founder-engineer: architecture, build, operations
Period
2026 – present
Status
In production
Stack
  • n8n
  • Supabase · Postgres
  • Cloudflare Pages
  • Firebase Auth
  • AWS SES
  • Gemini
DreamFoxVerse automation backbone Architecture diagram. Steps: Five entry points (forms · inbox · payments); Classify and score (Gemini); One customer record (Supabase Postgres); Owning engine (nurture · outbound · care); Send gate (Postgres function); Email delivered (AWS SES · DKIM); Watchdog (liveness every 30 min); Central error handler (dedupe · P1 email); Refusal recorded (P1 if gate is blind); Daily digest (all green only if proven). Connections: Five entry points to Classify and score; Classify and score to One customer record; One customer record to Owning engine; Owning engine to Send gate; Send gate to Email delivered [allow]; Send gate to Refusal recorded [deny]; Owning engine to Central error handler [on failure]; Watchdog to Central error handler [reads]; Watchdog to Daily digest [daily]. GUARDS OBSERVABILITY allow deny on failure reads daily Five entry pointsforms · inbox · payments Classify and scoreGemini One customer recordSupabase Postgres Owning enginenurture · outbound · care Send gatePostgres function Email deliveredAWS SES · DKIM Watchdogliveness every 30 min Central error handlerdedupe · P1 email Refusal recordedP1 if gate is blind Daily digestall green only if proven
The main path runs left to right. Every engine sends through one gate; failures and stalled schedules surface through the error handler and the watchdog.

The problem

DreamFoxVerse is run by one person. Every lead, reply, payment and client brief has to be handled within minutes, at any hour, without a team, and without the automation ever mailing the wrong person or quietly breaking.

The system

  1. Five entry points (free-audit form, contact form, inbound email, payment webhook and a paid client portal) classify the person and write to one customer record in Postgres.
  2. One engine owns each contact at a time: nurture, outbound, support or client messages. Ownership is recorded, so two engines cannot work the same person.
  3. Every send passes a send gate implemented as a Postgres function: opt-out, bounce suppression, ownership, cooldown and a daily cap, and marketing is refused for anyone on an active paid plan.
  4. A central error handler records every failure. A watchdog checks every 30 minutes that each scheduled workflow actually ran, and the daily digest only reports all green when it has seen the whole window.

Key decisions

Fail closed at the gate

WhyIf the database cannot be reached, the email is not sent and an alert is raised.

Trade-offA legitimate email can be delayed; mailing someone who opted out cannot be undone.

Assert side effects, not green runs

WhyWorkflow nodes set to continue on error look successful while failing. Checks read the written row, the delivered email and the HTTP status instead.

Trade-offSlower tests that touch real systems, and they found failures a green dashboard had hidden for weeks.

Liveness from the schedule’s longest legitimate gap

WhyA weekday job legitimately pauses for 72 hours over a weekend; alerting on its average cadence cried wolf every Saturday.

Trade-offSlightly later detection for weekday-only jobs.

Put guards in the database, not in each workflow

WhyA drip that is already waiting resumes with its old workflow snapshot. A Postgres function or trigger applies to every writer immediately.

Trade-offLogic in SQL needs its own migrations, rolled-back dry runs and revert scripts.

Results

  • 20+ production workflows share one send gate and one error handler
  • 25+ live end-to-end checks that read real side effects
  • Each guard proven by reverting its fix and watching the test fail

What I’d do next

Move the workflow logic that matters most into versioned, unit-tested code, and replace execution-log polling with a single run-log table.