Skip to main content

Bulk personalization

Problem: You have a CSV of 50 prospects. Produce one personalized follow-up deck per prospect, using an uploaded brief as the content source and the team’s default brand kit. Collect the canvas URLs when they’re done.

Primitives

  • POST /v1/uploads — upload the shared brief (once)
  • POST /v1/tasks — fan out one task per prospect with idempotency_key
  • callback_url webhook — receive terminal state per task (preferred) OR polling pool (fallback)

TypeScript (Node 20+, fetch)

Webhook handler (same shape as webhook-receiver.md) looks up event.data.id in tasks.json, matches it to a prospect, writes the canvas URL to a CRM / database / Slack.

Python (httpx)

Polling fallback (no webhook server)

If you can’t run a webhook receiver, poll all 50 tasks in a concurrency-capped pool instead:

Gotchas

  • idempotency_key per prospect + run. "prospect-deck:{id}" works if each prospect only gets one deck per logical run. If you re-run monthly, include the month: "prospect-deck:{id}:2026-04".
  • Rate limits are real. If you fan out 50 tasks in a tight loop, you may hit a per-key or per-team cap. Back off on 429 using Retry-After. Serialize if recurring.
  • brand_kit_id is the team default by default (so you can technically omit it). Passing it explicitly makes the code’s intent clear and future-proofs it if the team adds more kits.
  • Don’t poll AND have a webhook. Pick one. Doubling up wastes your rate budget and may trigger two Slack pings.
  • On task failure, you need a way to surface which prospect failed. The idempotency_key or the mapping file is your link.

See also