Delivery Events
After you submit an email message to Email API via the /send
endpoint, the
message is queued for delivery within MailChannels' extensive cloud
infrastructure. When something happens to your message, Email API can send you
a webhook callback allowing you to track message delivery attempts and failures
(i.e. bounces), user interactions such as opens and clicks, and more.
Webhook Management
Webhooks are managed via the /webhook
endpoint. The HTTP request method
specifies whether you are creating, retrieving, or removing a webhook:
POST
: Create a webhook to receive delivery eventsGET
: Retrieve an existing webhook configurationDELETE
: Remove an existing webhook configuration
Creating a Webhook
To create a webhook that will receive delivery events from Email API, send a
POST
request to /webhook
specifying the URL at which you wish to receive
webhook requests from Email API:
curl -X POST 'https://api.mailchannels.net/tx/v1/webhook?endpoint=YOUR_ENDPOINT_URL' \
-H 'X-Api-Key: YOUR_API_KEY'
Replace YOUR_ENDPOINT_URL
with your webhook receiver URL and YOUR_API_KEY
with your MailChannels API key.
Event Notification Format
Once configured, MailChannels will send event notifications to your webhook in the following format:
{
"email": "sender@example.com",
"customer_handle": "abc123",
"timestamp": 1625097600,
"event": "processed"
}
See the Appendix below for a JSON schema definition.
Event Fields
email
: Sender email addresscustomer_handle
: the account ID that you can find in the upper-right corner of the MailChannels Consoletimestamp
: Unix timestamp of the eventevent
: Event type, such asdelivered
orbounced
(see Supported Event Types below)
The
customer_handle
is set so that you can have a single webhook receiver that receives webhooks for multiple MailChannels accounts. For instance, if your account ID isexampleuser
then thecustomer_handle
field will be set toexampleuser
for webhooks related to emails sent by that MailChannels account.
To improve your webhook security, we highly recommend verifying that the customer_handle
value matches your account ID.
To find your account ID, go to the upper-right corner of the MailChannels Console
Retrieving Webhook Configuration
To view your current webhook configuration:
curl -X GET 'https://api.mailchannels.net/tx/v1/webhook' \
-H 'X-Api-Key: YOUR_API_KEY'
Deleting a Webhook
To stop receiving event notifications:
curl -X DELETE 'https://api.mailchannels.net/tx/v1/webhook' \
-H 'X-Api-Key: YOUR_API_KEY'
Supported Event Types
Event Type | Description |
---|---|
processed | Email received and being processed for delivery |
delivered | Email successfully delivered to recipient's server |
dropped | An error occurred when processing the email |
bounced | Email could not be delivered to recipient's server |
opened* | Recipient opened the email |
clicked* | Recipient clicked a link in the email |
complained* | Recipient filed a spam complaint about the email |
unsubscribed* | Recipient indicated they wish to unsubscribe from the email |
Note: If a dropped
event is generated, it means the API request also generated
an error. So if your event-processing code sees dropped
, you can safely assume that
the original API call to send the message also failed in your client code.
opened
and clicked
events require tracking to be enabled, which is not available in our free tier.
complained
and unsubscribed
are reserved for future use.
Best Practices
- Ensure your webhook endpoint can handle concurrent requests.
- Authenticate our requests by checking that each request contains your account ID in the
customer_handle
field. - Process events asynchronously to avoid blocking the webhook receiver.
- Implement retry logic in case of temporary failures.
- Store raw event data before processing to allow for reprocessing if needed.
By leveraging delivery events, you can gain real-time insights into email delivery status, improve deliverability, and enhance your email sending strategies.
Appendix: Sample Webhook Code
- Go
- JavaScript
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"time"
)
const (
ExpectedCustomerHandle = "myaccountid"
)
type WebhookPayload struct {
Email string `json:"email"`
CustomerHandle string `json:"customer_handle"`
Timestamp int64 `json:"timestamp"`
Event string `json:"event"`
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading request body", http.StatusInternalServerError)
return
}
defer r.Body.Close()
var payload WebhookPayload
err = json.Unmarshal(body, &payload)
if err != nil {
http.Error(w, "Error parsing JSON", http.StatusBadRequest)
return
}
// Validate required fields
// Note: payload.Email is not present in all notifications
if payload.CustomerHandle == "" || payload.Timestamp == 0 || payload.Event == "" {
http.Error(w, "Missing required fields", http.StatusBadRequest)
return
}
// Verify customer handle
if payload.CustomerHandle != ExpectedCustomerHandle {
http.Error(w, "Invalid customer handle", http.StatusForbidden)
return
}
// Validate event type
validEvents := map[string]bool{
"processed": true,
"delivered": true,
"dropped": true,
"opened": true,
"clicked": true,
"bounced": true,
"complained": true,
"unsubscribed": true,
}
if !validEvents[payload.Event] {
http.Error(w, "Invalid event type", http.StatusBadRequest)
return
}
// Process the webhook payload
fmt.Printf("Received webhook: Email: %s, Customer: %s, Event: %s, Time: %s\n",
payload.Email,
payload.CustomerHandle,
payload.Event,
time.Unix(payload.Timestamp, 0).Format(time.RFC3339))
w.WriteHeader(http.StatusOK)
w.Write([]byte("Webhook received successfully"))
}
func main() {
http.HandleFunc("/webhook", webhookHandler)
fmt.Println("Starting server on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const port = 3000;
const EXPECTED_CUSTOMER_HANDLE = 'myaccountid';
// Middleware to parse JSON bodies
app.use(bodyParser.json());
// Webhook route
app.post('/webhook', (req, res) => {
const { email, customer_handle, timestamp, event } = req.body;
// Validate required fields
if (!customer_handle || !timestamp || !event) {
return res.status(400).json({ error: 'Missing required fields' });
}
// Verify customer handle
if (customer_handle !== EXPECTED_CUSTOMER_HANDLE) {
return res.status(403).json({ error: 'Invalid customer handle' });
}
// Validate event type
const validEvents = ['processed', 'delivered', 'dropped', 'opened', 'clicked', 'bounced', 'complained', 'unsubscribed'];
if (!validEvents.includes(event)) {
return res.status(400).json({ error: 'Invalid event type' });
}
// Process the webhook payload
console.log(`Received webhook: Email: ${email}, Customer: ${customer_handle}, Event: ${event}, Time: ${new Date(timestamp * 1000).toISOString()}`);
// Here you could add additional processing logic if needed
res.status(200).json({ message: 'Webhook received successfully' });
});
// Start the server
app.listen(port, () => {
console.log(`Webhook receiver listening at http://localhost:${port}`);
});
Appendix: JSON Schema
The following JSON Schema describes the data structure that will be sent to your webhook by Email API.
The event types complained
and unsubscribed
are reserved for future use.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email",
"description": "The sender's email address"
},
"customer_handle": {
"type": "string",
"description": "The MailChannels account ID that generated the webhook"
},
"timestamp": {
"type": "integer",
"description": "The Unix timestamp (in seconds) when the event occurred; the timezone is always UTC"
},
"event": {
"type": "string",
"enum": ["processed", "delivered", "opened", "clicked", "bounced", "complained", "unsubscribed"],
"description": "The type of event that occurred"
},
"url": {
"type": "string",
"description": "For click events, the URL that was clicked by the recipient"
},
"user_agent": {
"type": "string",
"description": "For click and open events, the User-Agent header given when the recipient clicked a link or opened a message"
},
"smtp_id": {
"type": "string",
"description": "For click and open events, the Message-Id of the message that generated the event"
},
"ip": {
"type": "string",
"description": "For click and open events, the IP address of the host that made the HTTP request"
}
},
"required": ["customer_handle", "timestamp", "event"],
"additionalProperties": false
}