# Run a waitlist

A waitlist collects people who want your product before it is open to them, and then lets them in when you decide. It is a sign-up mode of the same authentication your product will use afterwards, not a separate tool: the people who join land in the same directory, and switching to a public launch is one setting.

## Turn the waitlist on

Set the sign-up mode of your product to **Waitlist** in the **Signup** section of **Users → Configuration** — the same setting that chooses between public and invite-only sign-up, described in [Configure authentication](/ship/users/configuration). From that moment your product's sign-up form stops creating accounts and starts collecting waitlist signups. Nothing else about your integration changes: your keys, your allowed origins and the libraries you have installed all stay as they are.

Two more settings belong to the waitlist:

- **Double opt-in** — when it is on, a signup does not count until the person confirms it from an email. It costs you some entries and gives you a list that will actually open your launch mail. It is a switch in the **Waitlist widget** section of **Users → Configuration**, alongside the widget's heading and button label.
- **Retention window** — how long a signup that never became a user is kept. Entries older than the window are removed automatically, which is what keeps the spam that any public form attracts from accumulating for years. This one has no field on the configuration screen; it is set through the product's API, and is available to an agent.

## Collect signups

There are two ways to collect people, and they can both be live at once.

**Your own form.** The browser library takes an email address and submits it in waitlist mode. You style it, place it and validate it however you like; the same origin allowlist that guards every browser call guards this one, so the form only works on the sites you have listed.

**The embeddable widget.** A script you drop on a landing page — including a page you do not own the code of — that renders the form, themed for your product, into a placeholder element. It mounts into placeholders that appear after the page has loaded, so a single-page marketing site works without special handling.

Either way the form is protected like every other public entry point: rate limits, a CAPTCHA by default, and the domain rules you set in [Configure authentication](/ship/users/configuration) — an allowlist or blocklist of email domains, blocking of disposable mailboxes, blocking of `+` sub-addresses.

Where a signup came from is captured with it: the referring page and the campaign parameters on the URL. That is what lets you tell which launch post actually worked. So is the consent the person gave: the wording they agreed to, its version and the moment they agreed, kept as evidence for as long as the entry exists.

## What a waitlist record is

Someone who signs up becomes a record in your directory with a stable id, their email address and any metadata you attached — and with no way to sign in. They have no password, no linked account and no session, and nothing they can do will create one until you invite them. Sign-in attempts for that address fail the way they would for an address you have never heard of.

Because they are ordinary directory records, everything in [Manage your end-users](/ship/users/user-management) works on them: you can search them, read them, add metadata, export them and erase them. They are kept out of the default user list, so your directory still shows the people who can actually use your product.

## Watch the funnel

**Users → Waitlist** opens on the funnel: one tile per state with its count, above the entries themselves. The four states are:

| State | Meaning |
|---|---|
| Pending | Signed up, waiting |
| Invited | An invitation has been sent and not yet accepted |
| Registered | The invitation was accepted; this is now a full end-user |
| Withdrawn | The person asked to be taken off the list |

The row of buttons under the tiles filters the list by state — **All**, **Pending**, **Invited**, **Registered**, **Withdrawn** — and that is the only filter the screen has. `users_waitlist_list` takes the same funnel status, plus `confirmed` for the entries whose address never came back from a double opt-in, and `users_waitlist_funnel` is the counts on their own. The screen shows the joining date on each row but does not filter on it, and neither tool narrows by where a signup came from — the attribution is stored on the entry and read from it, not searched by.

## Invite a cohort

Tick the entries you want on **Users → Waitlist** — one at a time, or all the invitable ones on the page at once — and press **Invite selected**. Sending is done in batches of at most a hundred entries, which is also the ceiling on `users_waitlist_invite`, so a launch is a handful of clicks rather than one message at a time, and each entry moves to **invited**. Entries that have already registered or withdrawn cannot be selected: there is nowhere left for them to go.

The invitation email leads to your product's ordinary sign-up flow, carrying a single-use invitation token. The invited person sets their first credential there — a password, or whichever sign-in method you have enabled — and everything your configuration requires still applies, including a second factor if you demand one. When they finish, the record becomes an ordinary active end-user with a session, the entry moves to **registered**, and from your backend's point of view nothing distinguishes them from anyone who signed up after launch. The id they were given the day they joined the waitlist is the id they keep.

An invitation is single-use and expires. Until it is accepted it can be revoked, which returns the entry to pending and lets you send a fresh one later; an expired invitation behaves the same way. The waitlist screen itself invites and erases and nothing more. Neither a revocation nor an expiry loses the signup or its attribution.

## Handle withdrawal and removal

Every waitlist email carries a way off the list. Following it opens a page that asks for one click to confirm — the confirmation is what actually removes the person, so a mail scanner that opens links on the recipient's behalf cannot quietly drop them from your list. The entry moves to **withdrawn** and the address goes on a suppression list, which is consulted before every waitlist email, so it will not be contacted again even if it is re-imported.

You can also remove someone yourself. **Erase** on a row of **Users → Waitlist**, behind a confirmation, purges that person's data exactly as an erasure does anywhere else in the directory, which is what a data-protection request needs; the entry, its attribution and its consent proof go together, and the directory row is hard-erased behind a surviving id. `users_waitlist_erase` is the same operation.

> **DANGER**
> There is no undo. See [Data and privacy](/privacy/data-and-privacy).

## Import a list you already have

If you are arriving with a list from a spreadsheet or another tool, upload it with the same bulk import that brings in existing users: **Users → Import** with **Waitlist** chosen instead of **Users**, described in [Migrate users in and out](/ship/users/migration), or `users_waitlist_import`, which takes at most five hundred entries a call. That way each row becomes a pending entry rather than an account, with its metadata and — if your file has them — its original signup date and consent record, so the evidence you were keeping stays intact. From there they are indistinguishable from people who used your form, and you invite them the same way.

## Open the doors

When you are ready to open the doors, set the sign-up mode back to **Public** in the **Signup** section of **Users → Configuration**. Your sign-up form starts creating accounts immediately and the widget stops collecting.

Everyone still on the list stays where they are: switching the mode invites nobody and deletes nothing. Invite the remaining entries in batches at whatever pace you want, or leave them and let them sign up through the front door like anyone else — an address on the waitlist does not block a normal registration of the same address.

## Next steps

- [Configure authentication](/ship/users/configuration): the sign-up modes, the domain rules and the CAPTCHA that guard the signup form.
- [Manage your end-users](/ship/users/user-management): the directory the entries live in, and erasure.
- [Migrate users in and out](/ship/users/migration): the import file format for a list you already hold.
- [Run a sign-in flow](/ship/users/auth-flows): what an invited person goes through when they accept.
