API Reference

Webhook Authentication

Mailbox webhooks are authenticated using the Mailsnag-Signature, Mailsnag-Signature-Algorithm and Mailsnag-Signature-Timestamp headers. The Mailsnag-Signature header contains the HMAC hex digest of the payload, Mailsnag-Signature-Algorithm is set to HMAC-256 to indicate hashing algorithm, and Mailsnag-Signature-Timestamp contains the current Unix timestamp in seconds and is used to prevent replay attacks.

In order to verify the webhook request, you need to compute the HMAC hex digest of the payload using your mailbox's Webhook Signing Secret and the Mailsnag-Signature-Algorithm hashing algorithm. The Webhook Signing Secret can be found in the Settings tab of the mailbox.

TIP

payload for hashing is generated by concatenating the Mailsnag-Signature-Timestamp header and the raw request body with a . in between: "{timestamp}.{body}".

Sample code for computing the HMAC hex digest of the payload:


def verify!(signing_key, headers, request_body)
  tolerance = -300..300 # 5 minutes in seconds
  signature = headers["Mailsnag-Signature"]
  timestamp = headers["Mailsnag-Signature-Timestamp"]

  if !signature || !timestamp
    raise "Missing required headers"
  end

  unless tolerance.include?(Time.current.to_i - timestamp.to_i)
    raise "timestamp out of range"
  end

  expected_signature = OpenSSL::HMAC.hexdigest(
    OpenSSL::Digest.new("sha256"),
    signing_key,
    "#{timestamp}.#{request_body}"
  )

  unless ActiveSupport::SecurityUtils.secure_compare(signature, expected_signature)
    raise "Invalid signature"
  end
end
import hashlib
import hmac
import time

def verify(signing_key, headers, request_body):
    tolerance = range(-300, 301)  # 5 minutes in seconds
    signature = headers.get('Mailsnag-Signature')
    timestamp = headers.get('Mailsnag-Signature-Timestamp')
    if not signature or not timestamp:
        raise ValueError('Missing required headers')
    current_timestamp = int(time.time())
    if current_timestamp - int(timestamp) not in tolerance:
        raise ValueError('Timestamp out of range')
    expected_signature = hmac.new(
        signing_key.encode('utf-8'),
        f"{timestamp}.{request_body}".encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(signature, expected_signature):
        raise ValueError('Invalid signature')
    return True
const crypto = require('crypto');

function verify(signingSecret, headers, requestBody) {
  const signature = headers['Mailsnag-Signature'];
  const timestamp = parseInt(headers['Mailsnag-Signature-Timestamp'], 10);
  const payload = `${timestamp}.${requestBody}`;
  const currentTimestamp = Math.floor(Date.now() / 1000);

  if (!signature || !timestamp) {
    throw new Error('Missing required headers');
  }

  if (isNaN(timestamp)) {
    throw new Error("Invalid Signature Headers");
  }

  if (currentTimestamp - timestamp > 300) {
    throw new Error("Message timestamp too old");
  }
  if (timestamp > currentTimestamp + 300) {
    throw new Error("Message timestamp too new");
  }

  const generatedSignature = crypto.createHmac('sha256', signingSecret).update(payload).digest('hex');

  if (generatedSignature !== signature) {
    throw new Error('Invalid signature');
  };
}

Receiving Mailbox

Webhook Payload

Below is the schema of the webhook payload. URLs to download the raw email and attachments are included in the payload. These URLs are valid for as long as message is retained in your Receiving Mailbox. Please make sure to download the raw email and attachments as soon as possible if you need to retain them.

{
  "attachments": [
    {
      "byteSize": Integer, // size of the attachment in bytes
      "cid": String, // Content-ID of the attachment
      "contentType": String, // MIME type of the attachment
      "filename": String, // filename of the attachment
      "id": Integer, // unique identifier of the attachment
      "inline": Boolean, // true if the attachment is inline
      "url": String // URL to download the raw email
    }
  ],
  "byteSize": Integer, // size of the email in bytes
  "cc": [
    String // email address of the recipient from the Cc header
  ],
  "from": String, // email address of the sender from the From header
  "headers": {
    String: String, // key-value pairs of the email headers
  },
  "html": String, // HTML part of the email if available
  "id": Integer, // unique identifier of the email
  "processedAt": String, // ISO 8601 date and time
  "rawEmail": String, // The full RFC 822 message as text. Only included if the mailbox is configured to include raw message
  "rawEmailUrl": String, // URL to download the the full RFC 822 message
  "receivedAt": String, // ISO 8601 date and time,
  "replyTo": [
    String // email address of the reply-to header
  ],
  "smtpFrom": String, // email address of the sender from the SMTP envelope
  "smtpLog": String, // URL to view the SMTP log
  "smtpTo": [
    String // email address of the recipient from the SMTP envelope
  ],
  "subject": String, // subject of the email
  "text": String, // plain text part of the email if available
  "to": [
    String // email address of the recipient 
  ]
}