# 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** — the same setting that chooses between public and invite-only sign-up. 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.

### UI

In the management App (`app.lessly.com`), set your product's sign-up mode to waitlist, and set double opt-in and the retention window.

### MCP

Ask your agent:

```text
Put Acme on waitlist sign-up, with double opt-in on.
```

It calls [`waitlist_config_upsert`](/reference/mcp-tools/waitlist_config_upsert); [`waitlist_config_get`](/reference/mcp-tools/waitlist_config_get) reads the settings back.

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.
- **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.

## 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.

### UI

The management App gives you the script tag for your product's waitlist form. Copy it from there.

### MCP

Ask your agent:

```text
Give me the waitlist widget snippet for Acme.
```

It calls [`waitlist_embed_get`](/reference/mcp-tools/waitlist_embed_get).

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

The waitlist view is a funnel of four states.

| 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 |

### UI

Open the waitlist view in the management App. It shows the counts, and lets you filter the list by state, by when someone joined, and by where they came from.

### MCP

Ask your agent:

```text
How many people are on the Acme waitlist, and how many have accepted an invitation?
```

It calls [`waitlist_signups_funnel`](/reference/mcp-tools/waitlist_signups_funnel) for the counts and [`waitlist_signups_list`](/reference/mcp-tools/waitlist_signups_list) for the entries themselves.

Filtering by where someone came from is the practical way to invite a cohort: everyone from a particular campaign, or everyone who joined in the first week.

## Invite a cohort

1. Select a group — a filter, a page of the list, or individual entries.
2. Send the invitations. Sending is done in batches, so a launch is a handful of clicks rather than one message at a time.
3. Each entry moves to **invited**.

### UI

In the waitlist view, select a group — a filter, a page of the list, or individual entries — and send the invitations.

### MCP

Ask your agent:

```text
Invite the first 200 pending signups on the Acme waitlist.
```

It calls [`waitlist_invites_send`](/reference/mcp-tools/waitlist_invites_send).

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 you can revoke it, which returns the entry to pending and lets you send a fresh one later; an expired invitation behaves the same way. Neither 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.

### UI

Erase the entry from the waitlist view in the management App.

### MCP

Ask your agent:

```text
Erase the Acme waitlist signup for ada@example.com — they filed a deletion request.
```

It calls [`waitlist_signups_erase`](/reference/mcp-tools/waitlist_signups_erase).

> **DANGER**
> Erasing an entry purges the person's data exactly as an erasure does anywhere else in the directory, which is what a data-protection request needs. The consent record for that signup goes with it, and 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, described in [Migrate users in and out](/ship/users/migration). In waitlist mode 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, set the sign-up mode back to **public**. 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.

## Verify it works

1. Submit a test address through your form or the widget.
2. It appears in the waitlist view as **pending**, with the referring page and the campaign parameters it arrived with.
3. Invite it, accept the invitation, and the same entry moves to **registered** — keeping the id it was given at signup.

## 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.
