e11

Outreach · Owned mail and WhatsApp campaign engine

The campaign engine, owned by the operator.

Outreach is what we run when a Dhara teaser or a Discovery finding has to become a sequenced, audit-trailed campaign — without renting the transport, the handles, or the opt-out list. Owned mail through the mounted sendmail socket. Owned WhatsApp Business handles. Queue-and-drain workers we administer.

Workspace-scaffolded. Cadence as JSONB. Per-prospect alerts. 23:18 IST digest. Partner-tier today.

Surface signal

Status

PARTNER

Tenancy

Workspace-scaffolded

API

outreach-api:8050

Why this exists

The campaign tools you rent are reading every prospect you load.

Modern outreach lives on rails — SendGrid for transport, a dialer SaaS for WhatsApp, a CRM for the contact list, an enrichment vendor on top. Every name your team researches passes through someone else's network as a feature, and the unsubscribe table — the one piece of the system regulators care most about — sits in their database, not yours.

Outreach is what we run when the campaign engine has to belong to the operator. Owned mail transport via the mounted sendmail socket. Owned WhatsApp Business handles. Queue-and-drain workers we administer. Opt-out plumbing that lives in our Postgres, not a vendor's.

Self-sustained by design

Owned, not rented.

Outreach exists because the campaign layer of a serious team should not depend on a third-party SMTP reputation, a reseller's WhatsApp credentials, or someone else's opt-out table when a regulator asks for it.

01

Own mail transport.

Outbound mail leaves through `docker exec mailserver sendmail -t -i` over the mounted socket on docker-mailserver. No SendGrid, no Mailgun, no third-party reputation we don't control. The headers, the SPF, the DKIM and the bounce loop are ours.

02

Own WhatsApp handles.

WhatsApp Business numbers are registered to our Meta Business Portfolio and provisioned per-tenant. The display_name approval, the 131042 billing flag, the 131049 quality state — every gate sits on credentials we hold, not a reseller in front of them.

03

Own queue, own drain.

wa_send_queue and the email send queue live in our Postgres. Drain workers are containers we run on e11-edge. There is no SaaS scheduler deciding when your prospect hears from you — only a worker that yields to rate limits and resumes when the window opens.

04

Own opt-out.

Opt-in and opt-out land on prospects.wa_phone and prospects.email columns inside the workspace. STOP, unsubscribe, list-unsubscribe headers, ABDM registry compliance — all enforced server-side before a row leaves the queue. The do-not-contact list is a row, not a vendor support ticket.

05

Audit trail by construction.

Every send writes to wa_message_events or the email log with the tenant, the prospect, the campaign, the cadence step, and the operator who released the queue. The audit isn't a feature — it's the only way the system is allowed to write.

The primitive

Three rows you can name. Typed.

Outreach is built on a small, opinionated trio — a prospect to act on, a campaign to sequence the action, a send queue to release messages one rate-window at a time. Everything else is a worker that drains one of those three.

ι · Prospect

prospects

POST /v1/prospects

The typed row a campaign acts on. Carries email, wa_phone, opt-in/out flags, source (Dhara teaser, Discovery finding, manual import) and the workspace it belongs to. Single source of truth — every send checks back here before queueing.

κ · Campaign

campaigns

POST /v1/campaigns

The cadence JSONB and the channel mix. A campaign is a sequence of steps — email, WhatsApp, wait, branch on reply — bound to a prospect set. Edited as data, not as a flowchart, so a partner can diff yesterday's cadence against today's in `git`.

λ · Send

send_queue

POST /v1/send

Every outbound message becomes a queue row before transport. Workers drain by tenant, channel, and rate window. The thin-finding gate, the ABDM registry check, the opt-out check — all run here before the row leaves the queue.

How it fits the fleet

Where signals become sequenced messages.

Outreach sits between the producers that surface signal — Dhara, Discovery — and the operator who decides what to say next. Every adjacent tool reads or writes through the same workspace-scaffolded contract.

dhara

The audit engine produces teaser reports — risk grade, severity counts, attack surface overview. Outreach turns that teaser into the first message of a sequenced campaign, with the share link wired back to dhara for click attribution.

discovery

Surface findings and CVE matches from the intelligence flywheel become campaign seeds. A KEV match on a known prospect is a signal Outreach can route into an existing cadence, not a one-off email someone hand-writes.

alerts

Per-prospect alerts (reply received, hard bounce, opt-out fired, queue stalled) ride the alerts hub. The 23:18 IST digest summarises the day's outreach activity in a single channel post — the operator reads one message, not fifty.

kosh

Prospect lists, campaign templates and reply snippets live in Kosh tables on the tenant's own Postgres. Outreach reads through the same typed contract, so the partner can curate a list in a Kosh view and run a campaign against it without an export.

architect

Outreach publishes a widget contract — campaign status, queue depth, recent replies — that the architect canvas renders next to a matter page. The partner's review of this week's ABDM funnel sits inside the workspace, not in a separate tool.

operator

The operator UI is where humans approve releases, edit cadences, scrub a prospect, or hold a queue. Outreach exposes the API; operator renders the controls. The split keeps the engine headless and auditable.

Surfaces & contracts

Six things you actually call.

Outreach is API-first — a small, deliberately narrow surface area. Every route does one job, and the queue is the only place a message becomes a send.

POST /v1/prospects

Add or update a prospect.

Idempotent on (workspace_id, email) and (workspace_id, wa_phone). Source is required — Dhara, Discovery, manual — so the row is traceable to the signal that produced it.

POST /v1/campaigns

Define a cadence.

Cadence stored as JSONB — steps, channels, branches, wait windows. Versioned per workspace; mutating an active campaign produces a new revision rather than rewriting history.

POST /v1/send

Release a message into the queue.

Single chokepoint. Runs the thin-finding gate, the opt-out check, the ABDM registry check and the rate-window check before the row is enqueued. Returns the queue id, never the transport id.

GET /v1/wa/health

WhatsApp substrate health.

Display_name state, 131042 billing flag, 131049 quality state and webhook freshness. Surfaced verbatim from Meta — no abstraction, no smoothing, so the operator sees the gate that's actually blocking sends.

POST /v1/wa/queue

Drain control for the WA queue.

wa_send_queue rows drained by a worker that respects per-number rate windows. Pause, resume and scrub-by-prospect are exposed; partner code never writes the queue table directly.

GET /v1/digest

23:18 IST digest payload.

The day's sends, replies, bounces, opt-outs, and gated rows — by campaign, by channel, by tenant. Same payload the alerts hub posts each evening; available on demand for a partner running their own digest cadence.

Senior engineering, visible

The proofs are in the substrate.

Five decisions visible in the v2 hardening, the queue shape, the JSONB cadences and the alert plumbing — not adjectives, design choices.

Workspace-scaffolded from row one.

v2 hardening (2026-04-19) made workspace_id non-null on every table that holds a prospect, a send, an event or a queue row. RLS at the session level. There is no `WHERE workspace_id = ?` to forget — the role can't see across tenants.

Queue-and-drain, not fan-out.

Every send is a queue row a worker pulls when the rate window allows. No long-running batch jobs, no cron that fires 200 emails in a burst. A drain stalls visibly, surfaces an alert, and resumes when the gate clears — never silently drops.

Cadence as JSONB, diffed in git.

Cadences are data, not stored procedures. A partner can export a campaign as JSON, review it in a pull request, and import the next revision. The engine doesn't carry a flowchart UI to maintain.

Thin-finding gate on /send.

If the underlying Dhara teaser is empty (no severity, no surface, no claim worth making) the send is refused at the queue. The discipline is encoded — the engine will not let an operator ship a message with nothing inside it.

Per-prospect alerts and an end-of-day digest.

Replies, bounces and opt-outs fire individual events to the alerts hub the moment they happen. The 23:18 IST digest is the only daily-summary surface — no inbox-of-the-week pattern, no daily mass-email-from-the-tool.

Who this is for

Partners who own their funnel.

Outreach earns its keep when the cost of a vendor sitting in front of your prospects starts to exceed the cost of administering a queue and a mail server.

Partners running an ABDM-adjacent funnel who need owned mail transport, owned WhatsApp handles, and an audit trail that survives a regulator question.
Operators of a fleet who already have Dhara teasers or Discovery findings to act on and want the campaign engine to live next to them, not behind a SendGrid login.
Teams whose deliverability problem isn't volume — it's reputation, opt-out hygiene, and the cost of a third party silently sampling their list.
Workspaces where a single message has to clear a thin-finding gate, an opt-out check, and a registry check before it ships, and that discipline can't live in a human's head.
Engineering-led shops that would rather administer Postgres + a drain worker than babysit five SaaS dashboards and a CRM integration.

FAQ

Final friction, reduced.

Is this a customer-facing product?

No. Outreach is the operator-side engine — partner-tier today. It runs inside the fleet, exposes a small API, and is administered through operator. Customer-facing campaign UIs (if any) sit on top, not in front.

Why not just use SendGrid or a WhatsApp BSP?

Because every prospect you load passes through their network as a feature, the deliverability reputation is theirs, and the opt-out table — the one piece a regulator audits first — sits in their database. Outreach owns the transport, the handles, the queue and the opt-out so the audit trail is yours.

Where does the WhatsApp substrate stand today?

α (send), γ (/health + webhooks → wa_message_events), β.1 (prospects.wa_phone + opt-in/out + operator UI) and β.2 (wa_send_queue + drain worker + POST /v1/wa/queue) are shipped. Pipeline is E2E proven; current gating is Meta-side — display_name approval, 131042 billing, 131049 quality.

Can I run a campaign without Dhara or Discovery?

Yes — prospects can be imported manually and campaigns can be authored against any source. The thin-finding gate only fires when the cadence is wired to a Dhara teaser. Outside that, the engine is content-agnostic; the discipline you bring is your own.

Discuss Outreach

Bring your campaign engine home.

Outreach is partner-deployed today — operator-side, bundled with Dhara and Discovery in the fleet. Talk to us about WhatsApp substrate, mail transport handover, or running it for an ABDM-adjacent funnel.

Direct line

Consultation requests stay owned. We reply from e11 after reviewing fit and timing.