Using webhooks and Netlify functions for email notifications in Sanity

42 replies
Last updated: Apr 5, 2022
Hey everyone. Anyone knows any good method to use webhooks to handle email notifications when a new document is created in Sanity? I’m collecting form submissions and need to have some sort of notification whenever a new one comes in.
AI Update

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:

  1. URL: Point to your endpoint (could be a serverless function, API route, etc.)
  2. Trigger on: Select "Create" to fire when new documents are created
  3. Filter: Use a GROQ filter to target specific documents: _type == "formSubmission"
  4. 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-key header 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.

Hi Guillermo - you could have your web hook sent to an email service with an endpoint setup for that link d of thing, or have it sent to a server less function that sends the email. Are you already using some sort of email service?
Not at the moment. I wanted to try with Zapier but the web hooks feature is for paid plans only.
What is your hosting service?
I ask because Netlify has a starter function for sending an email.
I'm using Netlify. I know they offer a forms feature but I already built out the API to send the json payload from the form directly to Sanity
Yeah, I guess I could try to integrate that into my script after the data is sent to Sanity.
I have no idea how though
You have a front end form that posts data to sanity?
Exactly
Ok and does it create a new instance of a document like “formSubmission” or whatever you called it?
If if so - set up a webhook to fire when that document type is created and give it the form data a payload or “cargo”.
Precisely, each submission creates a new document using the schema I set
Great so set up a webhook in the Sanity admin and have it send the form data to either a server less function or some email sends service like sendgrid.
What’s your current email provider?
Yes! I created a hook and set up the GROQ query and projection to only create the payload with the form data I want. Now it's just a matter of sending that out as an email
Great - so you just need to send the data to a thing that will send the email - are you using netlify cli?
Currently none. It's a project for a client. I guess if thing get too complex I can always suggest paying for Zapier
No CLI… actually I've never used it before
But I can give it a try
Gimme a minute going to go to computer. BRB
Have you played with webhooks before?
There's no rush, I appreciate your help
This will be the first time using web hooks for me. I'm going all-in in the jamstack for this project 🙏
OK, cool, so first thing you should do is set your webhook (assuming you are still just testing now) to post data to a place where you can see it. I've used this: https://webhook.site/#!/b239db54-9e8d-47f7-84fb-26be1f7f19a3
basically you can see how your data will look as it is shipped to the url that will accept it - just obiviously don't zap around private info
actually just go to webbook.site , it will generate a new unique url for you to post to
or this one is even simpler: https://requestcatcher.com/
Oh nice. So I can actually see the data I'm sending
exactly. So once you get that working, you can then send it to either a service designed for this (like SendGrid or something), or too a serverless function that will take the data and send the email. Like all things, a certain amount can be done for free, and then if it's super large volumes you end up paying, but how many emails are we talking?
actually gonna delete that example, there's a clearer one, hangone
Interesting how everything starts working together… I'm not anticipating a large volume of messages. At most two or three per day
If you are on netlify I really reccomend using netlify cli - it makes function creation really simple. I was worried at first it was going to mess in weird ways with my stuff but it is very non-intrusive
brb again
Awesome. I'm reading up on it as we speak
what's cool is that there is an in-the-web console where your function logs, so instead of seeing the payload on requestcatcher, you see it in that netlify window when your function gets the hook. and/or you can also test them locally. Here's the out-of-the-box nodemailer that Netlify has as an example function. You should be able to find all the data sent with the webhook in the
event
parameter. ALso, Netlify will use .env vars and node packages you define in the top level of your repo., anyway here's my copy/paste of that example email function: https://gist.github.com/bacalj/bc57aceeb7ee89a4b907563a41cb46d9
(because instead of sending your webhook to requestcatcher, you are going to send it to the url netlify sets up for your function, like www.mythingwhatever/.netlify/functions/my-function
AWESOME
😀 this is all extremely helpful
awesome, I was extremely jazzed when I first started playing with netlify functions, amazing what you can do
Yeah, I'm learning a lot of stuff week after week, went from basic React/Next to Sanity backend, multilingual sites, ISR, APIs and now web hooks.
I ended up setting up SendGrid after all. It was pretty straightforward to plug in the code into my existing api. Now after the form is sent, it creates a new document in Sanity and sends off the data in a nicely formatted email. Thanks for all your help!
Awesome!

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.

Was this answer helpful?