guides
Testing Webhook Responses for MailSlurp Events
Learn how to test MailSlurp webhooks with deterministic success and failure scenarios, including retry handling and endpoint reliability checks.
Webhook integrations fail most often because teams only test the happy path. This guide focuses on reliability checks you should run before production.
MailSlurp webhook behavior summary
- MailSlurp sends events to your endpoint via HTTP
POST 200/201within timeout window is treated as success- non-success responses and timeouts are retried
- failed deliveries are queued for retry until processed or exhausted
Test scenarios you should always run
| Scenario | Expected result | Why it matters |
|---|---|---|
Endpoint returns 200 quickly |
Event marked delivered | Confirms baseline integration |
Endpoint returns 500 |
Event is retried | Validates retry path |
| Endpoint times out | Event is retried | Validates latency/error handling |
| Endpoint returns malformed response | Event handled as failed | Confirms parser/contract resilience |
Manual testing option
For early local testing:
- expose local service with ngrok or equivalent
- send a controlled event
- verify payload handling and response code
Useful during initial development, but not sufficient as a release gate.
Automated webhook testing option
Use the MailSlurp test webhook helpers to generate deterministic endpoints and simulate known response patterns.
Success-path example:
import { CreateWebhookOptionsEventNameEnum, MailSlurp } from "mailslurp-client";
import {
Configuration as TestWebhookConfiguration,
CreateRulesetOptionsStrategyEnum,
EndpointControllerApi,
} from "@mailslurp/test-webhooks";
const apiKey = process.env.API_KEY!!;
const mailslurp = new MailSlurp({ apiKey });
const devhooksEndpointController = new EndpointControllerApi(
new TestWebhookConfiguration({
basePath: "http://api.infrahooks.com",
}),
);
describe.skip("NEW_EMAIL webhooks", () => {
test("can create NEW_EMAIL webhook and receive successfully", async () => {
// create an inbox, webhook, and a test endpoint
const testEndpoint = await devhooksEndpointController.createEndpoint({});
const inbox = await mailslurp.createInbox();
const webhook = await mailslurp.webhookController.createWebhook({
createWebhookOptions: {
eventName: CreateWebhookOptionsEventNameEnum.NEW_EMAIL,
url: testEndpoint.url!!,
},
inboxId: inbox.id!,
});
// can see that endpoint has not received an event
const endpointHistory = await devhooksEndpointController.getEndpointHistory({
endpointId: testEndpoint.id!,
});
expect(endpointHistory.items?.length).toEqual(0);
// send email to inbox
await mailslurp.sendEmail(inbox.id!, {
to: [inbox.emailAddress!],
subject: "email1",
});
// can fetch the email directly
const email = await mailslurp.waitForLatestEmail(inbox.id!, 60000, true);
expect(email.subject).toEqual("email1");
// endpoint receives the payload (note the expected length to wait for)
const endpointHistory2 = await devhooksEndpointController.getEndpointHistory({
endpointId: testEndpoint.id!,
expectedLength: 1,
});
expect(endpointHistory2.items?.length).toEqual(1);
// assert correct payload was sent to endpoint
const payload = JSON.parse(endpointHistory2.items?.[0]?.request?.body!);
expect(payload.webhookId).toEqual(webhook.id);
expect(payload.eventName).toEqual("NEW_EMAIL");
expect(payload.inboxId).toEqual(inbox.id);
expect(payload.emailId).toEqual(email.id);
expect(payload.to).toEqual([inbox.emailAddress]);
expect(payload.from).toEqual(inbox.emailAddress);
expect(payload.subject).toEqual("email1");
// can see webhook results via mailslurp
const results = await mailslurp.webhookController.getWebhookResults({
webhookId: webhook.id!,
});
expect(results.numberOfElements).toEqual(1);
const result = await mailslurp.webhookController.getWebhookResult({
webhookResultId: results.content?.[0]?.id!,
});
expect(result.resultType).toEqual("SUCCESS");
expect(result.responseStatus).toEqual(200);
await mailslurp.webhookController.deleteWebhook({
inboxId: inbox.id!,
webhookId: webhook.id!,
});
});
});
Failure-path example:
test.skip("can create NEW_EMAIL webhook and see failed results when endpoint fails to accept payload", async () => {
// create a test endpoint that always returns a 401 error
const testEndpoint = await devhooksEndpointController.createEndpoint({});
await devhooksEndpointController.createEndpointRuleset({
endpointId: testEndpoint.id!,
createRulesetOptions: {
strategy: CreateRulesetOptionsStrategyEnum.SINGULAR,
responses: [
{
statusCode: 401,
},
],
},
});
// create inbox and webhook
const inbox = await mailslurp.createInbox();
const webhook = await mailslurp.webhookController.createWebhook({
inboxId: inbox.id!,
createWebhookOptions: {
eventName: CreateWebhookOptionsEventNameEnum.NEW_EMAIL,
url: testEndpoint.url!!,
},
});
// send email to inbox
await mailslurp.sendEmail(inbox.id!, {
to: [inbox.emailAddress!],
subject: "email2",
});
// wait for endpoint to receive payload
const endpointHistory = await devhooksEndpointController.getEndpointHistory({
endpointId: testEndpoint.id!,
expectedLength: 1,
});
expect(endpointHistory.items?.length).toEqual(1);
// can see webhook results via mailslurp
const results = await mailslurp.webhookController.getWebhookResults({
webhookId: webhook.id!,
});
expect(results.numberOfElements).toEqual(1);
const result = await mailslurp.webhookController.getWebhookResult({
webhookResultId: results.content?.[0]?.id!,
});
expect(result.resultType).toEqual("BAD_RESPONSE");
expect(result.responseStatus).toEqual(401);
await mailslurp.webhookController.deleteWebhook({
inboxId: inbox.id!,
webhookId: webhook.id!,
});
});
Production hardening checklist
- enforce idempotency for repeated event deliveries
- log request IDs and event IDs for traceability
- keep response time low (do heavy work asynchronously)
- add alerting for retry spikes and sustained failures
- run webhook contract tests in CI
Recommended endpoint pattern
- Validate payload structure and signature (if enabled).
- Persist the event quickly.
- Return
200. - Process downstream asynchronously.
This pattern avoids accidental retries caused by slow business logic.