Hanzo AI

Marketing

Lifecycle email: drip sequences that reach the right people.

Also for this capability: API · CLI · MCP · SDKs

Lifecycle email: drip sequences that reach the right people.

Base URLhttps://api.hanzo.ai
Operations35
AuthAuthorization: Bearer $HANZO_API_KEY

Specification

HIP-1136 · Marketing — Lifecycle Email — Draft · read the specification →

/v1/marketing is lifecycle email: drip sequences that reach the right people. Audiences resolve from the IAM roster, promo codes record redemptions an admin grants against, and every send passes through the per-org suppression list. It is implemented in hanzoai/cloud at apps/marketing (HIP-0106). The two invariants this HIP states: there is exactly one send seam, and this capability can no longer mint money.

Motivation

A marketing surface accumulates senders — a campaign blast here, a drip step there, a calendar hook — and every sender that bypasses the opt-out list is a compliance violation waiting on a query. The design forces every delivery through one function so the suppression check cannot be skipped by construction (apps/marketing/suppress.go:26-32). The second lesson was paid for: an earlier promo redemption deposited real spendable wallet credit on any validated principal's say-so — plan and seat count from the request body, nothing collecting the charge the discount was against — a self-service money mint worth about $1.79M at the cap. The deposit is gone (apps/marketing/promos.go:30-48).

Specification

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.

§1 The store, and the engine underneath

One encrypted SQLite file, the deployment's own marketing (apps/marketing/store.go:42); every query filters WHERE org=?. Drip steps are durable tasks on the embedded hanzoai/tasks engine: each enrollment's next-run time lives in SQLite, a per-minute schedule sweeps due steps, and every step is claimed once, so a redeploy neither loses nor duplicates a send (apps/marketing/marketing.go:15-20).

§2 One send seam

Every marketing delivery — campaign, drip step, calendar email — calls the one deliver function, which consults the per-org suppression list and then hands off to the platform notify rail; marketing never constructs a provider and has no second door out (apps/marketing/suppress.go:138-148). A suppressed recipient is refused at the seam, and a signed public one-click unsubscribe writes the same list. A product announcement is not a feature beside this: it is a one-step sequence with an audience enrolled, inheriting claimed-once delivery, the suppression gate and the unsubscribe footer (apps/marketing/marketing.go:42-45).

§3 Audiences are honest

An audience resolves to real mailboxes through the IAM roster; one with an event filter narrows that roster to the cohort the analytics warehouse selected. When the roster or warehouse cannot be read the answer is honestly empty — never a fabricated count, never a send to nobody reported as success (apps/marketing/marketing.go:18-23).

§4 A redemption is evidence, not money

What a promo redemption produces is a ROW — the org, the server-derived plan, the discount claimed, and when — which an admin grants against through the admin surface and the auditable ledger. The plan is derived from the org's live paid subscription and never accepted from the caller (the input carries no plan and no seats, so there is no field to inflate); an unreadable plan authority refuses, inverting the spend gate's fail-open, because failing open here would let an outage manufacture the evidence money is granted against; the payment instrument is required as the anti-farming key (apps/marketing/promos.go:39-60).

§5 The address, tenancy, money, events, observability

Thirty-five operations under /v1/marketing, every one a typed op — one registry entry projecting REST, the document, the MCP tool and the CLI (apps/marketing/marketing.go:47-52). The tenant is the org minted from the validated bearer (HIP-0026), carried to the typed seam by the bridge and read back server-side — never a header, never an In field. Free (cloud.Free, plugin/marketing/main.go); the send rail and any granted credit are other planes' ledgers. It publishes nothing on the bus and emits nothing beyond the request span every route gets.

§6 Stage and upstream

beta: a vertical application. It derives from no third-party upstream; the durable engine it composes is hanzoai/tasks (HIP-1062), embedded, not mirrored here.

Rationale

The alternative to one send seam is a suppression check in every handler, which is the same check N times until one path forgets it. The alternative to deleting the promo deposit was gating it harder — but an automatic path that creates money is not a feature to fix, it is a mechanism to remove, because a money mint left switched off is one flag away from switched on.

Security Considerations

The wrong implementation here is a spam cannon with a ledger attached. Send authority: any path around the deliver seam bypasses opt-out, so the seam is the only door and tests assert exactly which recipients reach the rail. Money: the removed deposit is the standing lesson — a redemption input that named its own plan converted open signup into self-served credit; the current shape has no field to inflate and fails closed on an unreadable authority. Tenancy: the suppression list, audiences and enrollments are all org-column scoped on the validated principal, so one tenant can neither read another's roster nor unsubscribe another's customers.

Four surfaces

SurfaceReaches this capability asCoverage
RESTmarketing at its own prefix35 operations
CLIhanzo marketing …35 of 35
SDKMarketingApi in every published client35 methods
MCPtool marketing on https://api.hanzo.ai/v1/mcp35 operations, 4 under the document's own id — ask describe for the rest

Quickstart

export HANZO_API_KEY=sk-...   # console.hanzo.ai → API keys

Then the first call — a read that needs nothing but the key. GET /v1/marketing/promos, operation get_marketing_promos:

hanzo marketing promos list

Answers 200 with object — ok.

Endpoints

EndpointWhat it does
GET /v1/marketing/audiences/{id}/previewEvaluates the cohort LIVE — the same resolution an enrollment would run — and reports how big it is and how many real mailboxes it reaches.
GET /v1/marketing/audiences/{id}Returns one of the caller org's saved audiences.
DELETE /v1/marketing/audiences/{id}Removes one of the caller org's audiences and answers 204.
GET /v1/marketing/audiencesReturns the org's saved audiences, most recently updated first.
POST /v1/marketing/audiencesSaves a cohort filter for the caller's org.
POST /v1/marketing/calendar/{id}/publishPublishes a post NOW, synchronously, whatever its schedule.
GET /v1/marketing/calendar/{id}Returns one of the caller org's posts, including the exact error behind a failed publish.
PUT /v1/marketing/calendar/{id}Replaces a post's editable fields.
DELETE /v1/marketing/calendar/{id}Removes one of the caller org's posts and answers 204.
GET /v1/marketing/calendarReturns the org's calendar, latest scheduled first, optionally narrowed to one status.
POST /v1/marketing/calendarAdds a post to the content calendar.
POST /v1/marketing/campaigns/{id}/scheduleSets a campaign's send time and moves it to "scheduled".
GET /v1/marketing/campaigns/{id}Returns one of the caller org's campaigns.
PUT /v1/marketing/campaigns/{id}Replaces a campaign's editable fields.
DELETE /v1/marketing/campaigns/{id}Removes one of the caller org's campaigns and answers 204.
GET /v1/marketing/campaignsReturns the org's campaigns, most recently updated first, optionally narrowed to one lifecycle status.
POST /v1/marketing/campaignsRegisters a campaign in the caller's org.
GET /v1/marketing/promos/{code}/eligibilityPrices a promo against a plan and seat count.
POST /v1/marketing/promos/{code}/redeemRecords the caller org's claim on a promo.
GET /v1/marketing/promos/{code}/redemptionReturns the caller org's OWN redemption of a promo — an org-scoped read, so it can never surface another tenant's.
GET /v1/marketing/promosReturns every promo the deployment offers with its live counters: how many orgs have redeemed it and how many redemptions remain under the cap.
POST /v1/marketing/sequences/{id}/enrollAdds one contact or a whole audience to a sequence and schedules the first step for each.
POST /v1/marketing/sequences/{id}/enrollments/{eid}/cancelStops one walk mid-sequence and answers 204: no further step is sent, and steps already delivered are not recalled.
GET /v1/marketing/sequences/{id}/enrollmentsReturns who is walking one sequence, most recently enrolled first, with each walk's current step and next due time.
POST /v1/marketing/sequences/{id}/statusFlips draft/active/archived — the activation gate for sending, since only an active sequence accepts enrollments.
GET /v1/marketing/sequences/{id}/stepsReturns one sequence's steps in send order.
POST /v1/marketing/sequences/{id}/stepsAppends a message to the END of a sequence: the new step's idx is one past the last, so steps arrive in the order they are added.
GET /v1/marketing/sequences/{id}Returns one of the caller org's sequences together with its steps in send order.
GET /v1/marketing/sequencesReturns the org's drip sequences, most recently updated first.
POST /v1/marketing/sequencesRegisters a drip sequence in the caller's org.
GET /v1/marketing/summaryRolls up the caller org's campaigns: how many there are, how many are active, and the summed budget and spend in cents.
GET /v1/marketing/suppressionsReturns the org's opt-out list, newest first — everyone the send gate will refuse to deliver to.
POST /v1/marketing/suppressionsRecords an opt-out for the org (admin / self-service management).
DELETE /v1/marketing/suppressionsRe-subscribes an address on one channel and answers 204.
GET /v1/marketing/unsubscribeIs the PUBLIC one-click endpoint (no principal): a recipient clicks the signed link in an email footer.

All Hanzo APIs · Interactive reference

How is this guide?