---
number: "003"
slug: "003"
title: "From idea to waitlist with a welcome email, shipped by your coding agent"
summary: "Have Claude Code build and ship a real waitlist: Supabase stores sign-ups with a schema that lives in your repo, Resend Automations sends a welcome email and a day-3 follow-up, and Vercel hosts it. You review each step and run the irreversible commands yourself."
chain: ["Claude Code", "Supabase", "Resend", "Vercel"]
for: "Vibe coders, founders testing an idea, and designers who'd rather ship a page than mock one up"
time_to_set_up: "About 2–3 hours including DNS waits (estimate)"
cost: "$20/mo for Claude Pro if you keep Supabase, Resend and Vercel on free plans (Vercel Hobby is non-commercial; a business should budget $20/mo for Vercel Pro)"
last_verified: "Oct 2026 (desk-checked, not run end-to-end)"
date: "2026-10-14"
---

# From idea to waitlist with a welcome email, shipped by your coding agent

**Chain:** Claude Code → Supabase → Resend → Vercel

## Why now

Coding agents have been good at front ends for a while. The back end was still dashboard clicking. Two October releases change that:

- **Supabase Select (Oct 2026)** moved schema and config into your repo. **Declarative Schemas 2.0** works like this: the agent edits SQL files in `supabase/schemas/`, and the new `pg-delta` engine writes the migration. `pg-delta` is the default for new `supabase init` projects. Local Supabase can also run **without Docker** (alpha, off by default), which matters inside agent sandboxes. ([Supabase blog](https://supabase.com/blog/select-2026-build-anything), [declarative schemas docs](https://supabase.com/docs/guides/local-development/declarative-database-schemas))
- **Resend Automations** gives you event-triggered email sequences: fire `waitlist.joined` from your code and Resend runs the welcome, waits, and sends the follow-up. It's fully scriptable through the API, CLI and MCP server, and includes 10,000 runs a month free. ([Resend blog](https://www.resend.com/blog/introducing-automations), [pricing](https://resend.com/pricing))

Both tools now have official agent hookups for Claude Code, so it can do the setup with you rather than handing you a to-do list.

*Full disclosure on dogfooding:* daisychain.tools' own signup is this exact shape (a Vercel function writing to a Supabase table). The Resend welcome step is the part we haven't wired up on our own site yet.

## Who it's for

Vibe coders, founders testing an idea this week, and designers who want a live page with a real email loop, not a Figma frame that says "Join the waitlist".

## What you get

- A one-page site with an email form, deployed on your domain.
- A `waitlist` table in Supabase with row-level security on and no public access. Only your server can read or write it.
- A serverless `/api/join` endpoint that validates the email, stores it, de-dupes it, and fires a `waitlist.joined` event.
- A Resend Automation: **welcome email immediately → wait 3 days → "here's what we're building" email**, sent from your own domain.
- The whole thing in a git repo your agent can keep editing.

## You'll need

| Tool | Plan you need | Current price (Oct 2026) | Link |
|---|---|---|---|
| Claude Code | Any paid Claude plan (not Free) | Pro $20/mo or $17/mo billed annually | [claude.com/pricing](https://claude.com/pricing) |
| Supabase | Free (2 active projects, 500 MB) | $0; Pro from $25/mo | [supabase.com/pricing](https://supabase.com/pricing) |
| Resend | Free (100 emails/day, 3,000/mo transactional; 1,000 marketing contacts; 10,000 automation runs) | $0; Pro $20/mo | [resend.com/pricing](https://resend.com/pricing) |
| Vercel | Hobby for a personal project; **Pro for anything commercial** | Hobby $0; Pro $20/mo | [vercel.com/pricing](https://vercel.com/pricing) |
| A domain you control | For sending email and hosting the page | Varies | |

Swap note: Cursor or Codex can stand in for Claude Code. The Supabase and Resend MCP servers work with any MCP client.

## Setup

### 1. Start the repo and the Supabase project (10 min)

Create a Supabase project in the dashboard (Free plan) and note its **project ref**. Then:

```bash
mkdir waitlist && cd waitlist && git init
npx supabase@latest init        # new projects default to the pg-delta engine
npx supabase login
npx supabase link --project-ref YOUR_PROJECT_REF
```

### 2. Give Claude Code the right tools, with the safety on (5 min)

From the repo folder:

```bash
# Supabase: scoped to one project, read-only, database + docs tools only
claude mcp add --scope project --transport http supabase \
  "https://mcp.supabase.com/mcp?project_ref=YOUR_PROJECT_REF&read_only=true&features=database,docs"
```

Start `claude`, then inside the session:

```text
/plugin install resend@claude-plugins-official
/mcp
```

Authenticate both servers in the browser when prompted. The Supabase MCP is **read-only on purpose**. Schema changes go through files and the CLI (step 4), so every change is reviewable in git.

### 3. Write the brief once (2 min)

Save this as `CLAUDE.md` in the repo root:

```markdown
# Waitlist project: rules for the agent
- Static site in /public (plain HTML/CSS, no framework). One serverless function: /api/join.js (Vercel, Node 20, CommonJS).
- Database schema lives ONLY in supabase/schemas/*.sql. Never change the database through the dashboard, SQL editor or MCP.
  To change schema: edit the file, run `npx supabase db schema declarative sync -f <name> --no-apply`, show me the migration, wait for my OK.
- Secrets only from environment variables: SUPABASE_URL, SUPABASE_SECRET_KEY, RESEND_API_KEY. Never commit them, never send them to the browser.
- The browser only ever calls POST /api/join with {email, company}. "company" is a honeypot; if filled, return ok and do nothing.
- Do not run `supabase db push` or `vercel --prod` yourself. Print the command and ask me to run it.
```

### 4. Declare the table (agent drafts, you approve, 10 min)

Ask Claude Code: *"Create supabase/schemas/waitlist.sql per CLAUDE.md, then generate the migration."* It should produce something very close to:

```sql
create table public.waitlist (
  id uuid primary key default gen_random_uuid(),
  email text not null unique
    check (email = lower(email) and char_length(email) <= 254),
  source text not null default 'landing',
  created_at timestamptz not null default now()
);

alter table public.waitlist enable row level security;
-- No policies on purpose: anon and authenticated roles get nothing.
-- Only the server, using the secret key, can read or write.
```

```bash
npx supabase db schema declarative sync -f create_waitlist --no-apply
# review supabase/migrations/<timestamp>_create_waitlist.sql, then:
npx supabase db push
```

### 5. The endpoint (agent writes, you read, 15 min)

`/api/join.js`. This is what "good" looks like. Ask the agent to write it, then compare:

```js
const RE = /^[^@\s]+@[^@\s]+\.[^@\s]{2,}$/;
const { SUPABASE_URL, SUPABASE_SECRET_KEY, RESEND_API_KEY } = process.env;

module.exports = async (req, res) => {
  if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' });
  let b = req.body || {};
  if (typeof b === 'string') { try { b = JSON.parse(b); } catch { b = {}; } }
  if (b.company) return res.status(200).json({ ok: true });            // honeypot
  const email = String(b.email || '').trim().toLowerCase();
  if (!RE.test(email) || email.length > 254) return res.status(400).json({ error: 'Please enter a valid email.' });

  const db = await fetch(`${SUPABASE_URL}/rest/v1/waitlist`, {
    method: 'POST',
    headers: { apikey: SUPABASE_SECRET_KEY, Authorization: `Bearer ${SUPABASE_SECRET_KEY}`,
               'Content-Type': 'application/json', Prefer: 'return=minimal' },
    body: JSON.stringify({ email, source: 'landing' }),
  });
  if (db.status === 409) return res.status(200).json({ ok: true, status: 'already' }); // unique email: no second welcome
  if (db.status !== 201) return res.status(500).json({ error: 'Something went wrong.' });

  const rs = (path, body) => fetch(`https://api.resend.com${path}`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${RESEND_API_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
  try {
    await rs('/contacts', { email, unsubscribed: false });               // so broadcasts can reach them later
    await rs('/events/send', { event: 'waitlist.joined', email, payload: { source: 'landing' } });
  } catch (e) { console.error('resend', e); }                            // never lose a signup over email
  return res.status(200).json({ ok: true, status: 'joined' });
};
```

Use the Supabase **secret key** (`sb_secret_…`) or the legacy `service_role` key. Never use the publishable key here. RLS would (correctly) block it.

### 6. Verify your sending domain in Resend (10 min of work, then waiting on DNS)

In Resend → **Domains → Add domain**, use a subdomain such as `mail.yourdomain.com`. Add the records Resend shows you, exactly as shown, at your DNS host. Typically that's an MX and a TXT (SPF) record on a `send.` sub-subdomain, plus a TXT (DKIM) record. Add a DMARC record too if you don't have one:

```text
_dmarc.yourdomain.com  TXT  "v=DMARC1; p=none; rua=mailto:you@yourdomain.com"
```

Then create an API key with **Sending access** only, scoped to that domain, for production. Keep a full-access key just for your local agent session.

### 7. Build the welcome sequence (agent via Resend MCP, you approve, 15 min)

Ask Claude Code:

```text
Using the Resend tools: create two templates, "Waitlist – Welcome" and
"Waitlist – Day 3", from the copy in /emails/*.md (write that copy first, ask me to
approve it). Then create an Automation named "Waitlist welcome" with status
"disabled": trigger on event "waitlist.joined" → send "Waitlist – Welcome" →
wait 3 days → send "Waitlist – Day 3". Show me the automation JSON before creating it.
```

Copy rules worth giving the agent: send from `hello@mail.yourdomain.com`; the welcome says what they signed up for, when they'll hear next, and how to reply. Include a visible unsubscribe link in both emails.

Then, in the Resend dashboard, **fire a test event** and watch it move through each step in the observability panel. Publish (enable) the automation only after the test email looks right in your own inbox.

### 8. The page (agent, 20 min)

```text
Build /public/index.html: one screen, headline, one-line value prop, email field +
button, hidden honeypot input named "company", success/error message area.
POST to /api/join as JSON. System fonts, no frameworks, no trackers, < 30 KB.
Accessible labels, works without JS showing a plain message.
```

(If you use Claude Design, design the page there and use its Claude Code handoff instead.)

### 9. Ship (you run these, 10 min)

```bash
npx vercel link
npx vercel env add SUPABASE_URL production
npx vercel env add SUPABASE_SECRET_KEY production
npx vercel env add RESEND_API_KEY production       # the sending-only key
npx vercel --prod
```

Add your domain in Vercel → Project → Domains.

### 10. Test the loop

```bash
curl -s -X POST https://yourdomain.com/api/join \
  -H 'Content-Type: application/json' -d '{"email":"you+test1@yourdomain.com"}'
# → {"ok":true,"status":"joined"}   (run again → "already")
```

Check three things: there's a row in the `waitlist` table, a run shows in Resend Automations, and the welcome email is in your inbox (look in spam too, the first time).

## Time to set up

About 2–3 hours (estimate, not timed). Roughly an hour of agent-assisted building and an hour of you reviewing. The rest is DNS propagation, which you can't speed up.

## Cost

- **Personal project:** Claude Pro **$20/mo** + Supabase Free + Resend Free + Vercel Hobby = **$20/mo**.
- **A real business:** + Vercel Pro **$20/mo** (Hobby is for personal, non-commercial use). Supabase Pro ($25/mo) once you can't risk the free-tier pause. Resend Pro ($20/mo) past 100 emails/day.

Prices are list prices as of Oct 14, 2026.

## Gotchas

- **Supabase Free pauses after 1 week of inactivity.** A quiet waitlist is exactly the kind of project that gets paused, and a paused project means failed sign-ups. Check it weekly, or upgrade before you start promoting the page.
- **Vercel Hobby is non-commercial.** Fine for a side project, not for a company's launch page.
- **Resend Free sends at most 100 emails a day.** A launch-day spike can exceed that. Every sign-up gets a welcome, so 101 sign-ups = 101 emails.
- **Declarative schema means files only.** Changes made in the Studio or SQL editor are invisible to `pg-delta`'s diff and get silently dropped. That's why the MCP is read-only and `CLAUDE.md` says so.
- **Docker-free local Supabase is alpha** and off by default (`[experimental] stack = true` in `config.toml`). Use it in agent sandboxes. Don't depend on it for your only test environment yet.
- **No double opt-in here.** The welcome email confirms by courtesy, not by click. If you're collecting EU sign-ups or plan to sell to this list, add a confirm step (Resend Automation: wait for a `waitlist.confirmed` event).
- **Never let secrets reach the browser.** The only thing the page knows is `/api/join`.
- **MCP connections can be flaky.** If a tool disappears mid-session, run `/mcp` and reconnect before assuming the agent broke something.

## Remix

- **Add a "position in line" reply:** return `count(*)` from the endpoint and show it on success.
- **Send launch-day announcements:** your waitlisters are already Resend contacts, so a Broadcast can reach them (marketing plan limits apply).
- **Different host:** Cloudflare Pages + Functions or Netlify instead of Vercel; the endpoint is ~30 lines and ports easily.

## Freshness log

- **Oct 14, 2026:** First published. Desk-checked against the Supabase Select "Build anything" post and declarative-schema docs, Supabase MCP docs, Resend Automations post, pricing and contacts API, Resend MCP docs, and Vercel pricing (Hobby terms).

---

*Last verified: Oct 2026 (desk-checked, not run end-to-end). We checked every step, setting and price against the vendors' current docs. We have not yet run this chain live. If a step doesn't match what you see, reply and tell us. It'll be fixed in the next refresh.*
