Webhooks
Webhooks allow you to subscribe to real-time notifications about various events that occur in MailerSend.
You can create a webhook directly from your MailerSend account and listen for events so your integration can automatically trigger reactions.
Webhooks overview
Setup
Currently, you can create a webhook using the API endpoints listed below or directly from your account. Read more about webhooks.
Webhook Failure Warning
If your webhook fails 10 times within 24 hours, you will receive a notification. If the failures reach 20 within the same period, the webhook will be paused automatically, and no further notifications will be sent until the webhook is re-enabled.
Available events
These are all the events you can listen to and send a notification for.
Event | Description |
---|---|
activity.sent | Fired when your email is sent from our sending servers. We are now waiting for a response from the receiving servers. |
activity.delivered | Fired when your email is successfully delivered with no errors. |
activity.soft_bounced | Fired when your email is not delivered because it soft bounced. |
activity.hard_bounced | Fired when your email is not delivered. |
activity.opened | Fired when the recipient receives your email and opens it. |
activity.opened_unique | Fired when the recipient receives your email and opens it only for the first time. |
activity.clicked | Fired when the recipient clicks a link in your email. |
activity.clicked_unique | Fired when the recipient clicks a link in your email only for the first time. |
activity.unsubscribed | Fired when the recipient unsubscribes from your emails. |
activity.spam_complaint | Fired when the recipient marks your emails as spam or junk. |
activity.survey_opened | Fired when the recipient opens an email containting a survey for the first time. |
activity.survey_submitted | Fired when the recipient answers all available questions in a survey based email or after an idle time of 30 minutes. |
sender_identity.verified | Fired when the sender identity has been successfully verified. |
maintenance.start | Fired when the maintenance period begins. (More info in Handling Maintenance Modes). |
maintenance.end | Fired when the maintenance period ends. (More info in Handling Maintenance Modes). |
inbound_forward.failed | Fired when an inbound message fail to forward. |
Payload example
Our responses contain fat payloads, including the information about the event-related object, so there is no need to make an additional API request.
An example of activity.sent
event:
{
"type": "activity.sent",
"domain_id": "yv69oxl5kl785kw2",
"created_at": "2020-11-27T10:08:08.298647Z",
"webhook_id": "2351ndgwr4zqx8ko",
"url": "https://your-domain.com/webhook",
"data": {
"object": "activity",
"id": "5fc0d006b42c3e16e1774882",
"type": "sent",
"created_at": "2020-11-27T10:08:06.258000Z",
"email": {
"object": "email",
"id": "5fc0d003e7a5e7035446aa32",
"created_at": "2020-11-27T10:08:03.923000Z",
"from": "test@mailersend.com",
"subject": "Test email",
"status": "sent",
"tags": null,
"message": {
"object": "message",
"id": "5fc0d003f718c90162341852",
"created_at": "2020-11-27T10:08:03.017000Z"
},
"recipient": {
"object": "recipient",
"id": "5f6887d6fd913a6523283fd2",
"email": "test@mailersend.com",
"created_at": "2020-09-21T11:00:38.184000Z"
}
},
"template_id": "7nxe3yjmeq28vp0k",
"morph": null
}
}
An example of activity.survey_submitted
event:
{
"type": "activity.sent",
"domain_id": "yv69oxl5kl785kw2",
"created_at": "2020-11-27T10:08:08.298647Z",
"webhook_id": "2351ndgwr4zqx8ko",
"url": "https://your-domain.com/webhook",
"data": {
"email": {
"object": "email",
"id": "643e785e1ad4b461a307e5d2",
"created_at": "2023-04-18T11:00:46.609000Z",
"from": "test@domain.com",
"subject": "Test email",
"status": "sent",
"tags": null,
"preview_url": "http://preview.mailersend.com/email/643e785e1ad4b461a307e5d2",
"template": {
"object": "template",
"id": "83gwk2j7zqz1nxyd",
"url": "http://app.mailersend.com/templates/83gwk2j7zqz1nxyd"
},
"message": {
"object": "message",
"id": "643e785d4d8189e0ef046aa2",
"created_at": "2023-04-18T11:00:45.398000Z"
},
"recipient": {
"object": "recipient",
"id": "6410951922b6316e210cb2e2",
"email": "reciepient@email.com",
"created_at": "2023-03-14T15:39:05.608000Z"
}
},
"surveys": [
{
"survey_location_url": "http://preview.mailersend.com/email/643e785e1ad4b461a307e5d2#ml-survey-link-5",
"survey_id": "4",
"question_id": "5",
"question_index": 0,
"question_type": "intro",
"question": "We value your feedback.",
"next_question_index": 1,
"answers": [
{
"answer": "1",
"answer_id": "1"
}
],
"rules": [
{
"$$hashKey": "object:3113",
"answered": "answered_specific",
"collapsed": true,
"question_index": 1,
"specific_answer": 1,
"specific_answer_value": "Very Unsatisfied",
"action": "skip_to_question",
"action_question_index": 2
}
],
"correct_answers_rate": 0,
"is_last_question": false
},
{
"survey_location_url": "http://preview.mailersend.com/email/643e785e1ad4b461a307e5d2#ml-survey-link-5",
"survey_id": "4",
"question_id": "6",
"question_index": 1,
"question_type": "satisfaction_scale",
"question": "How would you rate our new product line?",
"next_question_index": 2,
"answers": [
{
"answer": "5",
"answer_id": "5",
"image": "https://assets.mlcdn.com/ml/images/editor/survey/faces/color/5.png"
}
],
"rules": [
{
"$$hashKey": "object:3113",
"answered": "answered_specific",
"collapsed": true,
"question_index": 1,
"specific_answer": 1,
"specific_answer_value": "Very Unsatisfied",
"action": "skip_to_question",
"action_question_index": 2
}
],
"correct_answers_rate": 0,
"is_last_question": false
},
{
"survey_location_url": "http://preview.mailersend.com/email/643e785e1ad4b461a307e5d2#ml-survey-link-5",
"survey_id": "4",
"question_id": "7",
"question_index": 2,
"question_type": "rating_scale",
"question": "How likely are you to recommend our new products to a friend or colleague?",
"next_question_index": 3,
"answers": [
{
"answer": "2",
"answer_id": "2"
}
],
"rules": [
{
"$$hashKey": "object:3113",
"answered": "answered_specific",
"collapsed": true,
"question_index": 1,
"specific_answer": 1,
"specific_answer_value": "Very Unsatisfied",
"action": "skip_to_question",
"action_question_index": 2
}
],
"correct_answers_rate": 0,
"is_last_question": true
}
]
}
}
An example of sender_identity.verified
event:
{
"type": "sender_identity.verified",
"domain_id": "7z3m5jgrogdpyo6n",
"created_at": "2023-07-05T08:14:27.281958Z",
"webhook_id": "7z3m5jgrogdpyo6n",
"url": "https://your-domain.com/webhook",
"data": {
"object": "sender_identity",
"id": "1234asdfg6zqx8ko",
"email": "info@example.com",
"name": "Test Name",
"reply_to_email": "replyto@example.com",
"reply_to_name": "Test Reply To",
"is_verified": true,
"add_note": true,
"personal_note": "Hi, I’m adding this email address to our sender identities in our MailerSend account so it can be shown as the From address in our emails. Can you click on the confirmation link, so that I can complete the process?",
"domain": {
"object": "domain",
"id": "1234asdfg6zqx8ko",
"name": "example.com",
"created_at": "2023-01-01T12:00:00.000000Z",
"updated_at": "2023-01-01T12:00:00.000000Z"
}
}
}
An example of maintenance.start
event:
{
"type": "maintenance.start",
"domain_id": "1234asdfg6zqx8ko",
"webhook_id": "1234asdfg6zqx8ko",
"url": "https://your-domain.com/webhook",
"created_at": "2023-01-01T11:30:00.000000Z"
}
An example of maintenance.end
event:
{
"type": "maintenance.end",
"domain_id": "1234asdfg6zqx8ko",
"webhook_id": "1234asdfg6zqx8ko",
"url": "https://your-domain.com/webhook",
"created_at": "2023-01-01T12:00:00.000000Z"
}
An example of inbound_forward.failed
event:
{
"type": "inbound_forward.failed",
"domain_id": "2351ndgwr4zqx8ko",
"created_at": "2023-12-13T18:38:34.681535Z",
"webhook_id": "7z3m5jgrogdpyo6n",
"url": "https://your-domain.com/webhook",
"data": {
"url": "https://your-domain.com/webhook-failed",
"response_code": 404,
"inbound_message": {
"object": "inbound_message",
"id": "6579f9b45070e80427c85478",
"sender": {
"email": "test@example.com"
},
"subject": "Test Email Inbound",
"created_at": "2023-12-13T18:36:36.000000Z"
},
"inbound": {
"object": "inbound",
"id": "7z3m5jgrogdpyo6n",
"domain_id": "2351ndgwr4zqx8ko"
}
}
}
Security
Webhook requests made by MailerSend include a Signature
header. It contains a string generated by hashing the data sent to your webhook endpoint with an individual Signing Secret. A signing secret is a random string that is generated when you create a webhook.
Verifying a signature:
// $signature - a header sent by MailerSend, please refer to your framework
// or PHP manual on how to read the Signature header
// $requestContent - please refer to your framework or PHP manual on how to read the request content
$computedSignature = hash_hmac('sha256', $requestContent, $signingSecret);
return hash_equals($signature, $computedSignature);
Retrying failed webhooks
When your webhook receives a response other than a 2xx
code from your endpoint URL, or if the endpoint doesn’t respond within 3 seconds, it will show up as a failed attempt in the log section of your webhook. If it receives a 2xx
, then it will show as a success.
If a webhook call fails, MailerSend will retry the call a couple of times, waiting 10 seconds between the first and second attempt and 100 seconds between the second and the third attempt. This is to avoid hammering the application you want to send the information to.
Recommendations
To ensure reliable handling of webhooks, we recommend that you:
- Send a 2xx response as soon as possible to confirm receipt of the webhook.
- Move any further processing logic to an asynchronous (async) background job. This allows your endpoint to respond quickly and avoids potential timeouts or repeated attempts.
Useful tools
- Webhook.site or Pipedream.com: These allow you to see the contents of a webhook and inspect what's being sent.
- Reqbin.com: A versatile tool for testing webhooks and making HTTP requests in real time.
FAQs
- How do I track the Message I've sent via SMTP relay back to a webhook?
If the message was sent successfully, our SMTP relay will send you back a 250 Message queued as XXXXXX
response. This can be parsed to an ID used in our Messages endpoints, and in webhooks that Message ID can be found at data.email.message.id
.
If you want full interoperability, we encourage you to use our Email API for the best results.
Get a list of webhooks
If you want to retrieve information about webhooks, use this GET
request:
GET https://api.mailersend.com/v1/webhooks
Request parameters
Query Parameter | Type | Required | Limitations | Details |
---|---|---|---|---|
domain_id | string | yes |
use MailerSend\MailerSend;
$mailersend = new MailerSend(['api_key' => 'key']);
$mailersend->webhooks->get('domain_id');
import 'dotenv/config';
import { MailerSend} from "mailersend";
const mailerSend = new MailerSend({
apiKey: process.env.API_KEY,
});
mailerSend.email.webhook.list("domain_id")
.then((response) => console.log(response.body))
.catch((error) => console.log(error.body));
from mailersend import webhooks
api_key = "API key here"
mailer = webhooks.NewWebhook(api_key)
mailer.get_webhooks("domain-id")
package main
import (
"context"
"log"
"time"
"github.com/mailersend/mailersend-go"
)
var APIKey = "Api Key Here"
func main() {
// Create an instance of the mailersend client
ms := mailersend.NewMailersend(APIKey)
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
domainID := "domain-id"
options := &mailersend.ListWebhookOptions{
DomainID: domainID,
Limit: 25,
}
_, _, err := ms.Webhook.List(ctx, options)
if err != nil {
log.Fatal(err)
}
}
import com.mailersend.sdk.MailerSend;
import com.mailersend.sdk.exceptions.MailerSendException;
import com.mailersend.sdk.webhooks.Webhook;
import com.mailersend.sdk.webhooks.WebhooksList;
public void GetWebhooks() {
MailerSend ms = new MailerSend();
ms.setToken("mailersend token");
try {
WebhooksList list = ms.webhooks().getWebhooks("domain id");
for (Webhook webhook : list.webhooks) {
System.out.println(webhook.name);
}
} catch (MailerSendException e) {
e.printStackTrace();
}
}
require "mailersend-ruby"
ms_webhooks = Mailersend::Webhooks.new
ms_webhooks.list(domain_id: "xxx2241ll")
Get a webhook
To retrieve information about a single webhook, use this GET
request:
GET https://api.mailersend.com/v1/webhooks/{webhook_id}
Request parameters
URL Parameter | Type | Required | Limitations | Details |
---|---|---|---|---|
webhook_id | string | yes |
use MailerSend\MailerSend;
$mailersend = new MailerSend(['api_key' => 'key']);
$mailersend->webhooks->find('webhook_id');
import 'dotenv/config';
import { MailerSend} from "mailersend";
const mailerSend = new MailerSend({
apiKey: process.env.API_KEY,
});
mailerSend.email.webhook.single("webhook_id")
.then((response) => console.log(response.body))
.catch((error) => console.log(error.body));
from mailersend import webhooks
api_key = "API key here"
mailer = webhooks.NewWebhook(api_key)
mailer.get_webhook_by_id("webhook-id")
package main
import (
"context"
"log"
"time"
"github.com/mailersend/mailersend-go"
)
var APIKey = "Api Key Here"
func main() {
// Create an instance of the mailersend client
ms := mailersend.NewMailersend(APIKey)
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
webhookID := "webhook-id"
_, _, err := ms.Webhook.Get(ctx, webhookID)
if err != nil {
log.Fatal(err)
}
}
import com.mailersend.sdk.MailerSend;
import com.mailersend.sdk.exceptions.MailerSendException;
import com.mailersend.sdk.webhooks.Webhook;
public void GetSingleWebhook() {
MailerSend ms = new MailerSend();
ms.setToken("mailersend token");
try {
Webhook webhook = ms.webhooks().getWebhook("webhook id");
System.out.println(webhook.name);
} catch (MailerSendException e) {
e.printStackTrace();
}
}
require "mailersend-ruby"
ms_webhooks = Mailersend::Webhooks.new
ms_webhooks.single(webhook_id: "zzz2241ll")
Create a webhook
Create a webhook using this POST
request:
POST https://api.mailersend.com/v1/webhooks/
Request parameters
JSON parameters are provided in dot notation
JSON Parameter | Type | Required | Limitations | Details |
---|---|---|---|---|
url | url | yes | Max: 191 | |
name | string | yes | Max: 191 | |
events | array | yes | ||
enabled | boolean | optional | ||
domain_id | string | yes | Existing hashed domain ID. |
use MailerSend\Helpers\Builder\WebhookParams;
use MailerSend\MailerSend;
$mailersend = new MailerSend(['api_key' => 'key']);
$mailersend->webhooks->create(
new WebhookParams('https://webhook_url', 'Webhook name', WebhookParams::ALL_ACTIVITIES, 'domain_id')
);
// Or a disabled webhook
$mailersend->webhooks->create(
new WebhookParams('https://webhook_url', 'Webhook name', WebhookParams::ALL_ACTIVITIES, 'domain_id', false)
);
import 'dotenv/config';
import { EmailWebhook, EmailWebhookEventType, MailerSend } from "mailersend";
const mailerSend = new MailerSend({
apiKey: process.env.API_KEY,
});
const emailWebhook = new EmailWebhook()
.setName("Webhook Name")
.setUrl("https://example.com")
.setDomainId("domain_id")
.setEnabled(true)
.setEvents([EmailWebhookEventType.SENT, EmailWebhookEventType.OPENED]);
mailerSend.email.webhook.create(emailWebhook)
.then((response) => console.log(response.body))
.catch((error) => console.log(error.body));
from mailersend import webhooks
api_key = "API key here"
webhookEvents = ['activity.sent', 'activity.delivered']
webhook = webhooks.NewWebhook(api_key)
webhook.set_webhook_url("https://webhooks.mysite.com")
webhook.set_webhook_name("my first webhook")
webhook.set_webhook_events(webhookEvents)
webhook.set_webhook_domain("domain-id")
webhook.create_webhook()
package main
import (
"context"
"log"
"time"
"github.com/mailersend/mailersend-go"
)
var APIKey = "Api Key Here"
func main() {
// Create an instance of the mailersend client
ms := mailersend.NewMailersend(APIKey)
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
domainID := "domain-id"
events := []string{"activity.sent", "activity.opened"}
createOptions := &mailersend.CreateWebhookOptions{
Name: "Webhook",
DomainID: domainID,
URL: "https://test.com",
Enabled: mailersend.Bool(false),
Events: events,
}
_, _, err := ms.Webhook.Create(ctx, createOptions)
if err != nil {
log.Fatal(err)
}
}
import com.mailersend.sdk.MailerSend;
import com.mailersend.sdk.exceptions.MailerSendException;
import com.mailersend.sdk.webhooks.Webhook;
public void CreateWebhook() {
MailerSend ms = new MailerSend();
ms.setToken("mailersend token");
try {
Webhook webhook = ms.webhooks().builder()
.name("Webhook name")
.url("Webhook url")
.addEvent(WebhookEvents.ACTIVITY_OPENED)
.addEvent(WebhookEvents.ACTIVITY_CLICKED)
.createWebhook("domain id");
System.out.println(webhook.name);
} catch (MailerSendException e) {
e.printStackTrace();
}
}
require "mailersend-ruby"
ms_webhooks = Mailersend::Webhooks.new
ms_webhooks.create(domain_id: "xxx2241ll", url: "https://domain.com/hook", name: "Webhook", events: ["activity.sent", "activity.delivered"], enabled: true)
Update a webhook
Update a webhook using this PUT
request:
PUT https://api.mailersend.com/v1/webhooks/{webhook_id}
Request parameters
URL Parameter | Type | Required | Limitations | Details |
---|---|---|---|---|
webhook_id | string | yes |
JSON parameters are provided in dot notation
JSON Parameter | Type | Required | Limitations | Details |
---|---|---|---|---|
url | url | optional | ||
name | string | optional | Max: 191 | |
events | array | optional | ||
enabled | boolean | optional |
use MailerSend\MailerSend;
use MailerSend\Helpers\Builder\WebhookParams;
$mailersend = new MailerSend(['api_key' => 'key']);
$mailersend->webhooks->update('webhook_id', 'https://webhook_url', 'Webhook name', WebhookParams::ALL_ACTIVITIES);
// Enable webhook
$mailersend->webhooks->update('webhook_id', 'https://webhook_url', 'Webhook name', WebhookParams::ALL_ACTIVITIES, true);
// Disable webhook
$mailersend->webhooks->update('webhook_id', 'https://webhook_url', 'Webhook name', WebhookParams::ALL_ACTIVITIES, false);
import 'dotenv/config';
import { EmailWebhook, EmailWebhookEventType, MailerSend } from "mailersend";
const mailerSend = new MailerSend({
apiKey: process.env.API_KEY,
});
const emailWebhook = new EmailWebhook()
.setName("Webhook Name 2")
.setEnabled(false)
.setEvents([EmailWebhookEventType.SENT, EmailWebhookEventType.OPENED]);
mailerSend.email.webhook.update("webhook_id", emailWebhook)
.then((response) => console.log(response.body))
.catch((error) => console.log(error.body));
from mailersend import webhooks
api_key = "API key here"
webhook = webhooks.NewWebhook(api_key)
webhook.update_webhook("webhook-id", "name", "a new webhook name")
package main
import (
"context"
"log"
"time"
"github.com/mailersend/mailersend-go"
)
var APIKey = "Api Key Here"
func main() {
// Create an instance of the mailersend client
ms := mailersend.NewMailersend(APIKey)
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
webhookID := "webhook-id"
events := []string{"activity.clicked"}
updateOptions := &mailersend.UpdateWebhookOptions{
WebhookID: webhookID,
Enabled: mailersend.Bool(true),
Events: events,
}
_, _, err := ms.Webhook.Update(ctx, updateOptions)
if err != nil {
log.Fatal(err)
}
}
import com.mailersend.sdk.MailerSend;
import com.mailersend.sdk.exceptions.MailerSendException;
import com.mailersend.sdk.webhooks.Webhook;
public void UpdateWebhook() {
MailerSend ms = new MailerSend();
ms.setToken("mailersend token");
try {
Webhook webhook = ms.webhooks()
.builder()
.name("Updated webhook name")
.updateWebhook("webhook id");
System.out.println(webhook.name);
} catch (MailerSendException e) {
e.printStackTrace();
}
}
require "mailersend-ruby"
ms_webhooks = Mailersend::Webhooks.new
ms_webhooks.update(webhook_id: "zzz2241ll", enabled: false)
Delete a webhook
Delete a webhook using this DELETE
request:
DELETE https://api.mailersend.com/v1/webhooks/{webhook_id}
Request parameters
URL Parameter | Type | Required | Limitations | Details |
---|---|---|---|---|
webhook_id | string | yes |
use MailerSend\MailerSend;
$mailersend = new MailerSend(['api_key' => 'key']);
$mailersend->webhooks->delete('webhook_id');
import 'dotenv/config';
import { MailerSend} from "mailersend";
const mailerSend = new MailerSend({
apiKey: process.env.API_KEY,
});
mailerSend.email.webhook.delete("webhook_id")
.then((response) => console.log(response.body))
.catch((error) => console.log(error.body));
from mailersend import webhooks
api_key = "API key here"
webhook = webhooks.NewWebhook(api_key)
webhook.delete_webhook("webhook-id")
package main
import (
"context"
"log"
"time"
"github.com/mailersend/mailersend-go"
)
var APIKey = "Api Key Here"
func main() {
// Create an instance of the mailersend client
ms := mailersend.NewMailersend(APIKey)
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
webhookID := "webhook-id"
_, err := ms.Webhook.Delete(ctx, webhookID)
if err != nil {
log.Fatal(err)
}
}
import com.mailersend.sdk.MailerSend;
import com.mailersend.sdk.MailerSendResponse;
import com.mailersend.sdk.exceptions.MailerSendException;
public void DeleteWebhook() {
MailerSend ms = new MailerSend();
ms.setToken("mailersend token");
try {
MailerSendResponse response = ms.webhooks().deleteWebhook("webhook id");
System.out.println(response.responseStatusCode);
} catch (MailerSendException e) {
e.printStackTrace();
}
}
require "mailersend-ruby"
ms_webhooks = Mailersend::Webhooks.new
ms_webhooks.delete(webhook_id: "zzz2241ll")