To send transactional emails from a V0 app using Mailgun, create a Next.js API route that calls the Mailgun API using your MAILGUN_API_KEY and MAILGUN_DOMAIN environment variables. Store both as server-only Vercel environment variables — never expose them to the browser. V0 generates your contact form or notification UI, and the API route handles email delivery securely on the server.
Send Transactional Emails from Your V0 App with Mailgun
Every V0-generated app that interacts with users eventually needs email — contact form submissions, welcome emails, password reset links, order confirmations, and team notifications are all standard requirements. Mailgun is one of the most reliable transactional email APIs available, with excellent deliverability, detailed logs, and a straightforward REST API that works perfectly from Next.js API routes.
The integration pattern is straightforward: V0 generates your frontend form or trigger UI, and a Next.js API route running on Vercel handles the actual email sending. Your Mailgun API key never touches the browser — it stays in Vercel's encrypted environment variable storage and is only used server-side. This is a security requirement, not just a best practice, because Mailgun API keys have full sending permissions.
Mailgun offers a free tier that lets you send up to 1,000 emails per month with a 3-month trial and then requires a paid plan, but the free sandbox domain lets you test immediately without DNS configuration. For production, you point Mailgun at your custom domain by adding DNS records, which typically takes 24-48 hours to verify. This guide covers both sandbox testing and production custom domain setup.
Integration method
V0 generates your email trigger UI (contact form, notification settings, etc.). A Next.js API route handles the actual email sending server-side using the Mailgun HTTP API or the mailgun.js SDK. Your MAILGUN_API_KEY and MAILGUN_DOMAIN are stored as server-only Vercel environment variables and never exposed to the client.
Prerequisites
- A Mailgun account at mailgun.com — free trial available, no credit card required initially
- Your Mailgun API Key from the Mailgun Dashboard under Settings → API Keys (Private API key)
- Your Mailgun sending domain — either the sandbox domain provided by Mailgun for testing (sandbox-xxxx.mailgun.org) or a verified custom domain
- A V0 project with Next.js App Router structure
- Basic familiarity with copying and editing code in your project
Step-by-step guide
Get Your Mailgun API Key and Sending Domain
Get Your Mailgun API Key and Sending Domain
Log in to your Mailgun dashboard at app.mailgun.com. Navigate to Settings in the left sidebar, then click API Keys. You will see your Private API key — click the eye icon to reveal it and copy it. This is your MAILGUN_API_KEY. Keep it secure and never commit it to source code or paste it in your V0 chat. Next, find your sending domain. If you are just testing, Mailgun provides a sandbox domain automatically — look in the left sidebar under Sending → Domains and note the sandbox domain name, which looks like sandbox-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.mailgun.org. For production use, you will add your own domain here and Mailgun will give you DNS records to add to your domain registrar. Note that Mailgun's sandbox domain can only send to email addresses you have verified in the sandbox — go to Sending → Domains → click your sandbox domain → Authorized Recipients and add your own email address there so you can receive test emails. Also note which Mailgun API region you are using — Mailgun has a US region (api.mailgun.net) and an EU region (api.eu.mailgun.net). Your dashboard URL will tell you which region your account is on. This affects the API endpoint URL you use in your API route.
Pro tip: Mailgun's sandbox domain restricts email recipients for testing — make sure to add your test email address to the Authorized Recipients list in your sandbox domain settings, otherwise test emails will silently fail to deliver.
Expected result: You have your Mailgun Private API Key, your sending domain name, and you know which API region (US or EU) your account uses.
Generate the Contact Form UI with V0
Generate the Contact Form UI with V0
Open V0 and describe the email trigger interface you need. For a contact form, describe the fields, validation behavior, loading states, and success/error feedback. V0 is excellent at generating well-structured React forms with Tailwind styling. Be specific about what happens after submission — ask for a loading spinner on the submit button, a success message that replaces the form, and an error message display area. Include client-side validation in your prompt so users see helpful feedback before the form even submits. After V0 generates the component, review the form structure. The component should use React state for the form fields and a handleSubmit async function that calls fetch('/api/mailgun/contact', {...}) with the form data in the request body. V0 may generate a fetch call with a placeholder URL — you will update this to match your actual API route path. The important thing at this stage is to get the UI working correctly — you will add the API route connection in the next step. If V0 generates a form with a basic fetch call structure, that is exactly what you need.
Create a contact form component at app/contact/page.tsx with inputs for name, email, and message. Add client-side validation showing red border and error text under empty required fields. The submit button should show a spinner while submitting. On success, show a green success card with a checkmark saying 'Message sent!' and offer a 'Send another message' link. On error, show a red error banner with the error message. All form data should POST as JSON to /api/mailgun/contact. Use Tailwind CSS with a clean centered card layout.
Paste this in V0 chat
Pro tip: Ask V0 to include honeypot fields (hidden inputs) in the contact form to reduce spam. Legitimate users will not fill in hidden fields, but bots often do. Check for filled honeypot fields in your API route and reject the request.
Expected result: V0 generates a contact form with name, email, and message fields, validation states, a loading button, and success/error feedback states.
Create the Mailgun Email API Route
Create the Mailgun Email API Route
Create the Next.js API route that sends emails via Mailgun. You have two options: use the mailgun.js npm package for a JavaScript SDK experience, or call the Mailgun REST API directly using fetch with form-encoded data. Both work well in Next.js. The direct fetch approach requires no extra dependencies, which is ideal for keeping your deployment lightweight. Mailgun's API uses HTTP Basic authentication with 'api' as the username and your API key as the password, encoded in Base64. The request body uses application/x-www-form-urlencoded format with from, to, subject, and text or html fields. The API endpoint is https://api.mailgun.net/v3/{your-domain}/messages for US region accounts. For EU region accounts, use https://api.eu.mailgun.net/v3/{your-domain}/messages. Validate all incoming request data in the API route before calling Mailgun — check that the email address format is valid, that the message is not empty, and that required fields are present. Return appropriate error responses so your frontend can display meaningful error messages. The route should also implement basic rate limiting or at minimum validate the request body shape to prevent abuse. For HTML emails, pass an html field instead of or in addition to the text field.
Create a Next.js API route at app/api/mailgun/contact/route.ts that accepts a POST request with name, email, and message fields. Validate that all fields are present and email is a valid email format. Use the Mailgun HTTP API (fetch with Basic auth) to send an email to process.env.CONTACT_EMAIL_TO with subject 'New Contact Form Submission from {name}'. Include the sender's name, email, and message in the email body as both plain text and HTML. Use process.env.MAILGUN_API_KEY for auth, process.env.MAILGUN_DOMAIN as the sending domain, and process.env.MAILGUN_FROM_EMAIL as the from address. Return 200 on success or error messages on failure.
Paste this in V0 chat
1import { NextRequest, NextResponse } from 'next/server';23const MAILGUN_API_KEY = process.env.MAILGUN_API_KEY;4const MAILGUN_DOMAIN = process.env.MAILGUN_DOMAIN;5const MAILGUN_API_URL = process.env.MAILGUN_REGION === 'eu'6 ? `https://api.eu.mailgun.net/v3/${MAILGUN_DOMAIN}/messages`7 : `https://api.mailgun.net/v3/${MAILGUN_DOMAIN}/messages`;89function isValidEmail(email: string): boolean {10 return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);11}1213export async function POST(request: NextRequest) {14 if (!MAILGUN_API_KEY || !MAILGUN_DOMAIN) {15 return NextResponse.json(16 { error: 'Email service not configured' },17 { status: 500 }18 );19 }2021 try {22 const body = await request.json();23 const { name, email, message } = body;2425 if (!name || !email || !message) {26 return NextResponse.json(27 { error: 'Name, email, and message are required' },28 { status: 400 }29 );30 }3132 if (!isValidEmail(email)) {33 return NextResponse.json(34 { error: 'Invalid email address' },35 { status: 400 }36 );37 }3839 const formData = new URLSearchParams({40 from: `${name} via Contact Form <${process.env.MAILGUN_FROM_EMAIL || `noreply@${MAILGUN_DOMAIN}`}>`,41 to: process.env.CONTACT_EMAIL_TO || '',42 'h:Reply-To': email,43 subject: `New Contact Form Submission from ${name}`,44 text: `Name: ${name}\nEmail: ${email}\n\nMessage:\n${message}`,45 html: `46 <h2>New Contact Form Submission</h2>47 <p><strong>Name:</strong> ${name}</p>48 <p><strong>Email:</strong> <a href="mailto:${email}">${email}</a></p>49 <h3>Message:</h3>50 <p style="white-space: pre-wrap;">${message}</p>51 `,52 });5354 const credentials = Buffer.from(`api:${MAILGUN_API_KEY}`).toString('base64');5556 const res = await fetch(MAILGUN_API_URL, {57 method: 'POST',58 headers: {59 Authorization: `Basic ${credentials}`,60 'Content-Type': 'application/x-www-form-urlencoded',61 },62 body: formData.toString(),63 });6465 const data = await res.json();6667 if (!res.ok) {68 console.error('Mailgun error:', data);69 return NextResponse.json(70 { error: 'Failed to send email. Please try again.' },71 { status: 500 }72 );73 }7475 return NextResponse.json({ success: true, id: data.id });76 } catch (error) {77 console.error('Email send error:', error);78 return NextResponse.json(79 { error: 'An unexpected error occurred' },80 { status: 500 }81 );82 }83}Pro tip: Add the h:Reply-To header set to the sender's email address. This means when you click Reply in your email client, it replies to the contact form sender rather than to your own from address.
Expected result: POSTing to /api/mailgun/contact with valid name, email, and message fields sends an email via Mailgun and returns { success: true, id: '...' }.
Add Environment Variables in Vercel and Test
Add Environment Variables in Vercel and Test
Now add your Mailgun credentials to Vercel so they are available in your deployed application. Open your Vercel project dashboard, click Settings in the top navigation, then Environment Variables in the left sidebar. Add the following server-only environment variables (no NEXT_PUBLIC_ prefix for any of these, as they must never be exposed to the browser): MAILGUN_API_KEY set to your Private API key from Mailgun, MAILGUN_DOMAIN set to your Mailgun sending domain (sandbox domain for testing, or your verified custom domain for production), MAILGUN_FROM_EMAIL set to a from address that uses your Mailgun domain such as noreply@yourdomain.com or hello@sandbox-xxxx.mailgun.org for sandbox, CONTACT_EMAIL_TO set to the email address where you want to receive contact form submissions, and optionally MAILGUN_REGION set to eu if your Mailgun account is on the EU region (leave this out for US region accounts). Set all of these for Production, Preview, and Development environments. After adding the variables, either create a .env.local file in your project root for local development testing, or redeploy from the Deployments tab to apply the new variables to your live deployment. Test the contact form by submitting it and checking your inbox. If the email does not arrive, check the Mailgun dashboard under Sending → Logs to see if the send was attempted and whether Mailgun reports any delivery issues.
1# .env.local (for local development — never commit this file)2MAILGUN_API_KEY=your-private-api-key-here3MAILGUN_DOMAIN=sandbox-xxxx.mailgun.org4MAILGUN_FROM_EMAIL=noreply@sandbox-xxxx.mailgun.org5CONTACT_EMAIL_TO=your-email@example.com6# MAILGUN_REGION=eu # Uncomment if your account is on EU regionPro tip: Mailgun's Sending → Logs dashboard shows every email attempt with the full delivery status, bounce details, and error messages. This is your first place to look when debugging delivery issues — it shows whether Mailgun accepted the send request, and if not, why.
Expected result: Submitting the contact form on your Vercel deployment sends an email to CONTACT_EMAIL_TO and the Mailgun dashboard shows the delivery as 'Delivered' in the Logs section.
Set Up a Custom Sending Domain for Production
Set Up a Custom Sending Domain for Production
The Mailgun sandbox domain is fine for development and testing, but for production use you should configure a custom sending domain. This gives your emails a professional from address (notifications@yourcompany.com), improves deliverability because the domain matches your brand, and avoids the sandbox's restriction on recipient email addresses. In your Mailgun dashboard, go to Sending → Domains and click Add New Domain. Enter a subdomain of your main domain, for example mail.yourcompany.com or mg.yourcompany.com — using a subdomain rather than your root domain is best practice because it keeps email sending reputation separate from your website's domain reputation. Mailgun will provide you with several DNS records to add: two TXT records (one for SPF, one for DKIM), and optionally a CNAME record for tracking. Log in to your domain registrar (GoDaddy, Namecheap, Cloudflare, etc.) and add these DNS records exactly as Mailgun specifies. DNS propagation typically takes 24-48 hours, though it often completes faster. Return to the Mailgun dashboard and click Verify DNS Settings — once all records show green checkmarks, your domain is verified. Update your MAILGUN_DOMAIN and MAILGUN_FROM_EMAIL environment variables in Vercel to use the new verified domain. For complex multi-domain or high-volume email setups, RapidDev's team can help configure Mailgun's dedicated IP pools and domain reputation management.
1// Example DNS records Mailgun will ask you to add2// (actual values will be unique to your domain and Mailgun account)34// TXT record (SPF):5// Host: mg.yourdomain.com6// Value: v=spf1 include:mailgun.org ~all78// TXT record (DKIM):9// Host: pic._domainkey.mg.yourdomain.com10// Value: k=rsa; p=MIGfMA0GCSqGS... (long key provided by Mailgun)1112// CNAME record (tracking - optional):13// Host: email.mg.yourdomain.com14// Value: mailgun.orgPro tip: Use a subdomain like mg.yourdomain.com rather than your root domain for Mailgun. This isolates your email sending reputation — if your email domain gets flagged as spam, it does not affect your website's domain.
Expected result: Your Mailgun dashboard shows all DNS records as verified (green checkmarks) for your custom domain. Emails sent from your app now come from your branded domain address.
Common use cases
Contact Form with Email Notification
A portfolio or business site built with V0 needs a contact form that sends an email to the site owner when someone submits a message. The API route validates the form data, formats a clear email, and sends it to the owner's inbox via Mailgun.
Create a contact form page with fields for the visitor's name, email address, subject, and message textarea. Add a send button and show a success confirmation message after submission. Include client-side validation that highlights empty required fields. Style it with Tailwind CSS in a clean card layout with a shadow. The form should POST to /api/mailgun/contact.
Copy this prompt to try it in V0
Welcome Email After User Signup
A SaaS app built with V0 sends a personalized welcome email immediately after a new user creates an account. The email includes the user's name, a getting-started guide, and a link back to the app.
Build a user registration page with fields for full name, email, and password. After successful form submission, show a 'Check your email for a welcome message' confirmation screen. The registration should POST to /api/auth/register which creates the user account and triggers a welcome email via /api/mailgun/welcome. Use Tailwind CSS with a centered card layout.
Copy this prompt to try it in V0
Order Confirmation Email
An e-commerce or booking app sends a detailed order confirmation email after checkout. The email includes the order summary, items purchased, total price, and order reference number so the customer has a record.
Create an order confirmation page that shows a success checkmark, order number, itemized list of purchased items with quantities and prices, total amount, and estimated delivery date. Add a 'Resend Confirmation Email' button. Style with Tailwind CSS in a clean receipt-style layout with a green success header.
Copy this prompt to try it in V0
Troubleshooting
API returns 401 Unauthorized from Mailgun
Cause: The MAILGUN_API_KEY is incorrect, missing from Vercel environment variables, or you are using the wrong type of API key (public key instead of private key).
Solution: Verify you are using the Private API key from Mailgun Dashboard → Settings → API Keys, not the Domain Sending key or Public key. In Vercel, check that MAILGUN_API_KEY is set without quotes or extra spaces. After updating environment variables in Vercel, you must redeploy for the changes to take effect — changes to environment variables do not automatically apply to existing deployments.
Mailgun logs show 'Delivered' but emails are not arriving in the inbox
Cause: Emails are likely being filtered into spam folders, or you are using a sandbox domain and the recipient email address has not been added to the Authorized Recipients list.
Solution: For sandbox domains, go to Mailgun → Sending → Domains → click your sandbox domain → Authorized Recipients and add the email address you are testing with. For custom domains, check your spam folder first. To improve inbox placement, ensure your domain has properly configured SPF and DKIM records and that your email content is not triggering spam filters.
TypeError: fetch is not a function in the API route on older Node.js versions
Cause: Native fetch was added to Node.js in version 18. If your Vercel project is configured to use an older Node.js runtime, fetch is not available globally.
Solution: Update your Vercel project to use Node.js 18 or later (Vercel Dashboard → Settings → General → Node.js Version). Alternatively, import node-fetch or install the mailgun.js package which bundles its own HTTP client.
1// If you need to support older Node.js, install node-fetch:2// npm install node-fetch34import fetch from 'node-fetch';5// Then use fetch normally in your API routeMailgun returns 'Domain not found' or 400 error on send
Cause: The MAILGUN_DOMAIN environment variable does not match a domain in your Mailgun account, or you are using the wrong API region URL (US vs EU).
Solution: Double-check the exact domain name in your Mailgun Dashboard under Sending → Domains. Ensure the MAILGUN_DOMAIN value in Vercel matches exactly. If your account is on the EU region (app.eu.mailgun.com), set MAILGUN_REGION=eu so the API route uses the EU endpoint (api.eu.mailgun.net instead of api.mailgun.net).
Best practices
- Always store MAILGUN_API_KEY as a server-only environment variable without any NEXT_PUBLIC_ prefix — exposing your Mailgun API key in the browser allows anyone to send emails from your domain
- Validate email input server-side in the API route even if you validate client-side — never trust client-submitted data for sending emails to prevent abuse
- Add the h:Reply-To header in your Mailgun send request so recipients can reply directly to the form submitter rather than to your noreply address
- Check Mailgun Sending Logs immediately when debugging delivery issues — the logs show the full delivery path including whether the receiving mail server accepted or bounced the message
- Use a subdomain (like mg.yourdomain.com) as your Mailgun sending domain to isolate email reputation from your main website domain
- Implement basic rate limiting on your email API route to prevent form spamming — consider limiting to one submission per IP address per hour using Vercel KV or a similar lightweight store
- Send both text and html versions of every email — some email clients prefer plain text, and including both versions reduces the chance of being marked as spam
Alternatives
Drip is a marketing email automation platform focused on customer journeys and campaigns rather than transactional email — choose Drip if you need sequence-based nurture campaigns, not single triggered emails.
HubSpot Marketing Hub combines email with a full CRM and marketing automation suite — choose it if you need contact tracking and lifecycle marketing in addition to email sending capability.
Frequently asked questions
Is Mailgun free to start with for a V0 app?
Mailgun offers a free trial that includes 1,000 free emails for the first three months, after which paid plans start at around $15 per month for up to 10,000 emails. The sandbox domain is always available for testing without any charges. For small apps, the free trial is sufficient to build and test your integration completely.
Can I use Mailgun to send HTML emails with custom templates?
Yes. Pass an html field in your Mailgun API request alongside or instead of the text field. You can use any HTML email template. Mailgun also has a Templates feature in the dashboard where you can store and manage email templates with Handlebars variables, then call them by name from the API. For complex HTML email templates, tools like MJML generate responsive HTML compatible with all major email clients.
What is the difference between Mailgun's sandbox domain and a verified custom domain?
The sandbox domain is automatically provided by Mailgun for testing and can only send to email addresses you explicitly authorize in the Mailgun dashboard. A verified custom domain (like mg.yourdomain.com) lets you send to anyone and gives your emails a branded from address. Custom domains require adding DNS records to your domain registrar and waiting up to 48 hours for DNS verification.
How do I track whether Mailgun emails were opened or clicked?
Mailgun has built-in tracking for opens, clicks, bounces, and unsubscribes. Enable tracking in the Mailgun dashboard under your domain settings. Mailgun rewrites links in your emails to track clicks and adds a tracking pixel for open detection. You can view analytics in the dashboard or receive webhook events when tracking events occur.
Can I schedule email sending with Mailgun from my Next.js API route?
Yes. Add the o:deliverytime parameter to your Mailgun API request with an RFC 2822 formatted date string. Mailgun will queue the email and send it at the specified time. For example: o:deliverytime=Thu, 13 Oct 2026 11:45:00 -0000. This is useful for scheduled reminders, appointment confirmations, and digest emails.
Does Mailgun work with Vercel Edge Runtime or only Node.js runtime?
The fetch-based Mailgun integration shown in this guide works with both Node.js runtime and Edge Runtime because it uses the standard fetch API. If you are using the mailgun.js npm package, check whether it is compatible with the Edge Runtime — some Node.js-specific features may not be available. The direct fetch approach is the safest for Edge Runtime compatibility.
How do I handle email bounces and unsubscribes in my V0 app?
Mailgun can send webhook events to your Next.js app when emails bounce, are marked as spam, or when recipients unsubscribe. Create a webhook endpoint at app/api/mailgun/webhook/route.ts, register it in the Mailgun dashboard under Sending → Webhooks, and handle the events to update your database — for example, marking email addresses as unsubscribed so you stop sending to them.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation