From a Vercel app you send transactional email with an HTTPS email API from a Route Handler, Server Action, or other server-only runtime — never from the browser. To do this, you put your API key in a Vercel env var (not NEXT_PUBLIC_*), call POST https://notify.cx/api/email/send with x-api-key, and use a verified domain for production from addresses.
So how do you send emails from a Vercel app? You treat email as a server-side HTTP call. Vercel already runs your Node (or Edge) functions; an email API fits that model better than opening SMTP ports from a serverless function.
What does 'Vercel app' mean?
Most Vercel apps in 2026 are Next.js App Router projects. The same pattern works for other frameworks on Vercel, as long as the send happens where secrets are available:
- App Router Route Handlers (
app/api/.../route.ts) - Server Actions
- Cron / background routes under
app/api/... - Edge functions that can call
fetch(Notify is HTTPS-only — fine on Edge)
Do not put the key in client components, middleware that ships secrets to the browser, or any NEXT_PUBLIC_ variable.
Why not SMTP on Vercel
SMTP from an app server used to be the default. On Vercel it is a poor fit:
- Long SMTP handshakes fight cold starts and platform timeouts
- You still need a relay host, ports, TLS flags, and bounce handling
- You get weaker first-class logs than a provider API with a dashboard
Prefer an email API. Keep SMTP for legacy tools that cannot speak HTTP. For the longer argument, see SMTP vs email APIs.
Pick a provider for the job
| Job | Sensible default |
|---|---|
| Password resets, receipts, magic links, product notifications | Minimal transactional API (Notify) |
| You already live in React Email + Resend’s SDK | Resend |
| Deliverability is the only priority and budget is secondary | Postmark |
| You are deep in AWS and will own IAM, SNS, logs yourself | Amazon SES |
Notify’s product surface is deliberately small: send, webhooks, logs. Free is 1,000 emails/mo; Pro is $10 / 10,000; Scale is $50 / 100,000. New accounts start sandbox-only until you verify a domain for production custom from.
Best for: builders shipping transactional mail on Vercel without ESP bloat. Not for: newsletters, drip campaigns, or drag-and-drop template studios.
Setup on Vercel (once)
- Create an account and API key at notify.cx/dashboard/credentials.
- In the Vercel project → Settings → Environment Variables, add:
NOTIFY_API_KEY=your_api_key_here
Apply it to Production (and Preview if you want preview deploys to send). Redeploy after adding secrets so the runtime picks them up.
- For production
fromaddresses, verify your domain (SPF/DKIM). Until then, use the dashboard guided test or the non-delivering sandbox rehearsal endpoint for API shape checks.
Option A — Route Handler (App Router)
// app/api/send-welcome/route.ts
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const { email, name } = await request.json();
if (!email) {
return NextResponse.json({ error: 'email required' }, { status: 400 });
}
const response = await fetch('https://notify.cx/api/email/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.NOTIFY_API_KEY!
},
body: JSON.stringify({
from: 'noreply@your-verified-domain.com',
to: email,
subject: 'Welcome',
message: `<p>Hi ${name ?? 'there'},</p><p>Thanks for signing up.</p>`
})
});
if (!response.ok) {
const detail = await response.text();
console.error('Notify send failed', detail);
return NextResponse.json({ error: 'send_failed' }, { status: 502 });
}
return NextResponse.json({ ok: true });
}
Call it from your app with fetch('/api/send-welcome', { method: 'POST', ... }). The browser never sees the API key.
Option B — Server Action
// app/actions/send-password-reset.ts
'use server';
export async function sendPasswordResetEmail(to: string, resetUrl: string) {
const response = await fetch('https://notify.cx/api/email/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.NOTIFY_API_KEY!
},
body: JSON.stringify({
from: 'security@your-verified-domain.com',
to,
subject: 'Reset your password',
message: `<p>Reset link (expires soon):</p><p><a href="${resetUrl}">${resetUrl}</a></p>`
})
});
if (!response.ok) {
throw new Error(`Notify error: ${await response.text()}`);
}
}
Same HTTP contract as the Route Handler — just invoked from a form action instead of a public API route.
Shared helper (what I’d ship Monday)
Copy-pasting fetch in every route gets messy. One helper is enough:
// lib/notify.ts
export async function sendEmail(input: {
to: string;
subject: string;
message: string;
from?: string;
}) {
const response = await fetch('https://notify.cx/api/email/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.NOTIFY_API_KEY!
},
body: JSON.stringify({
from: input.from ?? 'noreply@your-verified-domain.com',
to: input.to,
subject: input.subject,
message: input.message
})
});
if (!response.ok) {
throw new Error(await response.text());
}
return response.json();
}
Use it from Route Handlers, Server Actions, and Vercel Cron routes alike.
Edge vs Node on Vercel
Notify is a normal HTTPS fetch. Edge runtimes that support fetch work. Keep the key in env / bindings, not in the client bundle. If you later move the same helper to Cloudflare Workers or Supabase Edge Functions, the call shape stays the same — that is the point of an HTTP email API.
Webhooks and logs (optional, worth it)
Once mail is in production:
- Watch delivery in email logs
- Wire webhooks for bounce/complaint events into Slack or your own endpoint
You do not need a second product for “analytics studio.” Logs + webhooks cover the operational loop.
Common failure modes on Vercel
| Symptom | Likely cause |
|---|---|
NOTIFY_API_KEY undefined in production | Env var added but deploy not rebuilt; or only set for Development |
| 401 from Notify | Wrong key, or key from a different workspace |
403 / domain errors on from | Domain not verified; still sandbox-only |
| Email “works” locally, fails on Preview | Preview env missing the secret |
| Client-side send attempts | Key leaked or architecture wrong — move send to the server |
FAQ
How do I send emails from a Vercel app?
Use a server-only Route Handler or Server Action, store NOTIFY_API_KEY in Vercel environment variables, and POST to https://notify.cx/api/email/send with x-api-key, to, from, subject, and message. Verify your domain before production custom from addresses.
Can I use Nodemailer on Vercel?
You can point Nodemailer at a provider SMTP relay, but for new Vercel apps an HTTPS email API is usually simpler and more reliable under serverless constraints.
Does Notify need an SDK or MCP server?
No. One fetch is enough. There is no required Notify SDK for this path.
What about SES instead?
SES is fine if you already operate AWS and accept IAM/SNS/setup time (new SES accounts often wait on production access). For a thin transactional layer on Vercel with flat pricing, Notify is a strong default.
Next steps
- Add
NOTIFY_API_KEYin Vercel - Ship a Route Handler or Server Action with the snippet above
- Verify your domain for production
- Optional: skim the Next.js docs guide and pricing
Further Reading
Discover more articles on similar topics across our network
How Developers Can Automate Small Business Website Creation and Maintenance
Stackademic
Comments
Loading comments…