4 calls. 1 approval. 1 launch.
Most "AI marketing" tools stop at the brief. ChiefLab closes the loop: plan → approve → publish/send → measure → recommend the next iteration. Every step is approval-gated, persisted, and reproducible. Audit log proves every step fires live in production.
The shape
1. agent calls chiefmo_launch_product → launchPack + signed reviewUrl + approval-gated publishActions
2. user clicks reviewUrl, approves → action.status: "awaiting_approval" → "approved"
3. agent calls chiefmo_publish_approved_post → real Zernio postId
agent calls chiefmo_send_approved_email → real Resend messageId
4. 24h later, Vercel cron auto-fires → chiefmo_post_launch_review pulls metrics + recommends next iteration
→ POSTs to webhookUrl if you set one Step 1 — chiefmo_launch_product
The wedge tool. Call it the moment the user has shipped something and asks for help with launch / users / marketing / Product Hunt / launch graphics / launch email / "what to do after launch."
chiefmo_launch_product({
productUrl: "https://yoursite.com",
goal: "Get our first 100 users",
channels: ["linkedin", "x", "product_hunt", "email", "landing_hero"],
tenantId: "acme-co", // optional: load stored brand context
brand: { name, audience, voice, pillars }, // OR pass inline
imagesNeeded: 1, // 0 to skip; default 1
scheduleFor: "2026-06-01T09:00Z", // optional ISO 8601
webhookUrl: "https://your-app.com/hooks/chieflab", // optional
idempotencyKey: "launch-yoursite-2026-06" // CRITICAL on retries
}) Returns:
{
"launchId": "<UUID>",
"intent": "launch_product",
"run": { ... },
"launchPack": {
"positioning": { assetId, body },
"launchAngle": { assetId, body },
"channels": {
"linkedin": { assetId, body, publishViaConnector: "zernio" },
"x": { assetId, body, publishViaConnector: "zernio" },
"email": { assetId, body, publishViaConnector: "resend" },
"landing_hero": { assetId, body, publishViaConnector: "site_builder" }
}
},
"generatedImages": [{ id, usedFor, status, generator: "gemini-2.5-flash-image" }],
"publishActions": [
{
"id": "<UUID>", // PERSISTED to actionStore — pass to publish
"channel": "linkedin",
"connector": "zernio",
"executorTool": "chiefmo_publish_approved_post", // ← what to call after approval
"status": "awaiting_approval",
"preflight": {
"severity": "medium",
"warnings": ["Publishing to linkedin via zernio — visible to your audience..."],
"recommendations": [...],
"gates": ["Requires explicit human approval before execution."]
}
}
],
"reviewUrl": "https://chieflab.io/runs/<id>?token=...", // HMAC, 7-day TTL
"trackingPlan": { metricsTrackedAfter: "24h", nextRunTool: "chiefmo_measure_launch_results" }
} Step 2 — user approves on the reviewUrl
The reviewUrl is HMAC-signed (7-day TTL). Open it in a browser. No login. The page renders the briefs + graphics + per-action preflight. User clicks Approve / Reject per action. Rejected actions can store feedback that goes into per-tenant memory for the next launch.
Programmatic alternative (when the user is the agent's own builder, not an end-user): chiefmo_approve_action({ id }). This is the same approval transition the reviewUrl page does.
Step 3a — chiefmo_publish_approved_post (Zernio)
Strict approval gate. Refuses with { reason: "requires_approval", reviewUrl } if status is anything but "approved".
chiefmo_publish_approved_post({
actionId: "<UUID from launchPack.publishActions>",
content: "<final post text — your LLM rendered this from the brief>",
platforms: [{ platform: "linkedin", accountId: "zer_acc_..." }],
mediaUrls: ["data:image/png;base64,..."], // optional — ChiefLab uploads to Zernio CDN
scheduleAt: "2026-06-01T09:00:00Z" // optional — schedule instead of fire-now
})
// → { executed: true, zernioPostId, platforms, scheduled, results }
// → action.status flips "approved" → "executed", metadata.zernioPostId persisted Get the Zernio accountIds with chieflab_list_publish_accounts({}). Connect new accounts with chieflab_connect_publish_account({ platform }) — Zernio handles the per-platform OAuth dance.
Step 3b — chiefmo_send_approved_email (Resend)
chiefmo_send_approved_email({
actionId: "<UUID — required for the gate>",
from: "Brand <[email protected]>", // must be a verified Resend domain
to: "[email protected]",
subject: "Launch day",
html: "<h1>...</h1>",
text: "Plain-text fallback for inboxing"
})
// → { sent: true, messageId, to, sentAt }
// → action.status flips "approved" → "executed", metadata.resendMessageId persisted Verify your sender domains with chieflab_list_email_senders({}). Set the workspace-scoped Resend key with chieflab_set_resend_key({ apiKey }) if not already configured.
Step 4 — chiefmo_measure_launch_results (24h+)
chiefmo_measure_launch_results({
runId: "<launchId from step 1>",
lookbackDays: 7, // 1-90, default 7
outputMode: "context" // "context" returns metrics + brief; "full" renders the recommendation
})
// → {
// totals: { posts, likes, comments, views, shares },
// perAccount: [{ accountId, profile, analytics, topPosts }],
// ga4: { connected, metrics, period, rows },
// searchConsole: { connected, metrics, topQueries },
// brief: "<recommendation brief — your LLM renders into customer-facing summary>"
// } The cron auto-fire
You don't have to remember to call measure_launch_results manually. When step 3 fires, ChiefLab records a launch_followup row scheduled for publishedAt + 24h (configurable via CHIEFLAB_REVIEW_DELAY_HOURS). The Vercel cron at /api/cron/tick (daily, midnight UTC) scans for due rows, fires chiefmo_post_launch_review for each, and POSTs the result to your webhookUrl if you set one.
The webhook payload:
POST <your-webhookUrl>
X-ChiefLab-Signature: sha256=<hex>
Content-Type: application/json
{
"event": "launch.review_ready",
"operator": "chiefmo",
"tool": "chiefmo_post_launch_review",
"launchId": "<UUID>",
"actionId": "<UUID>",
"workspaceId": "<your-workspace>",
"result": { ...same shape as the tool returns... },
"deliveredAt": "2026-05-05T09:00:00.000Z"
} Verify the signature using your workspace's webhook secret (set via CHIEFLAB_WEBHOOK_SECRET).
Hard guarantees
- No publish or send tool ever fires without
status: "approved"on the action. The gate checks the actionStore record, not just the input. Calling without an approved actionId returns{ reason: "requires_approval", reviewUrl }— never silently fires. - The reviewUrl is HMAC-signed. 7-day TTL. Opens with no login. Tampering rejects. Use
buildReviewUrl()andverifyRunToken()if you want to re-issue. - Idempotency keys cache results for 24h. Retrying a launch with the same
idempotencyKeyreturns the original launchPack — won't double-create actions, won't double-fire publishes. - Runs persist across cold starts. Supabase-backed actionStore + assetStore + runStore. Cold-start on Vercel doesn't lose state.
- Per-tenant isolation. Brand context, voice samples, memory, runs all scoped by
tenantId. Multi-tenant agent products can serve thousands of end-users from one workspace. - Webhook delivery retries with exponential backoff. 1m → 5m → 30m → 2h → 12h. Signature on every attempt.
What's verified live
Every step above has been observed firing end-to-end against production with real run IDs, action IDs, and Zernio postIds. The wedge claim isn't aspirational.
What's next
- MCP reference — JSON-RPC contract, all production tools, error codes
- Quickstart — install + first launch in 60 seconds
- For AI agents — instruction text, hard safety rules
- Examples — real launch pack JSON envelopes from production