Using webhooks and Netlify functions for email notifications in Sanity
For handling email notifications when new documents are created in Sanity, webhooks are your best bet. They're a stable, production-ready feature that's perfect for triggering external actions like sending emails when form submissions come in.
Setting Up Webhooks
You can create webhooks in your project's API settings at sanity.io/manage. Here's what you need to configure:
Basic setup:
- URL: Point to your endpoint (could be a serverless function, API route, etc.)
- Trigger on: Select "Create" to fire when new documents are created
- Filter: Use a GROQ filter to target specific documents:
_type == "formSubmission" - Secret: Add a secret for signature verification (important for security!)
The webhooks documentation has full details on all configuration options including projections (to customize the payload), HTTP headers, and more.
Verifying Webhook Signatures
Always verify the signature to ensure requests are actually coming from Sanity. Sanity includes an X-Sanity-Signature header with each webhook request, which you should validate using the secret you configured.
If you're using Next.js, the next-sanity package includes helpful utilities:
import { parseBody } from 'next-sanity/webhook'
export async function POST(req: Request) {
const signature = req.headers.get('x-sanity-signature')
const body = await req.text()
const { isValidSignature, body: payload } = await parseBody(
body,
signature,
process.env.SANITY_WEBHOOK_SECRET
)
if (!isValidSignature) {
return new Response('Invalid signature', { status: 401 })
}
// Send your email notification here
await sendEmail({
to: 'your@email.com',
subject: 'New Form Submission',
body: `New submission: ${JSON.stringify(payload)}`
})
return new Response('OK', { status: 200 })
}For other environments, you can use the standalone @sanity/webhook toolkit:
import { isValidSignature, SIGNATURE_HEADER_NAME } from '@sanity/webhook'
export async function handleWebhook(req) {
const signature = req.headers[SIGNATURE_HEADER_NAME]
const body = await req.text()
if (!isValidSignature(body, signature, process.env.SANITY_WEBHOOK_SECRET)) {
return { statusCode: 401, body: 'Invalid signature' }
}
const payload = JSON.parse(body)
// Send email notification
// ...
return { statusCode: 200, body: 'OK' }
}Example: Sending Email with a Webhook
Here's a complete example using a Next.js API route with SendGrid:
import { parseBody } from 'next-sanity/webhook'
import sgMail from '@sendgrid/mail'
sgMail.setApiKey(process.env.SENDGRID_API_KEY!)
export async function POST(req: Request) {
const signature = req.headers.get('x-sanity-signature')
const body = await req.text()
const { isValidSignature, body: submission } = await parseBody(
body,
signature,
process.env.SANITY_WEBHOOK_SECRET
)
if (!isValidSignature) {
return new Response('Invalid signature', { status: 401 })
}
await sgMail.send({
to: 'your@email.com',
from: 'notifications@yourdomain.com',
subject: 'New Form Submission',
text: `
New submission received:
Name: ${submission.name}
Email: ${submission.email}
Message: ${submission.message}
`
})
return new Response('OK', { status: 200 })
}Alternative: Sanity Functions (Experimental)
If you'd prefer not to manage your own endpoint, Sanity Functions are an option worth knowing about. They're serverless functions that run directly on Sanity's infrastructure and can react to document changes.
However, note that Functions are currently marked as experimental, meaning the APIs are subject to change. For production use cases like yours, webhooks are the more stable choice.
If you want to explore Functions anyway, they work by defining a blueprint configuration and deploying functions that respond to document events. The setup involves initializing blueprints (npx sanity blueprints init) and configuring document functions to trigger on create/update/delete events.
Webhook Best Practices
- Idempotency: Use the
idempotency-keyheader Sanity includes to avoid processing duplicate deliveries - Response time: Respond quickly (under 30 seconds) or the webhook will timeout
- Error handling: Return appropriate status codes - 2xx for success, 429 to request retry with backoff
- Filtering: Use GROQ filters to only trigger on relevant documents, reducing unnecessary webhook calls
Check out the webhook best practices guide for more tips on building robust webhook handlers.
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.