Email capture — backend choice and setup¶
Status: code is live and provider-agnostic; the account is not created yet.
Covers: Todos.md AUG-02 — capture form works, UTM tagging in place, one test email lands.
Symlinked into the website repo as website/EMAIL_CAPTURE.md.
1. Why the site no longer takes pre-orders¶
Removed on 2026-08-09, on the strength of two risks the launch plan already names:
- §13 risk 12 — full pre-orders instead of refundable deposits. Every pre-order spends a highest-intent buyer who would otherwise be day-one Kickstarter velocity; the ~120-day chargeback window on an August payment shipping in March invites rolling reserves or frozen funds exactly when the manufacturer needs paying; and the FTC 30-day rule turns every schedule slip into a refund obligation.
- §13 risk 18 — site checkout splits traffic during the campaign. Site orders don't count toward the total that makes a campaign look successful.
An email list has neither problem, and it's the asset Kickstarter doesn't give you back (§13 risk 18 again: "no list, no pixel, no repeat-purchase path").
The /order page is deleted. /order and /order?option=N now redirect to /#notify
rather than 404, because those URLs are already in circulation.
2. Recommendation: Kit (formerly ConvertKit), free plan¶
| Cost | $0 up to 10,000 subscribers, permanently, no card |
| Sending | Unlimited emails on the free plan |
| Fits the plan | The gate is 2,500+ emails, checked 2027-02-14 (moved 2026-08-21 from Oct 31). Free covers 4× that. |
| Setup | Create form → copy the numeric id → set one env var. No server, no key in the bundle. |
Why it wins here. The requirement is not "collect addresses" — it's "collect addresses and then email all of them on launch day." That rules out form-only services (Formspree, Basin), which give you a spreadsheet and no way to send. Kit is a real ESP with deliverability, one-click unsubscribe, and CAN-SPAM/GDPR compliance handled, and its free tier is the only one large enough to carry the whole pre-launch without a bill arriving mid-campaign. It's also creator-native, which matches §8 — the creator outreach and the list are the same motion.
Free-plan limits that don't bite us: 1 form, 1 landing page, 1 automation, Kit branding on emails, no third-party integrations. We need exactly one form, we POST to it from our own markup rather than through an integration, and Kit branding on a pre-launch update email costs nothing.
The one limit to verify (see §4): whether custom fields are writable on the free plan. The
UTM data rides in fields, and Kit silently drops fields that don't exist on the account.
Alternatives, ranked¶
| Service | Free tier | Verdict |
|---|---|---|
| EmailOctopus | 2,500 subscribers / 10,000 emails per month | Solid fallback. Clears the gate with nothing to spare — a good month puts you over it. |
| Buttondown | 100 subscribers | Lovely tool, tier is a rounding error against a 2,500 target. |
| MailerLite | 250 subscribers / 2,500 emails per month | Was 1,000; cut hard in mid-2026. Disqualified. |
| Formspree / Basin | ~50 submissions/month | Form relay only — no sending. Wrong shape and too small. |
| Firestore (already on Firebase) | ~20k writes/day | No new vendor, but you own spam filtering, double opt-in, unsubscribe, and deliverability — and still need an ESP to send. Only worth it as a secondary raw write alongside Kit, if you want the addresses somewhere you control. |
3. Setup¶
- Sign up at kit.com, free plan.
- Grow → Landing Pages & Forms → New → Form → Inline. Content is irrelevant; we never render Kit's markup, only POST to its endpoint.
- Copy the numeric form id from the embed snippet (
data-uid, or the digits inapp.kit.com/forms/<ID>/subscriptions). - In
website/,cp .env.example .env.localand setVITE_KIT_FORM_ID=<ID>..env.localis gitignored via*.local. The id is public by design — it ships in the bundle and is not a secret. - Settings → Custom fields, create these exactly:
interest,utm_source,utm_medium,utm_campaign,utm_content,utm_term,referrer,landing_path. - Turn double opt-in OFF — form Settings → Incentive Email → "auto-confirm new members". Kit defaults it on, and on Kit that default is expensive: unconfirmed subscribers cannot be emailed and are hidden from the default Subscribers view. Every signup that never clicks the confirmation link is therefore invisible to you and unreachable on launch day — a pure subtraction from the 2,500 the 2027-02-14 gate counts. The cost of turning it off: typo'd and junk addresses enter the list, and the campaign-launch send is the one email where domain reputation really matters. The honeypot in the form absorbs most bot traffic, and Kit's own bounce handling absorbs the rest, so this is the right trade for a list that exists to be mailed once. Revisit if bounce rate on the first broadcast is ugly.
- Settings → Email → set your postal address. US CAN-SPAM requires a physical address in every commercial email regardless of where you sell; Kit adds the unsubscribe link itself but won't invent the address.
npm run build && npx firebase deploy.
4. Acceptance test — this is what closes AUG-02¶
Visit, in one go:
Submit a real address, then check in Kit that the subscriber arrived and that
utm_source=test is stored against them. Then:
- Subscriber appears under Confirmed, not Unconfirmed. If it's Unconfirmed, §3 step 6 hasn't been applied — see the troubleshooting note below.
-
utm_sourceetc. are populated, not blank. If they're blank, the free plan isn't storing custom fields — fall back to encoding the source as a Kit tag instead, or concatenate everything into one field. Do not ship the form believing attribution works when it doesn't. - Click a pricing tier's Notify me first, then submit:
interestshould readstarter/complete/whole-home/anchor-2pack. -
impulsestore.net/orderredirects to the form rather than 404ing.
Troubleshooting: "it returned success but nobody is in my dashboard"¶
Hit once already, on 2026-08-09. The POST is fine — Kit's default Subscribers view hides unconfirmed subscribers, and with double opt-in on (Kit's default) that is where every new signup lands. They are in the account, just filtered out.
- To see them: Subscribers → change the status dropdown to Unconfirmed.
- To stop it happening: §3 step 6 — auto-confirm new members.
Fixed 2026-08-10. Auto-confirm is on; new signups land Confirmed. "nothing is lost — i didnt have any emails besides test emails anyways" — so the two days the form ran with double opt-in on cost no real addresses, which is worth recording because the alternative reading (a silently stranded list) was the working assumption for a day. The corollary is a number rather than a fix: as of 2026-08-10 the list held test subscribers only.
GATE-EMAILSstarts from approximately zero, and the first real count is still unlogged.
A success response looks like this, and all three fields are normal:
{"consent":{"enabled":false,"url":"https://app.kit.com/forms/consents/…"},
"status":"success",
"redirect_url":"https://app.kit.com/forms/success?form_id=9"}
The consent object is a red herring. It's Kit's optional GDPR consent-capture step, it is
reporting enabled: false, and it gates nothing. The URL resolves to a consent page if you visit
it manually, which is what it's for — it is not a step the subscriber was required to complete and
it is not why the dashboard looked empty. Ignore it.
4a. Reading the bundle-demand numbers¶
The interest field records which Notify me button the subscriber clicked, so the list doubles
as a demand signal for the ladder. To turn it into a count you can read at a glance:
Grow → Subscribers → Segments → New segment, filter interest is starter. The segment
header shows a live subscriber count. Repeat for complete, whole-home, anchor-2pack. Four
segments, and the numbers sit on one screen from then on.
Two caveats before you act on the split:
- Blank
interestis the majority case, not an error. Anyone who used the hero button, the nav button, or scrolled to the form counts as undeclared. Only people who came via a pricing tier carry a value, so read the split as relative preference among those who expressed one, never as a share of the list. - It counts submissions, not clicks. Someone who clicks Notify me on Whole home, reads the price, and closes the tab is invisible here. That's a different and arguably more interesting number — see §7.
5. How the code is arranged¶
| File | Role |
|---|---|
src/lib/subscribe.ts |
The entire provider integration. Swapping services is an edit here and nowhere else. |
src/lib/attribution.ts |
Reads UTM params + referrer at boot, stashes them in sessionStorage, first-touch wins. |
src/components/home/EmailCapture.vue |
The form. Knows only subscribe(). |
src/main.ts |
Calls captureAttribution() before the router touches the URL. |
Three deliberate choices:
- Attribution is captured at boot, not at submit. The visitor reads the whole page and may
detour through
/privacyfirst; by submit time the query string is long gone. - First touch wins. A creator's
utm_sourcesurvives a later untagged visit from history. - An unconfigured form fails loudly. With
VITE_KIT_FORM_IDunset,subscribe()returnsunconfigured, logs an error, and the user sees a failure. The previous version of this component setsubmitted = trueand stored nothing — it had never captured an address.
Swapping provider¶
Replace the fetch in subscribe.ts. Everything else stays.
// EmailOctopus (LIST_ID public; API key must NOT ship — use their embed endpoint)
POST https://emailoctopus.com/lists/<LIST_ID>/members/embedded/…
{ email_address, fields: { … } }
// Buttondown
POST https://buttondown.email/api/emails/embed
form-encoded: email, embed=1
// Firestore (already a project dependency via firebase-tools)
addDoc(collection(db, 'signups'), { email, ...attribution, at: serverTimestamp() })
6. Pricing is now declared twice¶
PricingSection.vue mirrors the crowdfunding ladder in launch-plan.md §4. The plan is the
authority; the component is a copy. If the ladder changes there, that component changes in the
same edit — a site advertising a price the campaign doesn't honour is the worst version of this
drifting.
What the site shows, as of 2026-08-09:
| Tier | Pre-order | Retail | Off |
|---|---|---|---|
| Starter — watch + 1 anchor | $135 | $199 | 32% |
| Complete — watch + 3 anchors | $209 | $299 | 30% |
| Whole home — watch + 5 anchors | $269 | $379 | 29% |
| Anchor 2-pack (add-on) | $69 | $99 | 30% |
| Early bird starter (cap 400) | $119 | $199 | 40% |
Retail is the struck-through number and the pre-order price is the headline, which is §4's stated pricing discipline — "frame the campaign price explicitly as X% off the $199 retail so retail never reads as a hike."
Two deliberate departures from a straight transcription:
- The old "Watch Only $99" tier is gone. §4 says "Never sell the watch alone" — one anchor is the minimum for a working Sunrise Lock. The site had been offering exactly that SKU, along with an "Anchor Only $75" that matched no row in the plan.
- Founder's edition ($369, cap ~100) is not on the site. §4 caps the ladder at six tiers because decision paralysis is a defining trait of the beachhead audience, and the founder's tier is campaign-native (dev channel, contributors listing) rather than something to sell pre-launch. Add it to the Kickstarter page, not here.
The early bird appears as a callout under the grid rather than a fifth card. Pre-launch its job is to make joining the list worth something; the "X of 400 left" counter §4 wants only exists once the campaign is live.
7. Still open¶
- No deposit mechanism.
launch-plan.md§13 risk 12 prefers refundable deposits over both pre-orders and a bare email list, and the 2027-02-14 gate counts 250+ deposits separately from the 2,500 emails. This change removes pre-orders and does not add deposits — that's still an open build, and Kickstarter's own pre-launch page may be the right home for it rather than the site. - Section order changed 2026-08-09. The email capture now sits above pricing and carries the blue grid background pricing used to have; pricing is plain white. The capture is the only conversion event on the site until the campaign opens, so it gets the visual weight.
- Click-level bundle demand is not measured. §4a's
interestfield only sees people who finished the form. Counting clicks on each tier's Notify me — including the ones who never submit — would need a write path Kit can't provide. The cheapest version is a POST to the Firestore REST API (firestore.googleapis.com/v1/projects/<id>/databases/(default)/documents/…), which needs no new npm dependency, just a rule allowing anonymous create on one collection and nothing else. Costs to weigh first: an unauthenticated write path is spammable, which makes the tally directional rather than trustworthy, and collecting it needs a line in the privacy policy. Not built — the Kit field covers the question as asked.