MailSlurp logo

Webhook Documentation

Documentation navigation
Search documentation

Receive MailSlurp email and SMS events by webhook, handle retries safely, and route new messages into your application as they arrive.

Webhooks let you receive event payloads directly on your server when something happens, such as a new email, an inbound SMS message, or an account bounce. Webhooks can be attached to inboxes, phone numbers, or your account. See the event type payloads below for schema examples.

webhooks

About

Open video on YouTube

Why use webhooks? Webhooks let you respond to events when they occur. They remove the need to continually poll the MailSlurp API to request the latest emails. This mitigates rate-limiting errors. Webhooks also enable graceful error handling because they are backed by a queue and can be retried over time when your server is down or throwing exceptions.

Webhook event types

Webhook events are listed below. Each event type indicates the triggering action for the webhook. For instance NEW_EMAIL webhooks are triggered when an inbox they are attached to receives a new email.

See the event documentation for details on each type.

Delivery and idempotency

MailSlurp delivers webhooks at least once, so your endpoint may receive the same payload more than once. Use the unique messageId on each payload as an idempotency key. Save it on your server, check it when a webhook arrives, and skip any payload you have already processed.

Message payload

When a webhook is triggered, MailSlurp posts the corresponding JSON payload to the webhook URL. See the event type documentation below for schemas. Each MailSlurp library contains webhook payload types that extend AbstractWebhookPayload. Use the eventName property or the x-event header to cast the event to the appropriate concrete type.

function handleWebhookPayload(body: AbstractWebhookPayload) {
  // Use the event name to cast the payload to its concrete type.
  if (body.eventName === AbstractWebhookPayloadEventNameEnum.NEW_EMAIL) {
    const event = body as unknown as WebhookNewEmailPayload;
    log.info(event.emailId);
  } else {
    throw new Error("Unexpected webhook event");
  }
}

Or for simpler usage parse the request body into your expected payload:

const newEmail: WebhookNewEmailPayload = JSON.parse(requestBody);
log.info(
  `New email from ${newEmail.from} with subject ${newEmail.subject}`
);

Allow a static IP address range

MailSlurp sends webhooks from a static IP list if you use the useStaticIpRange flag when creating the webhook.

Public IPv4 address
Use the dashboard or contact support for the current static IP range.

To use static IP webhooks in code do the following:

// create a webhook with static ip
const webhook = await webhookController.createWebhook({
  inboxId: inbox.id,
  createWebhookOptions: {
    eventName: CreateWebhookOptionsEventNameEnum.NEW_CONTACT,
    // send test webhook to service that returns caller ip address
    url: 'https://f.mailslurp.link/f/getip',
    // must set static ip flag to enable
    useStaticIpRange: true
  }
});
// send test webhook
const result = await webhookController.sendTestData({
  webhookId: webhook.id
});
// check ip address in result
expect(publiclyKnownStaticIpRange).toContain(result.response.message);

Allow these IP addresses through your network firewall if you are having trouble receiving webhooks. Contact support for help.

Custom payload and redirect

You can customize the shape of the event payload for use with other services such as Slack or Teams by providing a requestBodyTemplate property containing templated JSON.

await mailslurp.webhookController.createWebhook({
  inboxId: inbox.id,
  createWebhookOptions: {
    eventName: CreateWebhookOptionsEventNameEnum.NEW_EMAIL,
    url: slackIncomingWebhookUrl,
    // custom request body for slack uses {{subject}} to insert
    // the subject from the standard NEW_EMAIL payload
    requestBodyTemplate: `{"text":"New message: {{subject}}"}`,
  },
});

Use mustache style templating to insert properties from the standard payload for the event into your custom payload.

Sent headers

Each webhook is sent via HTTP with the following headers:

Header Example Description
x-from api.mailslurp.com Identifies the MailSlurp server that sent the webhook.
x-event NEW_EMAIL Names the event that triggered the webhook.
x-signature sig-29s033if2 Works with the signature verification endpoint to verify the payload.
x-message-id 38547638 Unique ID for the webhook payload. Use this ID to avoid processing a webhook multiple times.
Authorization Basic xdsf924 Basic authentication. Only set if your webhook was created with a username and password.

You can add custom headers when you create a webhook and these header name value pairs will be sent with every request. Use these static headers in your application if required. See the static header section for more information.

Create and manage webhooks

Webhooks can be created in the MailSlurp dashboard or using the API WebhookController. Webhooks can be attached to an inbox or phone number or created without one depending on the event type.

Create email webhook

The examples in this section use the MailSlurp SDK for JavaScript, Python, Java or C#. Choose a language once and every example on the page switches to it. Install the SDK for your language, set MAILSLURP_API_KEY in your server environment, then create a client:

JavaScript

import { MailSlurp, CreateWebhookOptionsEventNameEnum } from 'mailslurp-client';

export function createClient() {
  const apiKey = process.env.MAILSLURP_API_KEY;
  if (!apiKey) throw new Error('Set MAILSLURP_API_KEY');
  return new MailSlurp({ apiKey });
}

Python

import os

import mailslurp_client


def create_client():
    api_key = os.environ.get("MAILSLURP_API_KEY")
    if not api_key:
        raise RuntimeError("Set MAILSLURP_API_KEY")
    configuration = mailslurp_client.Configuration()
    configuration.api_key["x-api-key"] = api_key
    return mailslurp_client.ApiClient(configuration)

Java

import com.mailslurp.apis.*;
import com.mailslurp.clients.ApiClient;
import com.mailslurp.clients.ApiException;
import com.mailslurp.models.*;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

final class WebhookSetup {
    static ApiClient createClient() {
        String apiKey = System.getenv("MAILSLURP_API_KEY");
        if (apiKey == null || apiKey.trim().isEmpty()) {
            throw new IllegalStateException("Set MAILSLURP_API_KEY");
        }
        ApiClient client = new ApiClient();
        client.setApiKey(apiKey);
        client.setConnectTimeout(10_000);
        client.setReadTimeout(75_000);
        return client;
    }
}

C#

using System;
using System.Collections.Generic;
using System.Linq;
using mailslurp.Api;
using mailslurp.Client;
using mailslurp.Model;

public static class WebhookSetup
{
    public static Configuration CreateConfiguration()
    {
        var apiKey = Environment.GetEnvironmentVariable("MAILSLURP_API_KEY");
        if (string.IsNullOrWhiteSpace(apiKey))
            throw new InvalidOperationException("Set MAILSLURP_API_KEY");
        var configuration = new Configuration { Timeout = TimeSpan.FromSeconds(75) };
        configuration.ApiKey.Add("x-api-key", apiKey);
        return configuration;
    }
}

Create a webhook for an inbox. MailSlurp sends a JSON payload to your URL each time the inbox receives an email:

JavaScript

export async function createWebhook(mailslurp, inboxId, url) {
  // MailSlurp POSTs a JSON payload to `url` each time this inbox receives an email.
  return mailslurp.webhookController.createWebhook({
    inboxId,
    createWebhookOptions: { url, name: 'New order emails', eventName: CreateWebhookOptionsEventNameEnum.NEW_EMAIL },
  });
}

Python

def create_webhook(api_client, inbox_id, url):
    # MailSlurp POSTs a JSON payload to `url` each time this inbox receives an email.
    return mailslurp_client.WebhookControllerApi(api_client).create_webhook(
        inbox_id,
        mailslurp_client.CreateWebhookOptions(url=url, name="New order emails", event_name="NEW_EMAIL"),
    )

Java

public static WebhookDto createWebhook(ApiClient client, UUID inboxId, String url) throws ApiException {
    // MailSlurp POSTs a JSON payload to `url` each time this inbox receives an email.
    return new WebhookControllerApi(client).createWebhook(inboxId, new CreateWebhookOptions()
        .url(url)
        .name("New order emails")
        .eventName(CreateWebhookOptions.EventNameEnum.NEW_EMAIL)).execute();
}

C#

public static WebhookDto CreateWebhook(Configuration configuration, Guid inboxId, string url)
{
    // MailSlurp POSTs a JSON payload to `url` each time this inbox receives an email.
    return new WebhookControllerApi(configuration).CreateWebhook(inboxId, new CreateWebhookOptions(
        url: url, name: "New order emails", eventName: CreateWebhookOptions.EventNameEnum.NEWEMAIL));
}

List the webhooks on an inbox, including their delivery health:

JavaScript

export async function listWebhooks(mailslurp, inboxId) {
  const page = await mailslurp.webhookController.getAllWebhooks({ inboxId, size: 20 });
  return page.content.map(webhook => ({ id: webhook.id, url: webhook.url, event: webhook.eventName, health: webhook.healthStatus }));
}

Python

def list_webhooks(api_client, inbox_id):
    page = mailslurp_client.WebhookControllerApi(api_client).get_all_webhooks(inbox_id=inbox_id, size=20)
    return [(w.id, w.url, w.event_name, w.health_status) for w in page.content]

Java

public static List<String> listWebhooks(ApiClient client, UUID inboxId) throws ApiException {
    return new WebhookControllerApi(client).getAllWebhooks().inboxId(inboxId).size(20).execute()
        .getContent().stream()
        .map(webhook -> webhook.getId() + " " + webhook.getUrl() + " " + webhook.getEventName())
        .collect(Collectors.toList());
}

C#

public static List<string> ListWebhooks(Configuration configuration, Guid inboxId)
{
    return new WebhookControllerApi(configuration).GetAllWebhooks(inboxId: inboxId, size: 20).Content
        .Select(webhook => $"{webhook.Id} {webhook.Url} {webhook.EventName}")
        .ToList();
}

Send sample data to check that your endpoint responds, then delete webhooks you no longer need:

JavaScript

export async function sendTest(mailslurp, webhookId) {
  // Sends a sample payload to your endpoint and returns the response it gave.
  const result = await mailslurp.webhookController.sendTestData({ webhookId });
  return { status: result.response.statusCode, message: result.message };
}

Python

def send_test(api_client, webhook_id):
    # Sends a sample payload to your endpoint and returns the response it gave.
    result = mailslurp_client.WebhookControllerApi(api_client).send_test_data(webhook_id)
    return result.response.status_code, result.message

Java

public static Integer sendTest(ApiClient client, UUID webhookId) throws ApiException {
    // Sends a sample payload to your endpoint and returns the response it gave.
    WebhookTestResult result = new WebhookControllerApi(client).sendTestData(webhookId).execute();
    return result.getResponse().getStatusCode();
}

C#

public static int? SendTest(Configuration configuration, Guid webhookId)
{
    // Sends a sample payload to your endpoint and returns the response it gave.
    var result = new WebhookControllerApi(configuration).SendTestData(webhookId);
    return result.Response.StatusCode;
}

JavaScript

export async function deleteWebhook(mailslurp, webhookId) {
  await mailslurp.webhookController.deleteWebhookById({ webhookId });
}

Python

def delete_webhook(api_client, webhook_id):
    mailslurp_client.WebhookControllerApi(api_client).delete_webhook_by_id(webhook_id)

Java

public static void deleteWebhook(ApiClient client, UUID webhookId) throws ApiException {
    new WebhookControllerApi(client).deleteWebhookById(webhookId).execute();
}

C#

public static void DeleteWebhook(Configuration configuration, Guid webhookId)
{
    new WebhookControllerApi(configuration).DeleteWebhookById(webhookId);
}

The complete webhook examples include dependency files for each language. This TypeScript example creates a new inbox and a NEW_EMAIL webhook for it with the event name enum:

const inbox = await mailslurp.createInbox();
const webhook = await mailslurp.webhookController.createWebhook({
  inboxId: inbox.id!,
  createWebhookOptions: {
    eventName: CreateWebhookOptionsEventNameEnum.NEW_EMAIL,
    url: testEndpoint.url!!,
  },
});

Account-scoped webhooks

For account-based events such as BOUNCE, pass null for the inbox ID:

const webhook = await mailslurp.webhookController.createAccountWebhook({
  createWebhookOptions: {
    eventName: CreateWebhookOptionsEventNameEnum.BOUNCE,
    url: testEndpoint.url!!,
  },
});

Account-scoped webhooks are useful for processing events from every inbox or your whole account. For example, one account webhook with the NEW_EMAIL type can receive inbound email events across all of your inboxes.

Phone-scoped webhooks

SMS related events support phone number scoping like so:

await mailslurp.webhookController.createWebhookForPhoneNumber({
  phoneNumberId: phone.id,
  createWebhookOptions: {
    eventName: CreateWebhookOptionsEventNameEnum.NEW_SMS,
    url: testEndpoint.url!!
  }
});

Set static headers

MailSlurp can include your static headers with every webhook request. Set the includeHeaders option when creating the webhook, and each POST request to your server will contain the provided name-value pairs.

// create webhook
const createWebhookOptions: CreateWebhookOptions = {
  name: "my-webhook",
  url: "https://your.server",
  includeHeaders: {
    headers: [{ name: "x-test-header", value: "123" }],
  },
};
const webhook = await webhookController.createWebhook({
  inboxId: inbox.id!!,
  createWebhookOptions: createWebhookOptions,
});

Authentication

To secure your endpoint with basic authentication, specify a username and password when you create the webhook. MailSlurp sends them in an Authorization header with the value Basic <credentials>, where the credentials are a base64-encoded string containing the username and password separated by a colon.

Setup your server

To receive webhook payloads, expose a public HTTPS endpoint that responds with a 200-299 status code. If your endpoint returns a redirect or error status, MailSlurp queues the payload and retries it with backoff.

Example handler

You can use any framework or server to handle MailSlurp webhooks. Here is an example using Node.js:

import { WebhookNewSmsPayload } from 'mailslurp-client';

import bodyParser from 'body-parser';
import express from 'express';

// create a server
const app = express();
app.use(bodyParser());

/**
 * Define an endpoint on your server for the NEW_SMS webhook event
 */
app.post('/inbound/new-sms', (request, response) => {
  // access the data on request body
  // and cast to the expected event type take action
  const sms = request.body as WebhookNewSmsPayload;
  // access the entity
  log.info(`New SMS from ${sms.fromNumber}`);
  // return a 2xx status code so MailSlurp knows you received it
  response.sendStatus(200);
});

Verify webhook signature

MailSlurp sends an x-signature header that can be used with the x-message-id header to verify a webhook payload. Pass both request headers to the verification endpoint from your handler:

JavaScript

export async function verifySignature(mailslurp, headers) {
  // In your webhook handler, pass the x-message-id and x-signature request headers.
  const { isValid } = await mailslurp.webhookController.verifyWebhookSignature({
    verifyWebhookSignatureOptions: { messageId: headers['x-message-id'], signature: headers['x-signature'] },
  });
  return isValid;
}

Python

def verify_signature(api_client, headers):
    # In your webhook handler, pass the x-message-id and x-signature request headers.
    result = mailslurp_client.WebhookControllerApi(api_client).verify_webhook_signature(
        mailslurp_client.VerifyWebhookSignatureOptions(
            message_id=headers["x-message-id"], signature=headers["x-signature"]
        )
    )
    return result.is_valid

Java

public static boolean verifySignature(ApiClient client, Map<String, String> headers) throws ApiException {
    // In your webhook handler, pass the x-message-id and x-signature request headers.
    VerifyWebhookSignatureResults result = new WebhookControllerApi(client).verifyWebhookSignature(
        new VerifyWebhookSignatureOptions()
            .messageId(headers.get("x-message-id"))
            .signature(headers.get("x-signature"))).execute();
    return Boolean.TRUE.equals(result.getIsValid());
}

C#

public static bool VerifySignature(Configuration configuration, IDictionary<string, string> headers)
{
    // In your webhook handler, pass the x-message-id and x-signature request headers.
    var result = new WebhookControllerApi(configuration).VerifyWebhookSignature(
        new VerifyWebhookSignatureOptions(messageId: headers["x-message-id"], signature: headers["x-signature"]));
    return result.IsValid;
}

The same check inside a JavaScript test:

const signature = request.header("x-signature");
const messageId = request.header("x-message-id");
const { isValid } = await mailslurp.webhookController.verifyWebhookSignature({
  verifyWebhookSignatureOptions: { signature, messageId },
});
expect(isValid).toBeTruthy();

Results and redrive

You can view webhook delivery results on the dashboard webhooks page. MailSlurp queues webhook deliveries and retries a payload when your endpoint returns an unsuccessful response status.

webhook history

Event types

Each webhook responds to one event type. When the attached inbox, phone number, or account produces that event, MailSlurp sends its JSON payload to the URL you specified using an HTTPS POST request. Each event has a different payload, as documented below.

Build a receiver that survives retries

Follow reliable webhook processing for durable queueing, duplicate handling and redrive. For extraction events, continue with AI results; for an agent that replies to messages, use conversation claims and guarded completion.