# Waitlist with confirmation email (/recipes/waitlist)

There's no simpler Bitelio setup than a waitlist: your app tracks one event, one workflow reacts to it, and one email goes out.

## Setup

import {Step, Steps} from 'fumadocs-ui/components/steps';

<Steps>
  <Step>
    ### Create the confirmation template

    Go to **Templates → New template** and build a **Marketing** template. Wherever you want contact data to appear, drop in a `{{variable}}` placeholder:

    ```text
    Subject: You're on the list, {{firstName}}

    Hi {{firstName}}, thanks for joining the {{product}} waitlist.
    We'll let you know as soon as your spot opens up.
    ```
  </Step>

  <Step>
    ### Track the signup from your backend

    When the form is submitted, call `POST /v1/track` with your project’s public key (`pk_*`). This endpoint does not accept a secret key. A backend call lets you validate the form first, but the public key does not authenticate a customer or prove that they consented. Never expose a secret key in browser code.

    ```bash
    curl https://api.bitelio.com/v1/track \
      -H "Authorization: Bearer pk_your_public_key" \
      -H "Content-Type: application/json" \
      -d '{
        "event": "waitlist.joined",
        "email": "ada@example.com",
        "subscribed": true,
        "data": { "firstName": "Ada", "product": "Beta" }
      }'
    ```

    Only send `subscribed: true` after the person explicitly agrees to these marketing messages. This also resubscribes an existing contact, so do not repeat it on background data updates. For confirmation before consent, use [double opt-in](/recipes/double-opt-in).

    One request does two things: it upserts the contact (subscribed unless you say otherwise) and logs a `waitlist.joined` event against them. Every key inside `data` is written onto the contact, so the template can read it back as `{{firstName}}`, `{{product}}`, and so on.

    <Callout title="Pick a stable event name" type="info">
      Once a workflow has run even once, its trigger event is **locked in permanently**. Choose a namespaced name like `waitlist.joined` instead of a generic one you might later want for something else.
    </Callout>
  </Step>

  <Step>
    ### Create the workflow

    Under **Workflows → New workflow**:

    * **Trigger**: `EVENT` on `waitlist.joined`
    * Add a `SEND_EMAIL` step pointing at the template from step 1

    Flip the workflow on. New workflows start disabled, and a disabled workflow never triggers — no matter how many events arrive.
  </Step>
</Steps>

## Tagging signups for later

To make waitlist members easy to segment down the road, insert an `UPDATE_CONTACT` step ahead of the email:

```json
{ "stage": "waitlist", "waitlistSource": "{{event.referrer}}" }
```

Now a [segment](/concepts/segments) on `stage == "waitlist"` gives you a clean audience for follow-up campaigns — a tidier approach than segmenting on whether `waitlist.joined` was ever fired.

## What's next

<Cards>
  <Card title="Workflows" href="/concepts/workflows">
    How triggers behave and what each step type does.
  </Card>

  <Card title="Track event API" href="/api-reference/public-api/trackEvent">
    The complete `POST /v1/track` reference.
  </Card>
</Cards>
