In Lovable, an AI agent is an Edge Function you describe in chat rather than code you write. This guide builds an onboarding concierge: it drafts a personalized welcome email with Claude when a user signs up, stores it as a pending row in your database, and only sends it through the Resend connector after you click approve. Budget 30-40 minutes and a Lovable Pro-tier credit balance.
| Fact | Value |
|---|---|
| Tool | Lovable |
| Difficulty | Beginner |
| Time required | 30-40 min |
| Compatibility | Lovable Free or Pro plan, Lovable Cloud (Supabase-based), Anthropic API key, browser only |
| Last updated | November 2026 |
What you'll build
You'll build an onboarding concierge for a SaaS app. When someone signs up, an Edge Function asks Claude to draft a short, personalized welcome email based on the plan they picked, then saves that draft to a pending_emails table with status set to pending instead of sending it. A small review page lists pending drafts with Approve and Discard buttons, and only Approve triggers the actual send through Resend. That's the no-code shape of a human-in-the-loop agent: the model drafts, a person decides, and only one narrow path can actually take the external action.
Lovable builds this from prompts typed into its chat panel, not from code you write by hand. Describing what you want in Agent Mode generates a Postgres table (Lovable Cloud runs on Supabase under the hood), an Edge Function in supabase/functions, and the UI to review drafts, all editable afterward in the Code tab if you want to see exactly what was generated. Secrets live in the Cloud tab, never in a prompt or a committed file, and Edge Functions read them with Deno.env.get() at runtime.
Choose Lovable for this when you want a working app with its own database and auth around the agent, not just the agent logic. If you only need the tool-calling loop itself and are comfortable writing Python or TypeScript, the Claude Agent SDK guide gives you the same approval pattern with more direct control.
Prerequisites
- A Lovable account on the Free or Pro plan
- A Lovable project with Lovable Cloud enabled (adds the Supabase-based backend automatically)
- An Anthropic API key
- A Resend account (or Lovable's built-in Lovable Emails if you'd rather skip the external connector)
Step-by-step guide
Describe the agent and let Lovable scaffold the data model
In the chat panel, describe the whole flow in one prompt: a table for pending email drafts, a trigger when a new user signs up, and a review page. Lovable's Agent Mode reads this and creates the pending_emails table, the signup trigger, and a first pass at the review UI in one pass. You don't write SQL or a migration file yourself; you read the plan it proposes and approve it.
1Prompt to Lovable:2"When a new user signs up, create a row in a pending_emails table (user_id, draft_subject, draft_body, status default 'pending', created_at). Add a /admin/drafts page that lists pending rows with Approve and Discard buttons. Don't send anything yet, we'll wire up the actual send next."Expected result: Lovable reports a new pending_emails table in the Cloud tab's Database section and a working /admin/drafts page showing an empty list.
Add your Anthropic key as a Secret
Open the Cloud tab, click the + next to Preview, and go to Secrets. Add ANTHROPIC_API_KEY with your key from the Anthropic Console. This is the only place the key should ever live; never paste it into a chat prompt or a file, since prompts and generated code can end up in your GitHub history once you connect a repo.
1Cloud tab -> Secrets -> Add Secret2Name: ANTHROPIC_API_KEY3Value: sk-ant-...Expected result: ANTHROPIC_API_KEY appears in the Secrets list, masked after saving.
Prompt for the drafting Edge Function
Ask Lovable to write the Edge Function that actually calls Claude. Be specific about what it should and shouldn't do: draft only, never send. Lovable generates a Deno function under supabase/functions that reads the new user's plan, calls the Anthropic API with the key from Deno.env.get, and writes the result into pending_emails with status still pending.
1Prompt to Lovable:2"Create an Edge Function draft-welcome-email that runs on new signups. It should call the Anthropic API (model claude-sonnet-5-5) with the user's name and plan, ask for a 3-sentence welcome email, and insert the result into pending_emails with status 'pending'. It must never call any send API itself."Expected result: A new file at supabase/functions/draft-welcome-email/index.ts, and signing up a test user produces a new pending_emails row with a real drafted subject and body.
Connect Resend for the actual send
In the Cloud tab, add the Resend connector (or enable Lovable Emails if you'd rather not create a separate Resend account) and store the Resend API key as a second Secret if you went the connector route. This step only wires up the capability to send; nothing is sent yet because no code path calls it outside the approval action you add next.
1Cloud tab -> Secrets -> Add Secret2Name: RESEND_API_KEY3Value: re_...Expected result: Resend shows as an available connector in the Cloud tab, ready for the Approve button to call.
Wire Approve to send, and Discard to just update status
Prompt Lovable to connect the two buttons on /admin/drafts. Approve should call a second Edge Function, send-approved-email, that reads the row, calls Resend with the stored draft, and flips status to sent. Discard should just flip status to discarded with no external call at all. Keeping these as two separate functions, one that only reads the database and one that can reach an outside API, is what makes the approval boundary real instead of cosmetic.
1Prompt to Lovable:2"Add a send-approved-email Edge Function that the Approve button calls: it reads the pending_emails row, sends draft_subject and draft_body through Resend to the user's email, then sets status to 'sent'. Discard should just set status to 'discarded' without calling any external function."Expected result: Clicking Approve on a test row sends a real email through Resend and the row's status updates to sent in the /admin/drafts list.
Test the full loop and harden it before publishing
Sign up a second test user, confirm a draft appears, approve it, and check the inbox. Then add two guardrails Lovable can generate in one more prompt: a daily cap on how many drafts draft-welcome-email will create (so a bug or a bot signup spike can't burn through your Anthropic credits), and a Supabase RLS policy so only an authenticated admin can read or act on pending_emails. Publish once both are in place.
1Prompt to Lovable:2"Add a check in draft-welcome-email that skips drafting if more than 200 rows were created in pending_emails in the last 24 hours, and add an RLS policy on pending_emails so only users with role 'admin' can select or update rows."Expected result: A signup past the daily cap is skipped with a logged reason, and the /admin/drafts page returns nothing for a non-admin test account.
Complete working example
1// Generated by Lovable from the prompt in step 3; shown here so you can see2// exactly what runs. Edit through Lovable's chat or the Code tab, not by hand3// in production, so the two stay in sync.4import { createClient } from "jsr:@supabase/supabase-js@2";56const ANTHROPIC_API_KEY = Deno.env.get("ANTHROPIC_API_KEY")!;7const SUPABASE_URL = Deno.env.get("SUPABASE_URL")!;8const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;9const DAILY_DRAFT_CAP = 200;1011Deno.serve(async (req) => {12 const { user_id, name, plan, email } = await req.json();13 const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY);1415 const since = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();16 const { count } = await supabase17 .from("pending_emails")18 .select("id", { count: "exact", head: true })19 .gte("created_at", since);2021 if ((count ?? 0) >= DAILY_DRAFT_CAP) {22 return new Response(JSON.stringify({ skipped: "daily_cap_reached" }), { status: 200 });23 }2425 const draftRes = await fetch("https://api.anthropic.com/v1/messages", {26 method: "POST",27 headers: {28 "content-type": "application/json",29 "x-api-key": ANTHROPIC_API_KEY,30 "anthropic-version": "2023-06-01",31 },32 body: JSON.stringify({33 model: "claude-sonnet-5-5",34 max_tokens: 300,35 messages: [{36 role: "user",37 content: `Write a 3-sentence welcome email for ${name}, who just signed up for the ${plan} plan. Return JSON: {"subject": "...", "body": "..."}.`,38 }],39 }),40 });4142 const draftJson = await draftRes.json();43 const parsed = JSON.parse(draftJson.content[0].text);4445 const { error } = await supabase.from("pending_emails").insert({46 user_id,47 recipient_email: email,48 draft_subject: parsed.subject,49 draft_body: parsed.body,50 status: "pending",51 });5253 if (error) {54 return new Response(JSON.stringify({ error: error.message }), { status: 500 });55 }56 return new Response(JSON.stringify({ drafted: true }), { status: 200 });57});58Common mistakes
Why it's a problem: Pasting the Anthropic key directly into a chat prompt so Lovable 'just has it'
How to avoid: Prompts and generated code can land in your GitHub history once you connect a repo. Always add keys through Cloud tab > Secrets and reference them with Deno.env.get.
Why it's a problem: Letting the drafting function and the sending function be the same Edge Function
How to avoid: If one function both drafts and sends, there's no code boundary left for the approval step to sit on. Keep draft-welcome-email and send-approved-email separate.
Why it's a problem: Skipping the RLS policy on the pending_emails table because it's 'just an internal admin page'
How to avoid: Lovable Cloud tables are reachable through the public Supabase API by default. Without RLS, anyone with the anon key can read or write drafts, including other users' email addresses.
Why it's a problem: Testing only in Preview and assuming the approve/send path works the same once published
How to avoid: Publish updates the live app on its own schedule; test the Approve button again against the published URL, not just Preview, before you tell users it's live.
Why it's a problem: Not capping how often the drafting function runs
How to avoid: A signup spike or a bot wave will burn through your Anthropic credits fast with no cap. Add a daily or hourly limit before you publish, not after you notice the bill.
Best practices
- Keep every external write (sending, charging, posting) behind its own Edge Function so one function is always safe to call freely and the other always requires the approval path.
- Store every API key as a Cloud tab Secret, never as plain text in a prompt, a component, or a committed file.
- Write the agent's instructions as part of the prompt you give Lovable, not as a vague 'make it smart' request; specificity in the prompt becomes specificity in the generated system prompt.
- Add a rate or cost cap to any function that calls an LLM on an event you don't fully control, like signups or form submissions.
- Turn on RLS on every table an agent's Edge Function touches, scoped to the role that should actually be able to read or write it.
- Review the generated Edge Function in the Code tab after each prompt. Lovable builds it correctly most of the time, but you're still the one shipping it.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I have a Lovable app with a draft-welcome-email Edge Function that drafts emails into a pending_emails table and a send-approved-email function gated behind an Approve button (code below). Design a third Edge Function that re-drafts an email if it's still pending after 48 hours, with a different, more casual tone. [paste complete_code]
In Lovable's chat, prompt: 'Add a Slack notification when a new row lands in pending_emails, using a Slack connector, so the admin doesn't have to keep checking /admin/drafts manually.'
Frequently asked questions
Can I build an AI agent in Lovable without writing any code at all?
Yes. Every step in this guide is a prompt typed into Lovable's chat panel. The TypeScript shown in complete_code is what Lovable generates behind the scenes; you can read it in the Code tab, but you don't have to write or edit it to make changes, just prompt again.
Where do Lovable AI agents actually run?
Inside Supabase Edge Functions, which are Deno-based serverless functions tied to your Lovable Cloud project. They run server-side, which is why secrets stay safe there and why this pattern works even though nothing runs in the user's browser.
Does Lovable have a built-in way to call Claude, or do I have to use the raw Anthropic API like in this guide?
Lovable Cloud includes an AI gateway in the Cloud tab's AI section with its own default models, which is the faster path for simple generation. This guide calls the Anthropic API directly from the Edge Function instead, since that gives you the exact model and prompt control the approval flow needs.
What happens to this agent if I switch from Lovable Cloud to my own Supabase project?
The Edge Functions and tables transfer with an export, since Lovable Cloud is Supabase under the hood. You'd re-add the Secrets manually in the new project's dashboard, since secret values never travel with an export.
Can the approval step notify someone outside the Lovable app, like over Slack or email?
Yes. Prompt Lovable to call a Slack or email connector from inside draft-welcome-email right after the insert, so a reviewer gets pinged instead of having to remember to check /admin/drafts.
Is this safe to connect to a real payments or refunds flow instead of just email?
The same draft-then-approve shape works, but raise the stakes on review: require a second admin to approve anything involving money, and log every approval decision to a separate audit table you never let the agent write to directly.
Can someone build this for me instead of prompting it myself?
Yes. RapidDev builds production agents on Lovable, the Claude Agent SDK, and n8n, including the approval and audit pieces most tutorials skip. A free 30-minute consultation is the fastest way to scope your version.
Talk to an Expert
Our team has built 1,000+ apps. Get personalized help with your project.
Book a free consultation