Skip to content
← ALL WRITING

2026-04-22 / 12 MIN READ

How zero-touch intake to delivery works for a $129 product

Architectural walkthrough of a zero-touch intake-to-delivery pipeline for a $129 productized service: Stripe, Supabase, Resend, and a token-gated page.

At $129, there is no human in the pipeline. A buyer hits checkout while you are asleep, and 40 seconds later they are reading their delivery. Here is the architecture that makes that work, and the four places most indie builders break it.

ZERO-TOUCH PIPELINE
STEP 1/11
BUYER
STRIPE
WEBHOOK
SUPABASE
RESEND
GATED PAGE
POST checkoutintake metadata
payment OK
session.completedsigned
INSERT orderidempotent
INSERT token30d expiry
send delivery URL
inbox: 1 link
GET /deliver/<token>
verify tokenvalid / used / expired
render delivery
mark used_at
BUYERSTRIPE
POST checkout
Six actors. Eleven messages. No human anywhere in the middle.

Prerequisites

Before you wire this up, you need five things in place. If any are missing, stop and get them first.

A Stripe account with Checkout enabled. Not a Stripe Payment Link alone. Checkout gives you server-side webhook events and session metadata, which is what the rest of the architecture hangs off. A Payment Link works for manual fulfillment but not for this.

A Postgres database. I use Supabase because the Next.js integration is clean and row-level security is already there. Any Postgres works. The architecture does not care.

An email sender on a domain you control. Resend, Postmark, SES - pick one with a clean domain reputation. Sending from gmail.com through an app password is not production-grade; deliverability will bite you within the first 50 sales.

A public product page with a buy button. The page is where the buyer decides. If the page is weak, no webhook will save you. I've written about how to land the price signal for the entry tier in detail.

A clear definition of delivery. What does the buyer get, and where? A PDF download, a token-gated page, a Shopify coupon, a bundle of MDX files, a Claude Code skill pack. The architecture below assumes a token-gated page (the format I use for my product), but the shape is the same regardless.

Step 1: Design the intake capture shape

The intake shape is the smallest set of fields the product needs to deliver. For a productized stack audit, the fields I actually need are: email, store URL, and a consent flag confirming the buyer is authorized to share that URL. Anything more than that is marketing research you are asking the buyer to pay for.

You have two places to capture the fields: inside Stripe Checkout using custom fields, or on a separate form before or after the checkout redirect. I prefer Stripe Checkout custom fields for anything up to about four fields. Less friction, fewer pages, and the data lands in the checkout session where the webhook can pick it up.

Pass intake data through the Stripe session as either custom_fields or metadata. Both get echoed back on the webhook event. Metadata is simpler for programmatic values (user-agent, affiliate code, product slug). Custom fields are cleaner for buyer-facing prompts.

The Stripe session creation call is also where you set the customer_email if the buyer is logged in somewhere, or pass a correlation ID (a UUID you generate before the redirect) so you can tie the checkout back to a pre-checkout intake form if you used one. The correlation ID is the one field I never skip. It turns the whole pipeline into a single join key later.

Step 2: Wire the Stripe webhook to your fulfillment trigger

The webhook endpoint is where the pipeline earns its keep. Three things matter here and most indie implementations get at least one wrong.

Signature verification. Stripe signs every webhook with a secret you set in the dashboard. Your endpoint must verify that signature before doing anything with the payload. Skipping verification means anyone on the internet can POST a fake checkout.session.completed event to your endpoint and trigger fulfillment. This is not theoretical; it happens within hours of an unsigned endpoint going live.

Idempotency. Stripe retries webhooks. A single real purchase can produce two, three, or seven webhook deliveries over the next 72 hours. Your handler must be safe to call twice. The canonical approach is to check for a row keyed by the Stripe event ID before writing anything, and to write the order row with a unique constraint on stripe_session_id. If the event arrives twice, the second write becomes a no-op.

Write a canonical order row. When a checkout.session.completed event arrives, write a row into a orders table with the session ID, the email, the intake fields, the amount paid, and a fulfillment_status column defaulting to pending. Everything downstream reads from that row. Do not try to derive downstream state from Stripe's API on demand; Stripe's API is eventually consistent and will surprise you.

The webhook handler itself should be as thin as possible. It verifies the signature, parses the event, writes the order row, and enqueues the next step (fulfillment). Anything else (email, tokens, Slack notifications) happens in the next stage after the row is written. A fat webhook handler is the easiest single thing to break.

Step 3: Generate the delivery token

Once the order row is written, the pipeline needs a way to let the buyer into the delivery. The token is that way.

Generate a random 32-character token (crypto-strong; do not use Math.random()). Write a row into a delivery_tokens table with the token, the order ID it belongs to, an expires_at timestamp (I use 30 days; long enough that a buyer returning after a week is fine, short enough that a token that leaks isn't perpetual), and a used_at timestamp that starts null.

Link the token to the order. The token is the only thing the buyer needs to access delivery. Do not reuse the Stripe session ID, the email, or any intake value as the access key. Those values appear in a buyer's browser history, inbox, and sometimes screenshots. The token is a single-purpose credential that lives in one place.

The token enables a critical property: even if the buyer's delivery email gets deleted or never arrives, you can regenerate a new token from the order row and resend the email. The token is ephemeral; the order row is canonical. That separation is what turns a one-shot pipeline into a recoverable one.

The token is ephemeral; the order row is canonical. That separation is what turns a one-shot pipeline into a recoverable one.

Step 4: Send the delivery email and gate the page

The delivery email is the last piece of automation before the buyer sees their purchase. Send it via Resend (or whatever sender you picked) from a domain on SPF/DKIM/DMARC that you verified during setup. A fresh domain with no warm-up is a deliverability problem waiting to trigger; spin up the sender at least two weeks before launch if possible.

The email contains a single URL pointing at the gated delivery page: something like https://yoursite.com/deliver/<token>. No marketing. No cross-promotion yet. Just the URL and one sentence that tells the buyer what they're about to open. Keep it terse. Buyers at this moment are impatient to see what they bought.

The delivery page is a route on your Next.js app. When it loads with a token in the URL, it runs server-side logic that reads the delivery_tokens row, checks expires_at, checks used_at, and either renders the delivery or renders an error state. Three error states matter: expired, already used, invalid. Handle all three distinctly with helpful copy. A buyer who got a bounced email or a copied-wrong URL should see an action they can take (re-send the email, contact support) rather than a generic error.

For products like mine, the delivery page renders a token-gated report. For others it might trigger a download, render a Claude Code skill pack as MDX, or reveal a Shopify coupon code. The gating pattern is the same; the rendered content is whatever the product is.

The final move is to mark the token used_at and update the order row's fulfillment_status to delivered. Now the buyer has their delivery, the order row has a complete state trail, and your analytics can count fulfillment events accurately.

Common mistakes

Skipping webhook signature verification. I listed this in Step 2 and it earns a second mention because it is the single most common production failure. An unsigned webhook endpoint is a public API for triggering fulfillment. Someone finds it. Always verify the signature before the first line of business logic runs.

Not handling duplicate events. Stripe's retry behavior will send the same event 2 to 7 times over 3 days. An idempotency bug means one purchase generates multiple tokens, multiple emails, and multiple delivered rows. The buyer gets three copies of the same email and thinks your system is broken. Worse, if a token is consumed on the first delivery and a second copy arrives via retry, the buyer clicks the new link and hits an already-used error.

Storing the token in email only. If the email bounces, the token is unrecoverable unless you also wrote it to the database. Always write the token server-side before you attempt to send the email. Then if the email fails to deliver, you can regenerate the send without creating a new token.

No observability on fulfillment failures. A pipeline that silently fails on the 73rd customer because Resend returned a 5xx and your handler didn't retry is worse than a pipeline that never ran. At minimum: log every webhook event with status, every token generation, every email send attempt, and every gated-page access. Wire the logs to something you will actually look at. I use Supabase as the single source of truth for pipeline state and a daily check-in on recent rows to catch anomalies before the buyer emails me about them.

What to try next

Once the core pipeline is running, the natural next step is upsell choreography. A buyer who just received a $129 product is warm. The delivery email can include a teaser for the $497 tier; the delivery page itself can include a module that the higher tier extends. But do not build upsell logic before the core pipeline is rock-solid for 20 or 30 real transactions. Upsell work on top of a broken intake flow is cosmetic.

The concrete example of this entire pipeline is the DTC Stack Audit product page. The architecture described here is what runs when a buyer clicks the buy button. The methodology the audit delivers is covered in detail in the DTC stack audit walkthrough.

The $129 tier's pricing logic (why this number, not $49 or $249) is documented separately in the entry-tier pricing decision log. Together those three pieces - the pricing logic, the delivery architecture, and the methodology inside the delivery - describe the complete tier-1 move. The full tier ladder that this sits inside is the productized ladder hub.

For context on why an always-on zero-touch pipeline matters in the first place (versus hourly billing or retainer work), the post on ending hourly billing covers the business-model frame. The receipt-side copy that keeps the first buyers from asking for refunds is in my refund-proof copy notes, which closes the loop between the page promise and what shows up in the delivery email.

Frequently asked questions

Can I build this without Supabase, using just Stripe and email?

You can, but you will regret it at around the 30th customer. Without a database, you cannot regenerate a lost token, you cannot see fulfillment status, you cannot answer "did this buyer actually receive the product?" when they email you. The database is the canonical record. It takes an afternoon to set up. Skip it and you trade that afternoon for months of ongoing firefighting.

What if I do not want to use Stripe?

Any payment processor with webhook support and signature verification works the same way. Lemon Squeezy, Paddle, Polar, Gumroad. The architecture is processor-agnostic. Stripe is the default because the documentation is strong and the webhook behavior is well-understood; if you have a reason to pick another, the pattern transfers directly.

How do I handle refunds in this pipeline?

A refund event comes in as a separate Stripe webhook (charge.refunded). Your handler updates the order row's refund_status, invalidates the delivery token by setting expires_at to now, and optionally revokes access to the gated page on the next request. Refunds happen rarely at the $129 tier but the logic should be in place on day one so you're not scrambling when one arrives.

What about taxes and VAT?

Stripe Tax handles most of this for you if you enable it on the checkout session. For EU/UK VAT, enabling Stripe Tax is the lowest-effort path; rolling your own VAT calculation is a small project on its own. For US sales tax, Stripe Tax handles the automatic nexus determination and collection. The pipeline architecture does not change based on tax handling; the tax numbers just land in the session metadata and the order row.

How long does it take to build this for a single product?

With Stripe, Supabase, and Resend accounts already in place, and a clear product definition, I estimate one to two focused days. The wiring is mechanical; the hard part is defining exactly what delivery looks like, which is a product decision that should be made before coding starts.

Sources and specifics

  • The four-stage pipeline (intake capture, Stripe payment, webhook fulfillment, token-gated delivery) is the live architecture running for the $129 product tier on this site.
  • Stripe's webhook retry behavior (2 to 7 deliveries over 72 hours) is documented in Stripe's webhook docs; handling idempotency is non-optional.
  • The token expiry of 30 days is a choice I made based on typical buyer usage; shorter expiries work for single-use digital goods, longer expiries for reference products. There is no universal right value.
  • The architecture described works on Next.js 16, React 19, and Tailwind v4 as of April 2026. Older Next.js versions will need adjustments (especially around async route handlers in the App Router).
  • The concrete live instance is the DTC Stack Audit. The pipeline was built in Q1 2026 and has been running continuously since.

// related

Let us talk

If something in here connected, feel free to reach out. No pitch deck, no intake form. Just a direct conversation.

>Get in touch