Get Started
Start HereBlogGuidesResourcesPricingFAQAbout Get Started
⚡ n8n Walkthrough

Build It: Lead Capture → Supabase → Welcome Email

A complete step-by-step walkthrough: capture a form submission via webhook, save it to Supabase, and send a welcome email with Resend — all in n8n.

This is the build guide. No more theory. We’re going to create a working automation end to end: a form submission comes in, the lead gets saved to a Supabase database, and they receive a welcome email via Resend. Automatically.

This is also the pattern that underpins almost every business automation. A customer takes an action → their data gets stored → they get a response. In SendJob, this is how every new customer inquiry works. In your business, it might be a quote request, a newsletter signup, a service booking, or a free trial signup. The mechanics are identical.

Follow each step in order. By the end, you’ll have a live workflow running in production.


What We’re Building and Why

The automation has three stages:

  1. Capture: a webhook receives a POST request from a form with name, email, and message
  2. Store: n8n inserts the lead into a leads table in Supabase
  3. Respond: n8n calls the Resend API to send a personalized welcome email to the new lead

This is deliberately simple. The goal is to show the full pattern without burying you in complexity. Once you’ve built this once, you’ll extend it: add duplicate detection, route different message types to different email templates, trigger a Slack notification to your team, start a follow-up sequence. Those are all just adding nodes to this foundation.


Prerequisites

Before you start, make sure you have:

  • n8n account — cloud at n8n.io (free tier is fine) or a self-hosted instance
  • Supabase project — free tier at supabase.com. If you don’t have one yet, sign up, create a new project, and wait for it to provision (usually takes about a minute)
  • Resend account — free tier at resend.com. You’ll need a verified sending domain. Resend walks you through this — it involves adding DNS records to your domain. If you don’t have a domain, you can send from the sandbox using their test domain, but real-world delivery requires a verified domain.
  • A way to send test POST requests — we’ll use curl (available on Mac, Linux, and Windows with Git Bash) or a simple HTML form. I’ll provide both.

That’s it. No paid accounts required for what we’re building today.


Step 1: Set Up the Supabase Table

Go to your Supabase project dashboard. We’re creating a leads table to store incoming form submissions.

Option A — Table Editor (no SQL):

  1. Click “Table Editor” in the left sidebar
  2. Click “New Table”
  3. Name it leads
  4. Disable “Enable Row Level Security” for now (we’ll talk about this in the troubleshooting section)
  5. Add columns:
    • name — type: text, not null
    • email — type: text, not null
    • source — type: text, default value: form
    • message — type: text, nullable
  6. Click Save

Supabase automatically adds id (uuid, primary key) and created_at (timestamptz, default now()) to every table.

Option B — SQL Editor:

If you prefer to paste SQL directly:

CREATE TABLE leads (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  name text NOT NULL,
  email text NOT NULL,
  source text DEFAULT 'form',
  message text,
  created_at timestamptz DEFAULT now()
);

Go to the SQL Editor in your Supabase dashboard, paste this, and run it.

Either approach gives you the same table. Confirm it exists in the Table Editor before moving on.


Step 2: Create the n8n Workflow and Webhook Trigger

Open n8n and create a new workflow. Name it something you’ll recognize — “Lead Capture” works.

Add the Webhook node:

  1. Click the + button on the canvas
  2. Search for “Webhook” and select it
  3. In the node settings panel on the right:
    • HTTP Method: POST
    • Path: lead-capture (this becomes part of your webhook URL)
    • Response Mode: Immediately (this sends a 200 response as soon as the webhook fires, without waiting for the workflow to complete — better user experience for form submissions)
    • Response Code: 200
  4. Click “Listen for Test Event” — this opens n8n’s test listener, which waits for an incoming request on the test webhook URL

The test URL is shown at the top of the node settings. It looks something like: https://your-n8n-instance.n8n.cloud/webhook-test/lead-capture

Copy that URL. We’ll use it in the next step.

Why the path matters: the path is the identifier for this webhook. If you have multiple webhooks in your n8n instance, each needs a unique path. Choose something descriptive and URL-safe — lowercase letters, numbers, and hyphens.


Step 3: Send a Test Request

Before adding more nodes, let’s confirm the webhook is receiving data.

Using curl:

Open a terminal and run:

curl -X POST https://your-n8n-instance.n8n.cloud/webhook-test/lead-capture \
  -H "Content-Type: application/json" \
  -d '{"name": "Jane Smith", "email": "jane@example.com", "source": "form", "message": "I need an HVAC inspection for my home."}'

Replace the URL with your actual test webhook URL.

Using an HTML form:

Create a file called test-form.html and open it in your browser:

<!DOCTYPE html>
<html>
<body>
  <form id="leadForm">
    <input name="name" placeholder="Name" required />
    <input name="email" type="email" placeholder="Email" required />
    <textarea name="message" placeholder="Message"></textarea>
    <button type="submit">Submit</button>
  </form>
  <script>
    document.getElementById('leadForm').addEventListener('submit', async (e) => {
      e.preventDefault();
      const data = Object.fromEntries(new FormData(e.target));
      await fetch('YOUR_TEST_WEBHOOK_URL_HERE', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ...data, source: 'form' })
      });
      alert('Submitted!');
    });
  </script>
</body>
</html>

After sending the request, go back to n8n. The Webhook node should show a green checkmark and the incoming data should be visible in the output panel. You should see your name, email, source, and message fields.

If nothing happened, check that you copied the test URL correctly and that n8n is still listening (the “Listen for Test Event” button should be active, not timed out).


Step 4: Add the Supabase Insert Node

Now that the webhook is receiving data, add the next step.

Click the + on the right edge of the Webhook node. Search for “Supabase” and add it.

Configure the Supabase credential:

  1. Click the Credential dropdown → “Create new credential”
  2. Name it something like “Supabase - [Your Project Name]”
  3. You need two values from your Supabase project:
    • Host: your project URL — find it in Supabase dashboard → Settings → API → “Project URL” (looks like https://abcdefgh.supabase.co)
    • Service Role Secret: in the same Settings → API page, scroll to “Project API Keys” → copy the service_role key (not the anon key)
  4. Save the credential

Configure the node:

  • Operation: Create
  • Table: leads
  • Fields to Send: Manual
  • Click “Add Field” for each column:
    • name → value: {{ $json.name }}
    • email → value: {{ $json.email }}
    • source → value: {{ $json.source }}
    • message → value: {{ $json.message }}

The {{ $json.fieldName }} syntax is n8n’s expression language. It references the fieldName property from the current item’s JSON data, which in this case comes from the webhook payload.

Run the workflow again (click “Test Workflow” or re-send your curl/form request). Check your Supabase table — you should see a new row with the data you sent.


Step 5: Add the Resend Email Node

With the lead saved, add the email step.

Click the + on the right edge of the Supabase node. Search for “Resend” and add it.

Configure the Resend credential:

  1. Click the Credential dropdown → “Create new credential”
  2. You need an API key from Resend — go to resend.com → API Keys → Create API Key
  3. Give it a name and create it. Copy the key immediately (it’s only shown once)
  4. Paste it into the n8n credential and save

Configure the node:

  • From: hello@yourdomain.com (must be from your verified Resend domain)
  • To: {{ $('Webhook').item.json.email }}

Wait — why $('Webhook').item.json.email instead of $json.email?

Because the current item at this point in the workflow is the Supabase response (the newly inserted row), not the original webhook data. You can reference any upstream node by name using $('NodeName').item.json.fieldName. The Supabase node returns the inserted record, which should include the email — but using the explicit Webhook reference is more explicit and safer.

You can also just use {{ $json.email }} if the Supabase insert response includes the email field, which it should. Either works — the explicit reference is just clearer about where the data is coming from.

Continue configuring:

  • Subject: Thanks for reaching out, {{ $('Webhook').item.json.name }}
  • Email Type: Text (or HTML if you want to write HTML email)
  • Text:
Hi {{ $('Webhook').item.json.name }},

Thanks for getting in touch. We received your message and will be in touch within 1 business day.

Here's what you sent us:
"{{ $('Webhook').item.json.message }}"

Talk soon,
[Your Name]
[Your Business]

If you want to send HTML email, switch Email Type to HTML and write proper HTML. For this walkthrough, plain text is fine and actually delivers better in some email clients.


Step 6: Test End to End

Time to run the whole thing.

  1. Make sure the Webhook node is still listening (click “Listen for Test Event” again if needed)
  2. Send a test request via curl or your HTML form — use a real email address you can check
  3. Watch the n8n canvas — each node should light up green in sequence
  4. Check your Supabase table — the new row should be there
  5. Check the inbox for the email address you submitted — the welcome email should arrive within a few seconds

If all three things happened — webhook received data, Supabase has a new row, email arrived — your workflow works.

Click on each node and review the output panel. Get familiar with what the data looks like at each stage. The webhook node shows the raw incoming payload. The Supabase node shows the response from the insert (the created row). The Resend node shows the API response (a message ID if successful).


Step 7: Activate and Go Live

Now we switch from test mode to production.

Update to the production webhook URL:

The test webhook URL only works when the editor is open. For production, you need the production URL. In the Webhook node settings, you’ll see both URLs listed. The production URL looks like: https://your-n8n-instance.n8n.cloud/webhook/lead-capture

Note the difference: webhook-test vs webhook.

Before activating, update whatever is pointing at your webhook to use the production URL. If you have a form on your website, update the fetch URL. If you built the test HTML form, update the URL there.

Activate the workflow:

Click the toggle in the top right of the workflow editor — change it from “Inactive” to “Active.” n8n will save the workflow and activate it. The production webhook URL is now live and listening 24/7.

Verify it’s working:

Submit a real form entry using the production URL. Check your Supabase table. Check the email inbox. Confirm both happened.

Then go to the n8n dashboard, find your workflow, and click “Executions.” You should see the successful execution there. This is where you’ll monitor ongoing activity and catch any failures.


Common Issues and Fixes

Webhook not receiving data after activation:

The most likely cause is that you’re still pointing at the test URL. Double-check that your form or integration is using the production URL (/webhook/ not /webhook-test/). If you’re on a self-hosted instance, also check that the n8n URL in your form matches your actual n8n domain.

Supabase insert failing — “new row violates row-level security policy”:

This means RLS is enabled on the leads table and blocking the insert. Two options:

  1. Go to Supabase → Authentication → Policies → leads table → disable RLS (easiest for getting started)
  2. Add a policy that allows inserts for the service role: CREATE POLICY "service_role_insert" ON leads FOR INSERT TO service_role WITH CHECK (true);

For a production system, option 2 is the right long-term answer. For getting this working right now, option 1 is faster.

Supabase credential not working:

Confirm you’re using the service_role key, not the anon key. They look similar but have different permissions. The service role key is longer and starts with eyJ. Also confirm the Host URL is just the base URL — https://yourproject.supabase.co — not a full API path.

Resend email not arriving:

Check your spam folder first. If it’s not there either, go to your Resend dashboard → Logs and check whether the email was accepted and delivered. Common causes:

  • Sending from an unverified domain — Resend will reject the send or the email will be blocked by receiving servers
  • The to address is incorrect — check the expression in the node
  • The API key doesn’t have send permissions — check the key’s permissions in the Resend dashboard

Email arriving but with blank personalization fields:

The expression is evaluating to empty. This usually means the field name doesn’t match. If you submitted fullName in your form but the expression says {{ $json.name }}, it’ll be blank. Check the Webhook node output to see the exact field names in your payload and match your expressions to those names.


What to Build Next

You now have a working lead capture system. Here are the natural extensions, roughly in order of impact:

Error notification workflow. Set this workflow’s error workflow to a dedicated error handler (covered in the advanced guide). When a Supabase insert or email send fails, you want to know immediately — not when a customer complains they didn’t hear from you.

Duplicate lead detection. Before inserting, add a Supabase SELECT to check if the email already exists in the table. Use an IF node to branch: if the lead exists, skip the insert and maybe send a different email (“Welcome back — we already have your info”). If they’re new, proceed with the insert and welcome email.

Lead scoring with Claude. After capturing the message, send it to the Claude API via an HTTP Request node. Ask it to score the lead’s urgency on a scale of 1-10 based on the message content. Store the score in the Supabase row. Route high-urgency leads to a priority Slack channel. This is genuinely powerful for field service businesses — a message that says “my AC stopped working and it’s 95 degrees” is very different from “just curious about pricing.”

Routing by content. Use a Switch node after the Webhook to check the source field or specific keywords in the message. Route different types of inquiries to different email templates — new customer inquiry gets one email, existing customer follow-up gets another.

Team notification. Add a Slack or email node to notify your team when a new lead comes in. Include the lead’s name, email, and message. One node, five minutes to add.

Each of these is a small addition to the workflow you just built. That’s the power of this pattern — you establish the foundation once, then extend it incrementally as your needs grow.


You’ve built your first end-to-end automation. Next up: understand the database that’s storing all that data. What Is Supabase? →

This is subscriber-only content

Get full access to every Basics, Advanced, and Walkthrough guide — plus monthly deep-dives, templates, and video walkthroughs.

Already a subscriber? Sign in →