Skip to content

Build with Lovable

This guide walks through building a working app on Caramel using Lovable’s AI assistant as the integration layer. By the end, you have a signup form that creates a contact in your Caramel audience and enrolls them in a welcome journey.

The example app is a coffee-shop newsletter signup called BrewClub. Estimated time: 30 minutes.

Note This guide assumes basic Lovable familiarity. If you’re new to Lovable, complete their getting-started tutorial at lovable.dev first.

Lovable’s assistant generates React + TypeScript. It’s most effective when it can discover your API’s schema rather than having each endpoint spelled out. The MCP connector provides the full tool catalog via tools/list at connection time — so you prompt in plain language and Lovable writes the calls.

For server-side integrations (a Node.js backend calling Caramel outside Lovable), use the REST gateway at gateway.caramelme.com directly.

Step 1 — Connect Caramel to your Lovable workspace

Section titled “Step 1 — Connect Caramel to your Lovable workspace”
  1. In Lovable, open Workspace Settings → Connectors → Add MCP server.
  2. Enter the URL: https://app.caramelme.com/api/functions/caramel-mcp
  3. Click Connect.
  4. Complete the magic-link OAuth flow — enter your Caramel email, click the link in your inbox.
  5. Lovable returns connected.

Verify by asking Lovable’s assistant:

What Caramel tools do I have available?

The assistant calls caramel.v1.meta.capabilities and lists all 19 tools. If nothing comes back, reconnect the connector.

Default scopes granted: meta:read, forms:read, forms:write, audience:read. Add audience:write when you need to upsert contacts directly.

Step 2 — Configure Caramel (one time, ~5 minutes)

Section titled “Step 2 — Configure Caramel (one time, ~5 minutes)”

Do this once per business in the Caramel dashboard.

  1. Create a form — Open Forms → New form. Add these fields:
    • email (required, type email)
    • first_name (required, type text)
    • favorite_drink (optional, type text)
    • subscribe_to_emails (optional, type checkbox)
  2. Activate the form. Note the form ID (shown in Forms → Share — looks like frm_01h7…).
  3. Verify a sender domainSettings → Domains → Add domain. Publish the DNS records Caramel provides (DKIM, SPF, DMARC). Verification takes 5–30 minutes after DNS propagates.
  4. Activate a welcome journeyAI Agent → Template Library, search “welcome series” for your industry, click Activate.

That’s the Caramel side. No Caramel-specific code required.

In Lovable’s chat:

Build a coffee-shop newsletter signup page for BrewClub. Header with the BrewClub logo, hero section with “Sign up for fresh-roast news and a free coffee”, and a form with fields for email, first name, and favorite drink, plus a “Yes, send me news and offers” checkbox. Style with warm brown tones. Use shadcn/ui components.

Iterate with follow-up prompts as needed:

  • “Add a success state showing ‘Check your inbox!’ after submission.”
  • “Require email and first name. Validate email format.”
  • “Add a loading spinner during submission.”

Prompt Lovable:

When the form is submitted, call caramel.v1.form.submit via the MCP connector. Use form ID frm_01h7…. Pass email, first_name, favorite_drink, and subscribe_to_emails. Include the current URL as source_url. Show success state on success, error toast on failure.

Lovable generates a handler like this:

const handleSubmit = async (formData: FormData) => {
setLoading(true);
try {
const res = await fetch('https://app.caramelme.com/api/functions/caramel-mcp', {
method: 'POST',
headers: {
'Authorization': `Bearer ${import.meta.env.VITE_CARAMEL_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
id: Date.now(),
params: {
name: 'caramel.v1.form.submit',
arguments: {
form_id: 'frm_01h7…',
data: {
email: formData.email,
first_name: formData.firstName,
favorite_drink: formData.favoriteDrink,
subscribe_to_emails: formData.subscribeToEmails,
},
source_url: window.location.href,
},
},
}),
});
const result = await res.json();
if (result.error || result.result?.isError) {
toast({ title: 'Something went wrong', description: 'Please try again.', variant: 'destructive' });
return;
}
setSuccess(true);
} catch {
toast({ title: 'Network error', description: 'Please check your connection.', variant: 'destructive' });
} finally {
setLoading(false);
}
};

A few things to note about this code:

  • VITE_CARAMEL_TOKEN — Lovable’s connector injects and refreshes this automatically.
  • MCP envelope — every call needs jsonrpc, method, id, and params.
  • Error handling — MCP tool errors surface as result.result.isError === true. Protocol errors surface as result.error. Check both.

Click Preview in Lovable, fill the form with your own email, and submit.

Expected behavior:

  1. Loading state for ~300 ms.
  2. Success state with “Check your inbox!”.
  3. Welcome email arrives within 30 seconds.

Troubleshooting:

SymptomLikely causeFix
Authentication required toastOAuth token expiredReconnect the Caramel connector in Workspace Settings
form_not_found in consoleWrong form ID or form is inactiveRun caramel.v1.form.list to get the correct ID
Success state but no emailSender domain not verifiedRun caramel.v1.domain.status — check DNS propagation
validation errorRequired field missingVerify email and first_name are present in the submission

When a customer hits a milestone (100th order, anniversary), upsert updated traits:

async function trackMilestone(email: string) {
await fetch('https://app.caramelme.com/api/functions/caramel-mcp', {
method: 'POST',
headers: {
'Authorization': `Bearer ${import.meta.env.VITE_CARAMEL_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
id: Date.now(),
params: {
name: 'caramel.v1.contact.upsert',
arguments: {
business_id: import.meta.env.VITE_CARAMEL_BUSINESS_ID,
email,
},
},
}),
});
}

Plan caramel.v1.contact.upsert requires the Business tier or above. Starter and Growth callers receive 403 tier_required. For custom trait writes on lower tiers, submit to a dedicated “milestone” form instead — form submit works on every tier.

Show the business owner this month’s signup count:

Add an admin panel at /admin that shows this month’s signup count and remaining AI credits from caramel.v1.meta.usage. Refresh every 10 minutes.

Lovable generates:

useEffect(() => {
const fetchUsage = async () => {
const res = await fetch('https://app.caramelme.com/api/functions/caramel-mcp', {
method: 'POST',
headers: {
'Authorization': `Bearer ${import.meta.env.VITE_CARAMEL_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
jsonrpc: '2.0',
method: 'tools/call',
id: Date.now(),
params: {
name: 'caramel.v1.meta.usage',
arguments: { business_id: import.meta.env.VITE_CARAMEL_BUSINESS_ID },
},
}),
});
const { result } = await res.json();
setUsage(JSON.parse(result.content[0].text));
};
fetchUsage();
const id = setInterval(fetchUsage, 10 * 60 * 1000);
return () => clearInterval(id);
}, []);

Render usage.forms_submitted_this_month for signups and usage.ai_credits_remaining for credits.

  1. Customer fills the form → Caramel creates the contact.
  2. Day 0: welcome email from your verified domain.
  3. Day 3: follow-up email (configured in the journey).
  4. The owner checks the admin panel for signup counts.

Coming soon The following are not yet available:

  • No imperative email.send. You can activate a journey but cannot call caramel.email.send({ to, subject, body }) directly. Use journey activation instead.
  • No outbound webhooks. Your app polls for status — use caramel.v1.meta.usage or the dashboard. Outbound webhooks are on the roadmap.
  • No programmatic business provisioning. Multi-tenant SaaS where each customer needs their own Caramel business must redirect customers to caramelme.com/signup until API provisioning ships.