To integrate Mailgun with Lovable, store your Mailgun API key and domain in Cloud → Secrets, then create an Edge Function that calls Mailgun's REST API to send transactional emails. Mailgun offers a simpler REST API than SendGrid, pay-per-email pricing after the free tier, and detailed delivery analytics — making it a strong choice for developer-focused transactional email without SendGrid's visual template builder.
Send transactional emails from Lovable via Mailgun's developer-friendly REST API
Mailgun is designed by and for developers. Where SendGrid focuses on visual template builders and marketing tools, Mailgun's strength is a clean, predictable REST API for transactional email, detailed delivery analytics, and pay-per-email pricing that scales with usage. The API uses standard form-data POST requests rather than JSON — a small quirk that catches developers off guard but makes the API easy to test with any HTTP client.
Lovable Cloud includes built-in email capabilities for Supabase Auth events (confirmations, magic links, password resets), so you only need Mailgun when you want custom transactional emails driven by your app's business logic. Order confirmations, shipping notifications, two-factor authentication codes, weekly reports — these are the use cases where Mailgun shines.
The integration pattern follows the same security model as all authenticated API calls in Lovable: your React frontend calls a Lovable Edge Function, the Edge Function reads the Mailgun API key from Cloud → Secrets using Deno.env.get(), constructs the email payload, and forwards it to Mailgun's API. The API key never touches browser code, never ends up in your Git history, and is protected by Lovable's security layer that blocks approximately 1,200 hardcoded keys daily.
Mailgun's European and US API endpoints use different base URLs: api.mailgun.net for US-hosted accounts and api.eu.mailgun.net for EU-hosted accounts. If you signed up with a European account and use the US URL (or vice versa), you will get a 404 error. This guide shows you how to store the correct base URL as a secret to avoid this issue.
Integration method
Mailgun has no native Lovable connector. Email delivery is handled via a Deno Edge Function that calls Mailgun's REST API using form-data POST requests. The Mailgun API key and sending domain are stored in Cloud → Secrets and accessed via Deno.env.get(), keeping credentials server-side and preventing CORS errors from direct browser calls.
Prerequisites
- A Lovable project with at least one deployed app (Edge Functions require Lovable Cloud)
- A Mailgun account (free trial provides 1,000 emails for 1 month; paid plans start at $35/month for 50,000 emails)
- A Mailgun sending domain configured with DNS records (either the sandbox domain for testing or a custom domain for production)
- Your Mailgun API key from Mailgun Dashboard → Settings → API Keys → Private API key
- Your Mailgun domain and the correct API base URL for your region (api.mailgun.net for US, api.eu.mailgun.net for EU)
Step-by-step guide
Set up your Mailgun domain and retrieve credentials
Set up your Mailgun domain and retrieve credentials
Before configuring Lovable, you need a Mailgun account with a configured sending domain and your API credentials ready. To get your API key: log in to Mailgun at app.mailgun.com. Go to Settings → API Keys. Find your 'Private API key' (not the public validation key). Click the eye icon to reveal it, then copy it — it looks like a long hex string like key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. For your sending domain: Mailgun automatically provides a sandbox domain for testing (looks like sandbox-xxxxxxxxxxxxx.mailgun.org). The sandbox domain only sends to authorized recipients (you must add email addresses in Mailgun → Domains → sandbox domain → Authorized Recipients). For production, add a custom domain: go to Domains → Add New Domain, enter your domain (e.g., mail.yourapp.com), and follow the DNS verification steps. Mailgun will give you MX, TXT (SPF), TXT (DKIM), and CNAME records to add to your domain registrar. Domain verification can take up to 24-48 hours but usually completes within minutes. Region check: Mailgun routes to different API endpoints depending on where your data is stored. Log in and check your dashboard URL — if it is app.mailgun.com, you are on the US region (api.mailgun.net). If it is app.eu.mailgun.net, you are on the EU region (api.eu.mailgun.net). Store the correct base URL as a secret to avoid 404 errors. Copy three values: your API key, your sending domain (sandbox or custom), and your API base URL.
Pro tip: For development, use Mailgun's sandbox domain and add your own email as an authorized recipient. This lets you test without needing full DNS setup. Switch to a custom verified domain before production — sandbox domains are only for development.
Expected result: You have three values ready: a Mailgun API key, a sending domain (sandbox or custom), and your API base URL. Your sandbox domain has your test email address as an authorized recipient, or your custom domain's DNS records are verified.
Add Mailgun credentials to Lovable Cloud Secrets
Add Mailgun credentials to Lovable Cloud Secrets
Store your Mailgun credentials in Lovable's Cloud Secrets panel. All three values — API key, domain, and base URL — need to be server-side secrets because the Mailgun API key can be used to send emails from your domain and access delivery logs. Exposing it in frontend code would allow anyone to send emails on your behalf. In Lovable, click the '+' icon at the top of the editor (next to the Preview label). In the Cloud panel, click the 'Secrets' tab. Add the following secrets by clicking 'Add new secret' for each: First: Name: MAILGUN_API_KEY, Value: your Mailgun private API key (starts with 'key-') Second: Name: MAILGUN_DOMAIN, Value: your sending domain (e.g., sandbox-xxxxx.mailgun.org for testing, or mail.yourapp.com for production) Third: Name: MAILGUN_BASE_URL, Value: the correct API base URL for your region. Use https://api.mailgun.net for US accounts, or https://api.eu.mailgun.net for EU accounts. Store the full URL including https:// — this is what the Edge Function will use directly. Optionally add: MAILGUN_FROM_EMAIL with value like noreply@mail.yourapp.com. This makes it easy to update the sender address without changing code. With all secrets saved, your Edge Function will have everything needed to authenticate and send emails.
Pro tip: The most common Mailgun setup error is using the wrong API base URL for your region. US accounts must use api.mailgun.net; EU accounts must use api.eu.mailgun.net. Using the wrong one results in a 404 error even with a valid API key.
Expected result: MAILGUN_API_KEY, MAILGUN_DOMAIN, MAILGUN_BASE_URL (and optionally MAILGUN_FROM_EMAIL) appear in Cloud → Secrets with masked values.
Create the Mailgun email Edge Function
Create the Mailgun email Edge Function
Create the Edge Function that proxies email sending requests to Mailgun's REST API. Unlike SendGrid which uses JSON, Mailgun's API expects form-data encoded requests using the standard multipart/form-data or application/x-www-form-urlencoded format. In Deno, you construct this using the FormData Web API. Mailgun authenticates using HTTP Basic auth where the username is literally the string 'api' and the password is your API key. This is different from Mailchimp (where the username is ignored) and SendGrid (which uses a Bearer token). The function below demonstrates the full pattern: reading secrets, constructing the form data, sending to Mailgun's /messages endpoint, and returning the result. Note that Mailgun returns a JSON response with an 'id' field (the message ID, useful for tracking) and a 'message' field (typically 'Queued. Thank you.'). For production apps, you can also pass the html parameter (HTML body), the template parameter (Mailgun template name), and h:X-Mailgun-Variables (JSON for template variables) — the Edge Function can be extended to support all of Mailgun's API parameters as needed.
Create an Edge Function at supabase/functions/send-email/index.ts that accepts a POST with { to, subject, html, text, fromName }. Use MAILGUN_API_KEY, MAILGUN_DOMAIN, MAILGUN_BASE_URL, and MAILGUN_FROM_EMAIL from Deno environment secrets. Construct a FormData payload and POST to the Mailgun /messages endpoint using Basic auth (username: 'api', password: MAILGUN_API_KEY). Return the message ID on success. Include CORS headers and handle OPTIONS preflight.
Paste this in Lovable chat
1import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'23const corsHeaders = {4 'Access-Control-Allow-Origin': '*',5 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',6}78serve(async (req) => {9 if (req.method === 'OPTIONS') {10 return new Response('ok', { headers: corsHeaders })11 }1213 try {14 const { to, subject, html, text, fromName } = await req.json()1516 const apiKey = Deno.env.get('MAILGUN_API_KEY')17 const domain = Deno.env.get('MAILGUN_DOMAIN')18 const baseUrl = Deno.env.get('MAILGUN_BASE_URL') || 'https://api.mailgun.net'19 const fromEmail = Deno.env.get('MAILGUN_FROM_EMAIL') || `noreply@${domain}`2021 if (!apiKey || !domain) {22 throw new Error('Missing Mailgun secrets: MAILGUN_API_KEY and MAILGUN_DOMAIN are required')23 }2425 if (!to || !subject || (!html && !text)) {26 return new Response(27 JSON.stringify({ error: 'Required fields: to, subject, and html or text' }),28 { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }29 )30 }3132 // Mailgun uses form-data, not JSON33 const formData = new FormData()34 formData.append('from', `${fromName || 'MyApp'} <${fromEmail}>`)35 formData.append('to', to)36 formData.append('subject', subject)37 if (html) formData.append('html', html)38 if (text) formData.append('text', text)39 // Enable click and open tracking40 formData.append('o:tracking', 'yes')41 formData.append('o:tracking-clicks', 'yes')42 formData.append('o:tracking-opens', 'yes')4344 const response = await fetch(`${baseUrl}/v3/${domain}/messages`, {45 method: 'POST',46 headers: {47 Authorization: 'Basic ' + btoa(`api:${apiKey}`),48 },49 body: formData,50 })5152 const data = await response.json()5354 if (!response.ok) {55 throw new Error(`Mailgun API error ${response.status}: ${data.message || JSON.stringify(data)}`)56 }5758 return new Response(59 JSON.stringify({ success: true, id: data.id, message: data.message }),60 { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }61 )62 } catch (error) {63 return new Response(64 JSON.stringify({ error: error.message }),65 { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }66 )67 }68})Pro tip: Mailgun's Basic auth format is: username = 'api' (literally the string 'api', not your email address), password = your API key. This trips up many developers who expect username:password format with their account email.
Expected result: The send-email Edge Function is deployed in Cloud → Edge Functions. It accepts POST requests with email parameters and calls Mailgun's API. The function is ready to receive calls from your React frontend.
Test the integration and set up delivery tracking
Test the integration and set up delivery tracking
Test your Mailgun integration from a deployed Lovable app — Edge Functions do not run in Lovable's preview mode. Deploy via the Publish button, then trigger the email flow from your live app URL. For initial testing with the sandbox domain, make sure the recipient email address is in your Mailgun sandbox's Authorized Recipients list. Navigate to Mailgun → Domains → your sandbox domain → Authorized Recipients and add your test email address. If the recipient is not authorized, Mailgun will return 200 but silently drop the email. To verify delivery: check Mailgun's Logs tab (Sending → Logs in the Mailgun dashboard). You should see your test message listed with an 'accepted' status, then 'delivered' once the recipient's mail server confirms receipt. If the email bounces or is filtered as spam, the reason appears in the logs. For production readiness, set up Mailgun's event webhooks to track opens and clicks. In Mailgun → Sending → Webhooks, add your Edge Function's URL (from Cloud → Edge Functions) as the webhook endpoint for events you want to track. This requires a separate webhook-receiver Edge Function — your send-email function handles outbound sending, while a separate mailgun-events function handles inbound delivery events. For complex implementations involving tracking webhooks, event storage, and multi-tenant sending across multiple domains, RapidDev's team can help design and implement the full pipeline.
Pro tip: Use Mailgun's Logs tab as your primary debugging tool. Every API call is logged with the full request details and Mailgun's response, including error reasons. This is faster than reading Edge Function logs for email-specific issues.
Expected result: Test emails arrive in the authorized recipient's inbox. Mailgun's Sending → Logs shows the message with 'delivered' status. Cloud → Logs shows the Edge Function executed without errors.
Common use cases
Send two-factor authentication codes via email
Build an email-based 2FA system where users receive a one-time code to verify their identity. An Edge Function generates a secure random code, stores a hashed version in Supabase with an expiry time, and sends the plain code to the user's email via Mailgun. A second Edge Function verifies the code when submitted.
Create an Edge Function called send-verification-code that generates a 6-digit random code, stores a SHA-256 hash of it in a verification_codes table in Supabase with the user's email and a 10-minute expiry, then sends the plain code to the user's email via Mailgun using MAILGUN_API_KEY, MAILGUN_DOMAIN, and MAILGUN_BASE_URL from secrets. Subject: 'Your verification code' with the code in a clear email body.
Copy this prompt to try it in Lovable
Send weekly digest emails to active users
Run a scheduled Edge Function that queries Supabase for active users and their activity data from the past week, then sends each user a personalized weekly digest email via Mailgun. Use Mailgun's batch sending feature to send individualized emails efficiently without looping through separate API calls for each user.
Create a scheduled Edge Function that runs every Monday at 9am. It should query the users table for users with email_digest_enabled = true, fetch each user's activity stats from the last 7 days, and send a personalized weekly digest email via Mailgun. Use MAILGUN_API_KEY, MAILGUN_DOMAIN, and MAILGUN_BASE_URL from secrets. Include their top 3 activities and a link to the full dashboard.
Copy this prompt to try it in Lovable
Track email open rates and clicks in your Supabase database
Configure Mailgun to send delivery events (sends, opens, clicks, bounces, unsubscribes) to a Lovable Edge Function webhook receiver. The Edge Function parses the events and stores them in a Supabase table, giving you a built-in email analytics dashboard without a third-party tool.
Create an Edge Function called mailgun-webhook that receives POST requests from Mailgun event webhooks. Parse the event data (event type, recipient, timestamp, message ID) and insert each event into an email_events table in Supabase. Verify the webhook signature using MAILGUN_WEBHOOK_SIGNING_KEY from secrets. Register this Edge Function's URL in Mailgun → Sending → Webhooks for the 'opened', 'clicked', 'bounced', and 'unsubscribed' events.
Copy this prompt to try it in Lovable
Troubleshooting
Mailgun returns 404 Not Found when the Edge Function calls the API
Cause: The API base URL does not match your account's region. US accounts use api.mailgun.net, EU accounts use api.eu.mailgun.net. Using the wrong URL returns 404 even with valid credentials.
Solution: Check your Mailgun dashboard URL. If it shows app.mailgun.com, you are on US (use https://api.mailgun.net). If it shows app.eu.mailgun.net, you are on EU (use https://api.eu.mailgun.net). Update the MAILGUN_BASE_URL secret in Cloud → Secrets with the correct URL, then redeploy the Edge Function.
Mailgun returns 200 and logs show 'accepted' but no email arrives
Cause: For sandbox domains, the recipient email address has not been added to Mailgun's Authorized Recipients list. Mailgun accepts and logs the API call but silently drops the email if the recipient is not authorized.
Solution: In Mailgun, go to Domains → your sandbox domain → Authorized Recipients. Click 'Add Authorized Recipient' and add the email address you are testing with. Mailgun will send a confirmation email to that address — click the link to verify it. After authorization, retry your test send. This restriction only applies to sandbox domains; custom verified domains can send to any recipient.
Edge Function logs show 'Mailgun API error 401: Forbidden' despite correct API key
Cause: Mailgun's Basic auth format requires the username to be the literal string 'api' — not your account email address. Using any other username, even your Mailgun email, results in a 401 error.
Solution: Verify the auth header construction in your Edge Function uses: btoa('api:' + apiKey). The username must be the string 'api', the colon is a separator, and then your API key is the password. This is a Mailgun-specific convention and differs from other services that use Bearer tokens or email:key combinations.
1// Correct Mailgun auth header:2Authorization: 'Basic ' + btoa(`api:${apiKey}`)3// NOT:4Authorization: 'Basic ' + btoa(`${yourEmail}:${apiKey}`) // WRONGEmails arrive in spam folders instead of the inbox
Cause: Custom domains without proper SPF, DKIM, and DMARC DNS records have low sender reputation. Without email authentication records, receiving servers treat the messages as suspicious.
Solution: In Mailgun → Domains, find your sending domain and click on it. Mailgun shows a checklist of DNS records that need to be added. Add the MX, SPF TXT, and DKIM TXT records to your domain registrar's DNS settings. Mailgun verifies these automatically — watch for the green checkmarks. DMARC is optional but recommended for production. After DNS propagation (15 minutes to 24 hours), your emails should hit the inbox reliably.
Best practices
- Use the Mailgun sandbox domain for all development and testing — it prevents accidentally sending test emails to real users while you are building.
- Store MAILGUN_BASE_URL as a separate secret (not hardcoded) to avoid regional endpoint confusion and to make it easy to switch regions without code changes.
- Enable open and click tracking (o:tracking, o:tracking-clicks, o:tracking-opens in the form data) for all emails and set up Mailgun webhooks to capture delivery events in your Supabase database for analytics.
- Always handle Mailgun's async delivery model correctly: the API returns 'Queued. Thank you.' immediately, not a final delivery confirmation. Use webhooks to track actual delivery status.
- For high-volume sending, use Mailgun's batch sending feature (send to a comma-separated list with recipient variables) rather than individual API calls per recipient — this is significantly more efficient.
- Set up Mailgun's Bounce Shield feature to automatically suppress future sends to bounced addresses, protecting your sender reputation.
- Use separate Mailgun API keys for development and production, and store them in separate Lovable project environments — never use your production API key during development.
Alternatives
Choose SendGrid if you want a visual dynamic template builder and a more generous free tier (100 emails/day forever), compared to Mailgun's 1-month trial then pay-per-email.
Choose Mailchimp if your primary need is email list management and marketing campaigns rather than individual transactional emails.
Choose Twilio if you need SMS, voice, or WhatsApp alongside email in a single provider relationship, as Twilio offers all communication channels under one API.
Frequently asked questions
How does Mailgun pricing compare to SendGrid for a small Lovable app?
Mailgun offers a free trial for the first month (typically around 1,000 emails), then moves to pay-per-email pricing starting at $35/month for 50,000 emails. SendGrid offers a permanent free tier of 100 emails/day (3,000/month). For apps sending under 3,000 emails/month, SendGrid's free tier is more economical. For higher volumes (5,000-50,000 emails/month), Mailgun's pricing becomes competitive. Both offer similar deliverability when properly configured.
Can I use Mailgun instead of Lovable Cloud's built-in email for Supabase Auth?
Yes — in Supabase Auth settings (accessible via Cloud → Auth in your Lovable project), you can configure a custom SMTP server. Enter your Mailgun SMTP credentials to route all auth emails (confirmations, password resets, magic links) through Mailgun instead of Lovable Cloud's default email. This gives you full control over auth email deliverability and branding. The SMTP hostname for Mailgun is smtp.mailgun.org with port 587.
What is the difference between Mailgun's sandbox domain and a custom domain?
The sandbox domain (sandbox-xxx.mailgun.org) is pre-configured for testing and only sends to email addresses you manually authorize in Mailgun's dashboard. It is free and requires no DNS setup, making it ideal for development. A custom domain (like mail.yourapp.com) requires DNS record setup for SPF, DKIM, and MX, but it lets you send to any email address and dramatically improves deliverability because emails come from your own domain. Always use a custom domain for production.
How do I receive inbound emails in my Lovable app using Mailgun?
Mailgun can route inbound emails to your Lovable Edge Function via webhooks. In Mailgun → Receiving → Create Route, set up a routing rule that matches incoming emails to a pattern (like support@mail.yourapp.com) and forwards the parsed email data as a POST request to your Edge Function URL. Your Edge Function can then process the email, extract attachments, and take actions in your Supabase database — creating support tickets, processing commands, etc.
Why does Mailgun use form-data instead of JSON like other email APIs?
Mailgun's API was designed in an era when form-data was the standard for web form submissions. The format allows easy file attachment support (you can append file objects to the FormData) and is compatible with any HTTP client. JSON APIs came later. Mailgun still maintains the form-data format for backward compatibility. In Deno, the standard FormData web API handles this correctly — the code example in Step 3 shows the exact pattern.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation