Daisychain / Drops / 003
Drop 003 · 2026-10-14

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

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.

Claude CodeSupabaseResendVercel

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, declarative schemas docs)
  • 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, 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
Supabase Free (2 active projects, 500 MB) $0; Pro from $25/mo 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
Vercel Hobby for a personal project; Pro for anything commercial Hobby $0; Pro $20/mo 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:

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:

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

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

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

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

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:

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

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)

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)

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

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.

Get the next drop.

One workflow a week, free. Re-checked as the tools change.