Your first campaign — audience, import, compose, send
Send your first broadcast — create an audience, import or paste in your list, compose the email, test it, then approve and fan out. Both the guided in-app flow and the CLI path.
A campaign is one-to-many email: authored, reviewed, then fanned out to an audience. This is the highest-blast-radius thing you can do, so it's built around two locks — content review and a send capability — and an idempotent ledger that cannot double-send. This guide takes you from an empty Lists section to a sent broadcast.
Before you start, you need a verified sending domain (connect your domain first). To confirm you're ready to send a broadcast — and see exactly what's missing if not — ask:
tinbox get-send-readiness --lane broadcast --json
It returns an ordered checklist with fix hints and deep links for the broadcast lane. The in-app campaign flow shows the same readiness inline.
The guided flow (in the app)
From an empty Lists section, New campaign opens a short guided flow that sequences the prerequisites instead of assuming them:
- Pick the kind — broadcast (one email to a whole list), personalized (the same campaign, rewritten per recipient), or cold outreach (1:1-at-bulk to people who haven't opted in — that's a different setup). For a first send, pick broadcast.
- Readiness — the flow shows the broadcast checklist inline; unmet items link to their fix. You can proceed on soft warnings; hard blockers (no verified domain) show the fix in-flow.
- Audience — pick an existing list or create one inline. The import hub is embedded here, so pasting or uploading a list is one step, not a settings detour.
- Compose — subject and body, with template suggestions for first-timers.
- Review and schedule — send now or schedule; the same review/approve mechanics as the direct path. The end state deep-links to the campaign's detail view.
The wizard is the first-run ramp — it's skippable at every step, and the direct create path is always there for repeat sends. Wizard state lives in the URL, so a refresh mid-flow loses nothing.
The same path, on the CLI
Every step has a CLI equivalent — useful for scripting, or for an agent doing the work.
Create an audience
tinbox create-audience --name "Newsletter" \
--fromName "Acme" --fromEmail news@acme.dev --replyTo hello@acme.dev
Add --doubleOptin to require subscribers to confirm by email before they count
as subscribed.
Bring in your list
Paste or upload a CSV in the import hub, add people one at a time, or import from an existing tool.
Add a single subscriber:
tinbox add-subscriber --audienceId aud_… \
--email ada@example.com --name "Ada Lovelace" --tags vip
Import a whole Mailchimp audience (members, merge fields, and tags) — the API key and every fetch run locally on your machine, never on the worker:
tinbox import mailchimp --api-key <key-usXX>
Imports are idempotent: re-running updates or skips, never duplicates. See Import existing mail and lists for Gmail, IMAP, mbox, and campaign imports.
List your audiences and their member counts any time:
tinbox audiences list --json
tinbox audiences members aud_… --json
Compose the campaign
tinbox create-campaign --audienceId aud_… \
--title "March update" \
--subject "What's new at Acme" \
--content "# Hello\n\nHere's what shipped this month…"
The campaign starts in draft. You can inspect its status, ledger progress, and
analytics with:
tinbox campaigns status cmp_…
Test it first
Send a single safe preview to yourself before the real fan-out. It never touches the recipient ledger:
tinbox campaigns test cmp_… --to you@acme.dev
Approve, then send
Fan-out needs both locks. Approval is the content lock — the state machine
(draft → in_review → approved) refuses to send from draft or in_review:
tinbox approve-campaign --id cmp_…
Dispatch additionally needs the authority lock — the calling token must hold
the newsletter:send capability on a write scope. Without it the API returns
FORBIDDEN and the CLI exits non-zero with a clear authz error; the CLI gets no
special powers. With it:
tinbox campaigns send cmp_…
The fan-out is ledgered and idempotent — one row per (campaign, recipient),
enqueued to Cloudflare Queues, checked before each send. A retry or double-run
only enqueues un-sent rows, so it cannot double-send the list. Every campaign
honors one-click List-Unsubscribe immediately.
After it sends
Check how it did — opens, clicks, unsubscribes, and the recipient-ledger breakdown:
tinbox get-campaign-report --id cmp_…
Treat opens as a soft signal: Apple Mail Privacy Protection pre-fetches the pixel and inflates the number.
Next steps
- Send a newsletter — the deeper reference for audiences, capture forms, scheduling, and tracking.
- Cold outreach setup — for reaching people who haven't opted in (a separate, isolated sending path).
- Build a drip / onboarding automation — triggered multi-step sequences (welcome series, drips).