Skip to content

Phase 2 — API, webhooks, monitoring, and dashboard (minimum sellable product) #4

Description

@joaopcm

The minimum sellable product. Roughly $30–40/month and about 6–8 weeks.

Gated on #3.

Cost discipline

Feature scope barely affects the bill; architecture does. Two rules that must hold:

  1. One long-running monolith. A continuous polling loop is the worst possible fit for per-invocation billing — 10k domains × 6 records × 6 sweeps/day is ~11M invocations/month. No Inngest, no Upstash, no serverless anything between the scheduler and the resolver. The dashboard on Vercel's free tier is fine; it's request-driven.
  2. Store changes, never observations. Logging every check result is 360k rows/day at 10k domains and turns a $20 bill into $400.

Domain profiles (packages/shared)

  • Versioned profile definitions — the record set a customer's product requires
  • Conditional requirements ("if the domain has CAA, require an issue for our CA"; "if MX is present, don't require MX")
  • Per-domain instruction rendering, including provider-specific name conventions from Phase 1

The state machine

pending → verifying → verified → degraded → failed

  • Hysteresis — N-of-M consensus across vantage points and consecutive-failure thresholds. A single resolver blip must never produce domain.failed. This is the highest-stakes correctness property in the product
  • degraded is a first-class state distinct from failed
  • Explicit, tested transition rules with a regression test per rule

Scheduler (packages/jobs + apps/api/src/jobs)

BullMQ + upsertJobScheduler, following the buckt queue/worker/scheduler split.

  • Adaptive intervalspending every 30s for 15 min, then 5 min, then hourly; verified + stable 30 days → daily; degraded every 15 min for an hour, then hourly
  • Floor every interval at the observed TTL
  • SOA serial fast path — check the zone serial before checking its records; one query instead of six. Full-verify daily regardless, since not every provider bumps it
  • Workbench mounted at /admin/jobs for queue visibility

API (apps/api)

Hono, /v1 prefix, route-per-file with colocated .spec.ts, Stripe-shaped { data, error, meta } envelope.

  • POST /v1/domains — register a domain against a profile, with external_id so customers don't need a mapping table
  • GET /v1/domains/:id — status, per-requirement results, observed values, diagnosis codes, rendered instructions
  • POST /v1/domains/:id/checksinteractive mode: cache-busting, authoritative-first, sub-second
  • GET /v1/domains/:id/timeline — record change history
  • GET /v1/dns/lookup — raw lookup by vantage point (the escape hatch)
  • Idempotency keys, scoped API keys, Redis-backed rate limiting

Webhooks (packages/webhooks)

  • Own sender, standard-webhooks wire format (webhook-id, webhook-timestamp, webhook-signature, HMAC-SHA256). Svix authored the spec, so swapping in Svix later changes nothing for our customers
  • deliveries table, exponential retry, circuit breaking on dead endpoints, replay endpoint
  • Events: domain.verified, domain.verification_failed, domain.degraded, domain.regressed, domain.record.changed, check.completed — each carrying reason, diagnosis codes, and observed values

Persistence (packages/db)

  • domainslast_checked_at / last_result updated in place
  • record_changes — append only on actual change
  • checks — day-partitioned, 7-day retention, auto-dropped
  • Drizzle + uuidv7() text PKs + pgEnum for the state machine

Dashboard (apps/web)

Full dashboard — front-end work is cheap here, so no need to cut it.

  • Domain list with status and filtering
  • Domain detail: rendered instructions, per-requirement diagnosis, observed vs expected
  • Change timeline
  • Profile management
  • API keys, webhook endpoints and delivery log, billing (Better Auth + Stripe)

Also

  • @propgate/sdk — TypeScript only, generated from OpenAPI
  • SandboxPOST /v1/domains/:id/simulate { state } plus deterministic fixture domains. Nobody in this space has testability; it's a real differentiator
  • Three resolver vantage points on cheap VPSes (~$12/mo total — spatial consensus plus temporal consensus is what makes the hysteresis good)

Acceptance criteria

  • 2–3 design partners onboarded and running against production
  • A regression cannot fire a false failed webhook (proven by test)
  • Infra bill stays under $40/month
  • Single-box deployment is acceptable here only because v1 is read-only: if we go down, the customer falls back to their own polling

Metadata

Metadata

Assignees

No one assigned

    Labels

    epicPhase-level tracking issuephase-2API, webhooks, dashboard

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions