# How to build a pre-launch email list for a Product Hunt launch

> Collect a launch list weeks before Product Hunt, tag each signup by channel, and send the launch-day email in two waves timed to the Pacific Time reset.

Published 2026-10-25 · https://ikiform.com/guides/waitlist/product-hunt

Start collecting emails on your own page four to six weeks before launch, with a hidden source field so you know which channel each signup came from. Ask people to opt in to a launch-day email, then export the list the week before and split it into two sends: one right after the Product Hunt day begins at 12:01 a.m. Pacific, and one for the Americas in their morning.

A Product Hunt launch has a fixed date and a 24-hour window. The people most likely to try your product and leave a thoughtful comment are the ones who asked to hear from you, so the list matters more than the launch post. This page covers what changes when the list is built for that one day.

## What's different about a Product Hunt list

- **The deadline is fixed.** A normal waitlist can wait until the product is ready. Here, the list has to be exported, cleaned and loaded into your email tool before a date you picked weeks ago.
- **The day runs on Pacific Time.** Launches go live at 12:01 a.m. PT and compete for that day's rank. Your list lives in every time zone, so one send at one hour reaches half of it asleep.
- **The ask is feedback, not votes.** Product Hunt's rules forbid asking for upvotes, and doing so can get a launch disqualified. Ask the list to try the product and comment on what they'd change. It's within the rules, and comments read better than a count anyway.
- **Launch day brings a spike.** Product Hunt visitors land on your site and sign up within minutes of each other. Form limits that never mattered for weeks can matter for a few hours.

Diagram: A Product Hunt launch list from the first signup to the day after

1. Four to six weeks out, the page goes live with the form, and each channel you share it in gets its own ref value.
2. A week before, export the list, remove duplicates and split the people who opted in into two waves by country.
3. The day before, raise the form's limit for the whole form so launch traffic isn't turned away.
4. At 12:01 a.m. Pacific the launch goes live.
5. Minutes later, send wave 1 to everyone outside the Americas, where it's morning or afternoon.
6. At about 6 a.m. Pacific, send wave 2 to the Americas.
7. The next day, thank the list and compare which ref values brought the people who signed up and stayed.

Ikiform records the country of each signup, so the split needs no extra question on the form. Unknown countries go in the second wave.

## The form

Start from the email-only form in the [main guide](https://ikiform.com/guides/waitlist.md#form) and add two things: a checkbox that asks for the launch-day email, and a hidden `ref` filled from the link people arrived on.

`index.html`:

```html
<form id="launch-list" action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
  <label for="email">Email</label>
  <input id="email" name="email" type="email" autocomplete="email" required>
  <label>
    <input name="launch_reminder" type="checkbox" value="true">
    Email me the morning we launch on Product Hunt
  </label>
  <input type="hidden" name="ref" value="direct">
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden>
  <button>Get early access</button>
</form>

<script>
  const params = new URLSearchParams(location.search);
  const ref = params.get("ref") || params.get("utm_source");
  if (ref) {
    document.querySelector('#launch-list [name="ref"]').value = ref.toLowerCase().replace(/[^a-z0-9_-]/g, "").slice(0, 40);
  }
</script>
```

- The checkbox starts unticked. A launch-day email is a separate thing from early access, so people opt in to it. Ikiform stores `true` or `false` with the signup's timestamp.
- The script copies `?ref=` or `?utm_source=` into the hidden input and strips anything that isn't a letter, digit, `-` or `_`. Without either, `ref` stays `direct`.
- Product Hunt usually adds `ref=producthunt` to the links on your launch page, so launch-day signups tag themselves.

Add the field definition on the form's **Fields** tab. Ikiform then refuses posts that are missing the email, stores the checkbox as a real `true` or `false`, and caps `ref` at 40 characters so nobody can stuff it.

`Fields`:

```json
{
  "version": 1,
  "title": "Launch list",
  "fields": [
    { "id": "email", "type": "email", "label": "Email", "required": true },
    { "id": "launch_reminder", "type": "checkbox", "label": "Email me the morning we launch on Product Hunt" },
    { "id": "ref", "type": "text", "label": "Source", "validation": { "max_length": 40 } }
  ]
}
```

### One ref per channel

Decide the values before you share the link anywhere, and use them every time:

| Where you share | Link | Stored ref |
| --- | --- | --- |
| Your X or Bluesky posts | `example.com/?ref=x` | `x` |
| LinkedIn | `example.com/?ref=linkedin` | `linkedin` |
| A community post, such as Indie Hackers | `example.com/?ref=ih` | `ih` |
| Your newsletter or blog | `example.com/?utm_source=newsletter` | `newsletter` |
| Your Product Hunt launch page | Added by Product Hunt | `producthunt` |

## Get the form ready for launch traffic

The default limits suit a quiet pre-launch page. Launch day isn't quiet. Each visitor can post 5 a minute, which is plenty, but the whole form is capped at 300 a minute. A front-page launch can send more signups than that in a busy minute.

- The day before, open the form's settings and choose a Custom [rate limit](https://ikiform.com/docs/rate-limits.md) with a higher limit for the form. Put it back after the launch week.
- If you're demoing at a meetup or a co-working space, everyone shares one IP. Choose Relaxed for that day.
- A refused post gets `429 rate_limited` and a `Retry-After` header. The plain HTML form shows Ikiform's error page with a link back.
- Turn on the human check under **Spam check** if launch day brings bots. The plain form needs no change for it; see [spam protection](https://ikiform.com/docs/spam.md#human-check).

## Split the launch email into two waves

A week before launch, read the list through the [REST API](https://ikiform.com/docs/api.md#list-submissions) and split it. This script keeps the newest signup per email, drops anyone who didn't tick the box, and writes two CSV files by the country Ikiform recorded. It needs a secret key with `submissions:read`.

`waves.mjs`:

```js
import { writeFileSync } from "node:fs";
import { createClient } from "@ikiform/sdk";

const AMERICAS = new Set(["US", "CA", "MX", "BR", "AR", "CO", "CL", "PE", "EC", "UY", "PY", "BO", "VE", "GT", "CR", "PA", "DO", "PR"]);
const ikiform = createClient({ apiKey: process.env.IKIFORM_SECRET_KEY });
const seen = new Set();
const waves = { midnight: [], morning: [] };

for await (const submission of ikiform.submissions.iterate({ formId: "frm_xxxxxxxxxxxxxxxx", status: "received", limit: 100 })) {
  const email = String(submission.fields.email ?? "").trim().toLowerCase();
  if (!email || seen.has(email)) continue;
  seen.add(email);
  if (submission.fields.launch_reminder !== true) continue;
  const country = submission.meta.country ?? "";
  const wave = !country || AMERICAS.has(country) ? waves.morning : waves.midnight;
  wave.push([email, String(submission.fields.ref ?? "").replaceAll(",", ""), country].join(","));
}

for (const [name, rows] of Object.entries(waves)) {
  writeFileSync(`wave-${name}.csv`, ["email,ref,country", ...rows].join("\n"));
  console.log(`${name}: ${rows.length} people`);
}
```

- Signups arrive newest first, so a person who signed up twice is judged by their latest answer. If they unticked the box the second time, they're left out.
- Import both files into your email tool as separate segments and schedule them: wave 1 for a few minutes after 12:01 a.m. Pacific, wave 2 for around 6 a.m. Pacific.
- Run it once more on launch eve to pick up the last week of signups.

### The launch-day email

Send it from your email tool, so unsubscribes and bounces are handled. Lead with the product, link the launch page second, and ask for a comment, not a vote:

`Launch-day email`:

```text
Subject: We're on Product Hunt today

Hi,

You joined the Acme list a few weeks ago. Today we launched on Product Hunt,
and the product is open to everyone on this list first.

Try it: https://acme.example/start
Our launch page: https://www.producthunt.com/products/acme

If you have a minute, tell us on the launch page what you'd change.
Honest comments help more than anything else today.

Ada, founder of Acme
```

> Ikiform doesn't email the person who fills in the form. Notification emails go to you and your team only, so there is no autoresponder, confirmation email or double opt-in. Send those from your email tool, triggered by a [webhook](https://ikiform.com/docs/webhooks.md). If you want a welcome email the moment someone joins, forward signups by webhook to your email tool; the [main guide](https://ikiform.com/guides/waitlist.md#confirm) shows the relay.

## The day after

Export the list again with **Export CSV**, filtered to the last 7 days, and count signups by `ref`. The channels that brought people who later signed in are where your next launch post goes. Send the people who joined on launch day a short thank-you from your email tool; they found you through Product Hunt and haven't heard from you yet.

## Check it works

Do this a week before launch, not on the night.

1. Under the form's **Allowed domains**, add your site's domain, plus `localhost` while you develop. Leave it empty to accept posts from anywhere.
2. Open the page with `?ref=x` and submit. The submission shows `ref` as `x` and `launch_reminder` as `false`.
3. Submit again with the box ticked and no query string. `ref` reads `direct` and `launch_reminder` reads `true`.
4. Open **Submissions** in the dashboard. The new submission is there with every field you sent.
5. Run the wave script and check each test address is in the file you expect for its country.
6. Nothing arrived? Switch the dashboard's status filter to **Spam**. Spam gets the same success answer as a real submission.

## Gotchas

| Problem | What to do |
| --- | --- |
| `429 rate_limited` | One visitor sent more than the form's rate limit allows. Standard allows 5 a minute per visitor. Wait for `Retry-After`, or choose Relaxed or Custom for forms that post from one shared IP. |
| `422` with Unknown field for `utm_source` or another parameter | Only `ref` is defined. The script reads `utm_source` into `ref` instead of sending it, so check you didn't add another hidden input. |
| Every signup has `ref` set to `direct` | The script runs before the form exists, or the form's `id` differs. Place the script after the form and keep `id="launch-list"`. |
| A country is empty in the export | Ikiform couldn't tell where the visitor was. The script puts empty countries in the morning wave, which reaches the Americas awake and everyone else later in their day. |
| The submission is in the dashboard but no email or webhook arrived | It was stored as spam. Spam sends nothing. Open it under the **Spam** filter to see the reasons, and mark it as not spam to send its email and webhooks. |

## Questions

### How early should I start a waitlist before a Product Hunt launch?

Four to six weeks gives you time to share the page in a few places and learn which ones work. Start earlier if you have no audience yet; the list grows slowly at first.

### Can I ask my waitlist to upvote on Product Hunt?

No. Product Hunt's rules forbid asking for upvotes, and doing so can get a launch disqualified. Ask people to try the product and leave a comment about what they'd change. That keeps you within the rules and gives you feedback you can use.

### What time should I send the launch email?

Launches go live at 12:01 a.m. Pacific Time. Send one wave a few minutes later to people outside the Americas, and a second around 6 a.m. Pacific to the Americas, so most people read it in their morning.

### Does Ikiform know which country a signup came from?

Usually. Each submission records the country of the visitor's connection when it can, and it appears in the API and in webhooks as meta.country. It can be empty, so treat it as a hint.

### Is Ikiform free?

No, there is no free plan. Testing is free: you can send 50 test submissions a month from localhost before you pay. To go live, Pro starts at $6/mo for 500 submissions, or you can pay once for 10,000 submissions for $49 or 50,000 submissions for $149, with credits that never expire.

## More guides

- [Waitlist for a new SaaS](https://ikiform.com/guides/waitlist.md): Build a pre-launch waitlist on your own landing page: the form, spam protection, duplicates, confirmation emails and a clean export on launch day.
- [Beta signup with screening](https://ikiform.com/guides/waitlist/beta-signup.md): A beta application form with screening questions the server enforces, and a script that scores applicants and picks each cohort from the list.
- [Plain HTML waitlist](https://ikiform.com/guides/waitlist/html.md): A complete no-build waitlist page in plain HTML that works with JavaScript off, shows an inline thank-you when it loads, and falls back when it must.
- [Waitlist on Framer](https://ikiform.com/guides/waitlist/framer.md): Two ways to collect waitlist signups on Framer: Framer's form with a webhook to Ikiform, or a code component that keeps per-visitor limits and spam checks.
- [Mobile app waitlist](https://ikiform.com/guides/waitlist/mobile-app.md): A pre-launch waitlist for an iOS or Android app that asks which phone people use, sends iPhone testers to TestFlight and fills a Play closed test.
- [Next.js waitlist](https://ikiform.com/guides/waitlist/nextjs.md): A Next.js App Router waitlist: a server action that forwards signups with a secret key, useActionState, an optimistic thank-you and a per-visitor limit.
