Free email webhooks let your app react when an email is sent, delivered, bounced, complained about, opened, clicked, or received. The useful part is not merely getting a JSON POST request: it is turning that event stream into safe, idempotent actions such as suppressing bad addresses, updating message status, or routing a customer reply into a support workflow.

This guide focuses on the two things people usually mean when they search for free email webhooks:

  1. Outbound email event webhooks — notifications about messages your application sends.
  2. Inbound email webhooks — notifications when somebody sends an email to an address or domain you control.

The worked example uses Resend because its published Free plan includes sending and receiving, 3,000 emails per month, a 100-email daily limit, and one domain. Free-plan terms are provider-specific and can change, so treat those limits as a product decision to recheck before committing your production architecture. (resend.com)

What free email webhooks actually are

A webhook is an HTTPS request that one service sends to your application when an event happens. With email, the provider is the sender of the webhook and your application owns the receiving URL, such as https://app.example.com/api/email-webhook.

That is different from polling an email API every few minutes. Instead of repeatedly asking, “Did anything happen?”, the provider pushes an event as it occurs. Resend describes its webhooks as real-time HTTPS requests with JSON payloads, while Mailgun similarly sends a JSON HTTP or HTTPS POST to the endpoint you configure. (resend.com)

“Free” can describe several different layers, and mixing them up causes surprise bills or fragile systems:

LayerWhat it doesCan it be free?
Email providerSends mail, receives mail, and generates eventsOften free only within a monthly or daily allowance
Webhook endpointReceives the provider’s HTTPS POST requestOften possible on a serverless free tier
StorageKeeps message status, event history, or inbound contentOften limited on free tiers
AutomationSends Slack alerts, creates tickets, calls an AI workflowMay be free at low volume, but commonly has task limits
Your domainLets you send and receive as you@yourdomain.comUsually not free; it is separate from the webhook service

A free email webhook setup is therefore not necessarily a permanently free email system. It is a low-cost or zero-cost way to validate a product, handle a small production workload, or build the first version of an email-driven feature without paying for polling infrastructure.

Choose the email job before choosing a provider

The right implementation starts with the event you need, not the provider name. Outbound and inbound email webhooks have different payloads, security concerns, and operational consequences.

Outbound delivery and engagement events

Outbound webhooks describe the lifecycle of mail your application sent. Common events include:

  • email.sent: the provider accepted a sending request.
  • email.delivered: the receiving mail server accepted the message.
  • email.bounced: the recipient mail server permanently rejected the message.
  • email.delivery_delayed: delivery hit a temporary problem.
  • email.complained: a recipient marked the message as spam.
  • email.opened and email.clicked: engagement signals, when tracking is enabled.

For example, Resend distinguishes a successful API send request from delivery to the recipient’s mail server, and defines email.bounced as a permanent rejection by the recipient’s mail server. It also documents delivery delays as temporary conditions such as a full inbox or a transient recipient-server issue. (resend.com)

The practical implication is important: sent is not delivered, and delivered is not read. Do not show a customer “Your recipient saw this” because you got an email.sent event. Likewise, a delivered event means acceptance by the recipient’s mail server, not a guarantee that it appeared in the inbox or was read.

Use outbound events to do things such as:

  • update an in-app message timeline;
  • stop retrying a failed password-reset notification and show a fallback channel;
  • suppress permanently bouncing recipients from future campaigns;
  • record complaints and unsubscribes immediately;
  • trigger a human-support follow-up when a high-value customer’s critical email fails.

Mailgun documents the same broad pattern: accepted, delivered, temporary failure, permanent failure, opened, clicked, unsubscribed, and complained events can feed list hygiene, reporting, fallback channels, CRM updates, and compliance records. (documentation.mailgun.com)

Inbound email events

Inbound webhooks let your application receive messages sent to you. Typical use cases include a support inbox, reply handling, “email documents to this address” workflows, receipt forwarding, or a personal-email-to-database feature.

With Resend Receiving, email to a receiving domain results in a POST to your endpoint. A managed *.resend.app address can be used for testing, while a custom domain can receive mail after the required mail routing is configured. The provider can route based on the to field, meaning a single domain can support application addresses such as receipts@yourdomain.com and support@yourdomain.com. (resend.com)

One subtle but critical implementation detail: an inbound event may contain message metadata rather than the complete content. Resend’s email.received webhook includes metadata, while bodies, headers, and attachments are retrieved separately through its Receiving and Attachments APIs. That design avoids putting potentially large attachments directly into serverless webhook requests. (resend.com)

That means your workflow is usually:

  1. Receive and verify email.received.
  2. Store the event and acknowledge it quickly.
  3. Fetch the email body only if your workflow needs it.
  4. Download or scan attachments only when necessary.
  5. Route the message according to recipient address, sender, subject, or your own business rules.

A practical free email webhook architecture

For a small but production-shaped implementation, use four components:

Email provider
   │
   │ HTTPS POST event
   ▼
Public webhook endpoint
   │
   ├── Verify signature and timestamp
   ├── Deduplicate event delivery
   ├── Store minimal event record
   └── Return 2xx promptly
          │
          ▼
Background work or queue
   ├── fetch inbound body/attachments
   ├── update database records
   ├── send alerts
   └── trigger support, CRM, or automation actions

This shape separates accepting an event from doing expensive work. A webhook provider expects a response quickly, may retry on failure, and cannot know whether your database transaction, AI extraction, PDF parsing, or third-party API call will finish reliably.

A minimal endpoint can write an event to a database table or queue and return 200 OK. A worker then handles the slow or failure-prone part. If you are not using a queue yet, make the synchronous handler small, deterministic, and idempotent.

Cloudflare Workers is one possible low-volume receiver: its Workers Free plan documents 100,000 requests per day and 10 milliseconds of CPU time per invocation. That is more than enough request capacity for many early email-event workloads, but the CPU limit means it is not the right place to synchronously parse large files, call several APIs, or run AI extraction in the request path. (developers.cloudflare.com)

What you need before creating the webhook

Prepare these items first:

  • A provider account that supports the required event type.
  • A publicly reachable HTTPS endpoint.
  • A secret store or environment-variable system for the webhook signing secret.
  • A database or durable store if events matter after the request ends.
  • An event-deduplication strategy.
  • A test recipient address you control.

Do not point production webhooks at an arbitrary request-inspection service. Those tools are useful for inspecting provider-generated test payloads, but real email payloads can contain personally identifiable metadata. SendGrid’s documentation explicitly warns that third-party webhook testing sites are not services you or SendGrid control and should be used only with sample data. (twilio.com)

Free email webhooks: provider options and trade-offs

There is no universal “best” free provider because the best choice depends on whether you are tracking outbound delivery, receiving incoming mail, or both.

Resend: straightforward for transactional events and inbound mail

Resend supports outbound email lifecycle events plus email.received for inbound mail. Its dashboard and API let you create webhooks, select event types, and obtain a signing secret. It also supports replaying events, which is useful when your endpoint was down or you changed handler code and need to reprocess an event. (resend.com)

For a creator, founder, or builder who wants to prototype reply handling or receipt ingestion, the managed receiving domain is especially convenient because it avoids DNS work for the first test. For a branded address, move to a custom domain and configure it according to the provider’s inbound setup requirements.

The published Free plan allowance is suitable for development and small real-world workloads, but it includes a 100-email-per-day sending limit. That daily cap matters if a user action can create a burst of transactional messages, such as a bulk import, team invitation campaign, or notification fan-out. (resend.com)

Mailgun: broad event taxonomy and account or domain scope

Mailgun supports domain-level and account-level webhook configuration and documents delivery, failures, engagement, unsubscribe, and complaint events. It is a strong fit when you already use Mailgun for sending and need detailed lifecycle data tied to sending domains. (documentation.mailgun.com)

Mailgun also documents a concrete retry behavior: 200 marks the webhook as successful, 406 Not Acceptable rejects it without a retry, and other responses can trigger retries for applicable event types. Never return 406 just because your database is temporarily unavailable; that tells Mailgun not to retry. (documentation.mailgun.com)

SendGrid: established event webhook with explicit signing support

Twilio SendGrid’s Event Webhook posts email events to a URL you configure. Its signed webhook feature uses a public/private key pair; the provider includes a signature in X-Twilio-Email-Event-Webhook-Signature, which your endpoint verifies using the generated public key. SendGrid also offers OAuth 2.0 webhook security, and the two mechanisms can be used independently or together. (twilio.com)

Choose SendGrid when it is already your sending provider or your team needs its event configuration. Do not copy a Resend signature-verification recipe into a SendGrid handler: signature formats, headers, keys, and verification libraries are provider-specific.

Postmark: useful when inbound and outbound streams are deliberately separated

Postmark offers outbound webhooks for events such as bounces and delivery notifications, plus inbound webhooks for received mail. Its bounce webhook is a JSON push notification sent when Postmark processes an outgoing bounce report. (postmarkapp.com)

The key comparison point is not a feature checklist. It is whether your application needs inbound mail, how you model streams and domains, how much event retention you need, and whether the provider’s pricing and event semantics fit the sending service you already use.

If you are evaluating email platforms more broadly, compare webhook support alongside sender-domain setup, event retention, API ergonomics, and the point at which you leave the free tier—not just the advertised number of free emails. Review your expected volume against transactional email pricing before choosing a production dependency.

Worked example: receive and verify Resend webhooks

This example builds a secure endpoint for Resend events using a Next.js App Router route. It handles email.delivered, email.bounced, email.complained, and email.received; logs only safe metadata; and returns a quick success response after verification.

1. Create a public endpoint

Create this file in a Next.js project:

// app/api/email-webhook/route.ts
import { Webhook } from "svix";

export const runtime = "nodejs";

export async function POST(request: Request) {
  const payload = await request.text(); // Keep the body exactly as received.

  const headers = {
    "svix-id": request.headers.get("svix-id") ?? "",
    "svix-timestamp": request.headers.get("svix-timestamp") ?? "",
    "svix-signature": request.headers.get("svix-signature") ?? "",
  };

  let event: any;

  try {
    const secret = process.env.RESEND_WEBHOOK_SECRET;
    if (!secret) throw new Error("Missing webhook secret");

    event = new Webhook(secret).verify(payload, headers);
  } catch (error) {
    console.error("Rejected email webhook", error);
    return Response.json({ error: "Invalid signature" }, { status: 400 });
  }

  // Use svix-id as a delivery identifier for deduplication in your database.
  const deliveryId = headers["svix-id"];

  switch (event.type) {
    case "email.delivered":
      console.log("Delivered", {
        deliveryId,
        emailId: event.data.email_id,
        recipients: event.data.to,
      });
      // await markDelivered(event.data.email_id, deliveryId)
      break;

    case "email.bounced":
      console.log("Bounced", {
        deliveryId,
        emailId: event.data.email_id,
        recipients: event.data.to,
      });
      // await suppressAddressAndMarkMessage(event.data, deliveryId)
      break;

    case "email.complained":
      console.log("Spam complaint", {
        deliveryId,
        emailId: event.data.email_id,
        recipients: event.data.to,
      });
      // await suppressAddressAndMarkMessage(event.data, deliveryId)
      break;

    case "email.received":
      console.log("Inbound email metadata", {
        deliveryId,
        emailId: event.data.email_id,
        from: event.data.from,
        to: event.data.to,
        subject: event.data.subject,
      });
      // Queue a job to fetch body/attachments through the Receiving API.
      break;

    default:
      console.log("Unhandled event type", event.type);
  }

  return Response.json({ received: true });
}

Install the verification library:

npm install svix

Then add the signing secret to your deployment environment:

RESEND_WEBHOOK_SECRET=whsec_your_secret_here

The detail that prevents many failed integrations is await request.text(). Signature verification must use the raw request body. Parsing JSON and serializing it again can change byte-level representation and invalidate a cryptographic signature. Svix’s verification documentation specifically requires the raw string payload and warns against frameworks that parse and then stringify JSON. (docs.svix.com)

2. Add the endpoint in the provider dashboard

In Resend:

  1. Deploy the route so it has a public HTTPS address, for example https://your-app.example/api/email-webhook.
  2. Open Webhooks and choose Add Webhook.
  3. Paste the endpoint URL.
  4. Select only the events you need: begin with email.delivered, email.bounced, email.complained, and optionally email.received.
  5. Copy the signing secret into RESEND_WEBHOOK_SECRET.
  6. Redeploy or restart the application so the environment variable is available.

Resend also supports creating a webhook through its API. The documented request accepts an endpoint plus an events array, and returns a signing_secret; treat that secret like a password and never commit it to Git. (resend.com)

3. Trigger a controlled test

For outbound events, send one transactional test email to an inbox you own. For inbound events, send a message to your configured receiving address. Start with a simple subject such as Webhook test 001 so you can find it in provider logs and your own logs.

A successful delivery test should produce:

  • a provider event-delivery attempt marked successful;
  • an HTTP 200 or other 2xx response from your endpoint;
  • a log record with the provider event type;
  • a database update or queued job if you added persistence;
  • no “invalid signature” error.

For inbound mail, seeing email.received does not prove that your app stored the body or attachments. Verify the subsequent API fetch separately, because the webhook is only the notification that a message is available. (resend.com)

4. Make it idempotent before relying on it

Webhook delivery is not a promise of exactly-once processing. Your endpoint can perform the database update successfully, lose the HTTP response because of a timeout, and receive the same event again when the provider retries.

Store a unique delivery identifier before applying irreversible effects. For Svix-formatted deliveries, svix-id identifies the message and remains the same when that delivery is resent. Put it in a table with a unique index, such as:

create table webhook_deliveries (
  provider text not null,
  delivery_id text not null,
  event_type text not null,
  received_at timestamptz not null default now(),
  payload jsonb not null,
  primary key (provider, delivery_id)
);

Your handler should attempt the insert first. If the unique-key insert fails, return 200 without repeating the business action. This prevents duplicate support tickets, duplicate Slack messages, duplicate refunds, and duplicate subscription state changes.

Security rules that should not be optional

An email webhook URL is public by design. Anyone can send an HTTP POST to it. The endpoint must decide whether a request genuinely came from your provider before it changes user data or triggers automation.

Verify signatures, not just a secret URL

Do not rely on an obscure route like /api/notifications/very-long-random-string. URL secrecy can reduce random noise but does not authenticate requests.

Use the provider’s signature mechanism:

  • Resend provides a webhook signing secret and uses Svix-style verification.
  • SendGrid can sign webhook requests and provides a public verification key.
  • Mailgun includes signature data that your application validates using its webhook signing key.

Signature verification protects against forged payloads. Svix also explains that signed webhook systems need replay protection: a valid captured payload could otherwise be submitted again. Its libraries reject timestamps more than five minutes away from current time by default, but timestamp validation does not replace database-level deduplication. (resend.com)

Return the right status code

Return 2xx only after the webhook is accepted for durable handling. If your service cannot safely accept it because the database is down, return a retryable failure such as 500 or 503 rather than pretending success.

Conversely, if signature verification fails, return 400 or 401 and do not process the payload. A signature failure is not a temporary provider problem; retrying an untrusted request will not make it trustworthy.

Providers differ in retry policy. Resend retries when it does not receive 200, while Mailgun treats 200 as success and 406 as a final, non-retryable rejection for its documented webhook flow. Read the exact semantics for your provider before making response codes part of your failure design. (resend.com)

Keep sensitive content out of ordinary logs

Email payloads can include recipient addresses, sender addresses, subjects, message IDs, and sometimes content or attachment metadata. Log a minimal correlation record instead of full raw bodies in general application logs.

A useful default is:

provider=resend
webhook_delivery_id=msg_...
event_type=email.received
email_id=...
status=accepted

If you need raw payload retention for debugging or audit purposes, encrypt it where appropriate, control access, set a retention period, and avoid exposing it in error-monitoring tools. Treat inbound attachments as untrusted files: scan them and do not let an email attachment execute, render unsafely, or determine a storage path.

Common failures and how to fix them

“The provider says delivery failed, but my route works in the browser”

A browser performs a GET request. Email providers send POST requests. Your endpoint must accept POST, be publicly reachable, and serve HTTPS. Check the exact deployed URL, including path, redirects, and whether your hosting provider blocks the route behind authentication.

“Signature verification always fails”

The usual causes are parsing the body before verifying it, using the wrong secret, copying a secret with whitespace, or missing one of the signature headers. Preserve the raw body and verify before calling JSON.parse or framework JSON helpers.

Do not test a signed endpoint by inventing a JSON request in Postman and expecting verification to pass. A manually generated payload lacks the provider’s valid signature. Use the provider’s test feature, trigger a real low-risk event, or temporarily test only the routing layer with signature verification still enforced in production.

“I get duplicate events”

Assume duplicates are normal. A timeout, network interruption, or non-2xx response can lead to redelivery. Add a unique database constraint keyed by provider plus delivery ID, then make handlers safe when the same email status arrives twice.

Resend supports replaying webhook events, including previously successful ones. Replays are useful for recovery, but they are another reason downstream updates must be idempotent. (resend.com)

“Inbound events arrive, but the message body is missing”

That may be expected behavior rather than data loss. With Resend Receiving, use the received email API to fetch content and the attachments API to fetch attachment metadata and download URLs after you receive the event. Do not assume all providers put full MIME content directly in a webhook payload. (resend.com)

“An open event says the user read the email”

Open tracking is a weak engagement signal. It relies on image loading and can be affected by privacy features, client behavior, and automated security scanning. Resend explicitly notes that open rates are not always accurate. Use opens for broad analytics, not as proof that a person read a time-sensitive or legally important message. (resend.com)

How to know your email webhook integration worked

Do not stop at “I received JSON once.” Validate the full chain with a small acceptance checklist.

  1. Reachability: the provider dashboard shows a successful event attempt to your HTTPS endpoint.
  2. Authenticity: invalid signatures return an error and create no side effects.
  3. Correct routing: each chosen event type runs the intended code path.
  4. Persistence: your event record is stored with its provider delivery ID.
  5. Idempotency: resending the same event changes nothing twice.
  6. Failure behavior: temporarily return 500 in a test environment and confirm provider retry behavior.
  7. Business outcome: a bounce suppresses the address, an inbound message opens the intended workflow, or a delivered event updates the right application message.
  8. Observability: you can correlate a provider event, webhook delivery ID, internal job ID, and final record in your database.

Build a small internal event viewer early. Even a filtered database table showing event type, timestamp, recipient, provider message ID, delivery ID, processing state, and last error is much more useful than searching raw logs during an incident.

For a broader sending implementation, use your provider’s API documentation and email API setup guides alongside this webhook layer. Sending is the command path; webhooks are the evidence and reaction path.

When a free setup is no longer enough

A free email webhook architecture is a good starting point when traffic is low, a short outage is tolerable, and a single developer can monitor the flow. Move beyond it when webhook events become business-critical or when the cost of losing or delaying an event exceeds the cost of better infrastructure.

Upgrade the architecture—not necessarily every vendor—when you need:

  • durable queues with dead-letter handling;
  • long-term event retention and analytics;
  • multiple independent webhook consumers;
  • alerting on endpoint failures and retry exhaustion;
  • stronger audit controls for inbound content;
  • high-volume sending beyond a provider’s free daily or monthly cap;
  • dedicated operational ownership for deliverability, abuse prevention, and incident response.

A scalable next step is to write every verified webhook into an append-only event table or queue, acknowledge it, and let separate workers produce side effects. That gives you replay capability under your control, makes database outages easier to manage, and lets you add a new consumer later without modifying the original receiver.

FAQ

Are email webhooks free?

The webhook feature may be included in an email provider’s free tier, but the complete system can still have costs for domains, sending volume, storage, automation tasks, or serverless compute. Check both the email provider’s limits and the hosting limits for your endpoint.

Can I receive incoming email with a webhook for free?

Yes, some providers support inbound email webhooks within a free allowance. For example, Resend’s published Free plan includes sending and receiving, and its managed receiving domain can simplify initial testing. For branded inbound addresses, you will generally need a domain you control and the required DNS configuration. (resend.com)

Do I need a server for email webhooks?

You need a public HTTPS endpoint, but it does not have to be a traditional always-on server. A serverless function or edge worker can receive webhooks. Keep the request handler fast and move expensive work to a queue or background process.

Should I save every email webhook event?

Save events that affect product state, deliverability, user support, compliance, or debugging. At minimum, retain the provider, delivery ID, event type, relevant message ID, processing result, and timestamp. Decide separately whether raw payloads and inbound content need retention.

Why does my webhook receive the same event more than once?

Providers may retry when they do not receive a successful response or when a network failure makes delivery uncertain. Design for at-least-once delivery: verify the request, store a unique delivery ID, and make every business action idempotent.