ZIPPY DOCS

Email capture

Turn taps on your rich fallback page into an owned email list — consent-first by design, always-204 on the public endpoint, and erasure that is never paywalled.

Email capture

Your rich fallback page already catches the taps that can't open the app. Email capture adds one small form to it — "Get updates from {your name} ⚡" — so a visitor who was about to bounce can hand you their email instead.

That email lands in Audience → Contacts in your dashboard. It's yours: exportable as CSV, pushed to your own endpoint by webhook, deletable at any time. No algorithm sits between you and it.

Email capture is available on Hero and up, and rolls out behind the audience-capture feature flag.

Turn it on

  1. Make sure the link has its rich fallback page enabled.
  2. On the link, switch on Collect emails.
  3. That's it. The next visitor who lands on the fallback page sees the form.

The form only renders when all three are true: your plan includes audience capture, the audience-capture flag is on for your workspace, and the link has Collect emails switched on. Otherwise the fallback page renders exactly as before.

What the visitor sees

A heading naming you, one email field, and a consent checkbox that is unchecked by default:

Get updates from Ada's Studio ⚡ Drop your email — no app needed.

[email protected]

☐ I agree to receive emails from Ada's Studio. Unsubscribe anytime.

There's also a hidden website field the visitor never sees — a honeypot. Bots fill it; humans don't.

The public endpoint

The form posts to a public, unauthenticated endpoint:

POST /api/public/capture
Content-Type: application/json
 
{
  "slug": "summer-drop",
  "email": "[email protected]",
  "consent": true,
  "website": ""
}
HTTP/1.1 204 No Content

It always returns 204

Every single path returns 204 No Content. Malformed body, honeypot filled, consent missing, slug that doesn't exist, capture switched off, plan without the feature, email already on your list — all 204.

That's deliberate. A public sink that answered differently for "already subscribed" would be an email-enumeration oracle: anyone could probe whether [email protected] is on your list. So the endpoint tells the caller nothing at all, and the form thanks the visitor either way.

Two guardrails ride along with it: emails are capped at 254 characters (the RFC 5321 maximum), and anything in the honeypot field is dropped on the spot.

consent must be exactly true. Anything else — false, missing, a string — and the submission is dropped before anything is stored. There is no "implied consent" path.

The consent text is written by the server, never sent by the client. When a contact is stored, Zippy records the exact sentence the visitor agreed to, with your workspace name baked in:

I agree to receive email updates from Ada's Studio via Zippy. I understand I can
unsubscribe at any time.

That string is stored per-contact and rides along in your CSV export, so months later you can show precisely what each person agreed to. The visitor always knows who is getting their email — you, by name — because the form and the stored consent both say so.

Your contacts

GET /api/audience returns your newest contacts (up to 500) plus a total count:

{
  "count": 1284,
  "contacts": [
    {
      "id": "c_2f8a...",
      "email": "[email protected]",
      "sourceSlug": "summer-drop",
      "country": "US",
      "createdAt": "2026-07-19T14:05:40.301Z"
    }
  ]
}

sourceSlug tells you which link earned the contact, so you can see which drop, reel, or campaign is actually building your list. country is best-effort from the edge geo header and is null when unknown.

Past 500 contacts, use the export.

Export

GET /api/audience/export.csv hands you the whole list, oldest first:

email,source_slug,country,consent_text,captured_at
[email protected],summer-drop,US,"I agree to receive email updates from Ada's Studio via Zippy. I understand I can unsubscribe at any time.",2026-07-19T14:05:40.301Z

Cells are formula-neutralized, so a captured email can't turn into a spreadsheet formula when someone opens the file in Excel. Take that CSV anywhere — your ESP, your CRM, a text file on your laptop. It's your list.

Live delivery

Every genuinely new contact also fires a contact.captured webhook to your own endpoint, email included, so new contacts can land in your CRM the second they arrive. A re-submit of an email already on your list is a silent no-op and sends nothing.

Erasure is never paywalled

DELETE /api/audience/:id removes a contact. It has no tier gate and no flag gate — it works on every plan, including Free, including a workspace that downgraded away from audience capture yesterday.

DELETE /api/audience/c_2f8a...
{ "deleted": true }

This is on purpose. Erasure is a data-subject right, not a feature. Somebody asking to be removed from your list should never hit a paywall, and a creator honouring that request should never have to upgrade to do the right thing. Building capture on a plan gate while leaving deletion open is the only shape that isn't hostile.

Deleting a contact deletes the row — email, source, country, consent text, all of it.

Who's responsible for what

You are the data controller for the emails you collect; Zippy is the processor. Practically:

  • Zippy stores the consent wording, the source link, and the timestamp so you have a record.
  • You decide what you send, and you honour unsubscribes and deletion requests.
  • The form promises "updates from you." Sending something else breaks the promise the visitor actually agreed to.

Double opt-in isn't implemented yet — it's a planned upgrade, and we'd rather say that than imply a confirmation email goes out today.

Notes

  • Hero and up, behind the audience-capture flag. Deletion is every plan, always.
  • Capture only runs on default-host links (the same set the fallback page serves).
  • Your links keep redirecting forever whether or not anyone ever fills in the form — permanence isn't affected by anything on this page.
  • Pairs with audience segments: segments tell you who's tapping, capture gives you a way to reach them, and broadcasts is where you actually reach them.

FAQ

Enable the rich fallback page on the link, then switch on Collect emails. Anyone who taps your link and can't be sent straight into a native app lands on your fallback page and sees a "Get updates from you" form. Their email goes to your Audience list, exportable as CSV.

Does Zippy tell me if the email was already on my list?

No — the public capture endpoint returns 204 for every outcome, including duplicates. That's what keeps it from being used to test whether a given address is one of your subscribers. Your dashboard count and export show you the truth; the public endpoint reveals nothing.

No. A submission without consent: true is discarded before anything is written. There's no setting to turn it off, because a list built without consent is a liability, not an asset.

What happens if someone asks to be deleted?

Delete the contact from Audience → Contacts, or call DELETE /api/audience/:id. It works on every plan — erasure is never gated behind an upgrade.

Where do captured emails go?

Your dashboard, your CSV export, and your webhook endpoint. You can also email them from Zippy directly — see broadcasts, which enforces the consent record stored here and attaches a one-click unsubscribe to every message. Zippy never uses your contacts for anything else.

Read more