Hoppa till innehållet

Webhook events

Sendora posts each event as JSON to the webhooks that take it. A delivery may arrive more than once, so key your handling on id, and verify Sendora-Signature before you trust the body. Webhooks shows how.

Headers

  • Sendora-Signatureheaderstring

    t= the Unix time of sending, then v1= an HMAC-SHA256 over <t>.<body> for each live secret of the webhook, the newest first. Verify one before you trust the body.

  • Sendora-Eventheaderstring

    The event, the same as event in the body.

  • Sendora-Delivery-Idheaderstring

    The delivery, the same as id in the body and on every retry of it.

  • User-Agentheaderstring

    Sendora-Webhooks/1.

Acknowledges the delivery. Any other answer, or none within 10 seconds, is retried.

Message events

Every event in this group carries these fields:

  • idstringrequired

    The delivery's id. A delivery may arrive more than once, so key your handling on it.

  • attemptintegerrequired

    The attempt number, from 1.

  • messageIdstringrequired

    The message the event is about.

  • recipientstringrequired

    The recipient the event is about.

  • occurredAtstring (date-time)required

    When it happened.

  • serverIdstringrequired

    The server the message was sent from.

  • streamIdstringrequired

    The stream the message went on.

  • broadcastIdstring or nullrequired

    The broadcast the message belongs to; null for a message sent on its own.

  • tagstring or nullrequired

    The tag you sent with the message.

  • metadatamap of string to stringrequired

    The metadata you sent with the message.

  • testbooleanrequired

    True for a test server's message: the event was simulated and nothing was sent.

delivered

The receiver accepted the message for the recipient.

  • event"delivered"required
  • detailsobjectrequired
    • codeinteger or nullrequired

      The SMTP status code the receiver answered; null when it gave none.

    • responsestring or nullrequired

      The receiver's response text.

Example (20 lines)
{
  "id": "9b2f4c1e-7a3d-4e5f-8c6b-1d2e3f4a5b6c",
  "attempt": 1,
  "messageId": "5f432ffd-c005-499b-aa3a-5b8a088ea20e",
  "recipient": "anna@example.com",
  "occurredAt": "2026-09-15T12:00:03.000Z",
  "serverId": "0f8a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
  "streamId": "3d1a2b4c-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "broadcastId": null,
  "tag": "invoice",
  "metadata": {
    "invoiceId": "2026-0912"
  },
  "test": false,
  "event": "delivered",
  "details": {
    "code": 250,
    "response": "2.0.0 OK"
  }
}

bounced

The receiver refused the message for good, or Sendora gave up; the recipient is suppressed when hard.

  • event"bounced"required
  • detailsobjectrequired
    • codeinteger or nullrequired

      The SMTP status code the receiver answered; null when it gave none.

    • responsestring or nullrequired

      The receiver's response text.

    • classificationstring or nullrequired

      How the bounce was classified, such as InvalidRecipient.

    • hardbooleanrequired

      True when the address is now on the suppression list.

    • expiredbooleanrequired

      True when the message expired after repeated deferrals rather than being refused.

Example (23 lines)
{
  "id": "9b2f4c1e-7a3d-4e5f-8c6b-1d2e3f4a5b6c",
  "attempt": 1,
  "messageId": "5f432ffd-c005-499b-aa3a-5b8a088ea20e",
  "recipient": "anna@example.com",
  "occurredAt": "2026-09-15T12:00:03.000Z",
  "serverId": "0f8a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
  "streamId": "3d1a2b4c-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "broadcastId": null,
  "tag": "invoice",
  "metadata": {
    "invoiceId": "2026-0912"
  },
  "test": false,
  "event": "bounced",
  "details": {
    "code": 550,
    "response": "5.1.1 The email account that you tried to reach does not exist.",
    "classification": "InvalidRecipient",
    "hard": true,
    "expired": false
  }
}

deferred

The receiver asked Sendora to try later; several may follow.

  • event"deferred"required
  • detailsobjectrequired
    • codeinteger or nullrequired

      The SMTP status code the receiver answered; null when it gave none.

    • responsestring or nullrequired

      The receiver's response text.

Example (20 lines)
{
  "id": "9b2f4c1e-7a3d-4e5f-8c6b-1d2e3f4a5b6c",
  "attempt": 1,
  "messageId": "5f432ffd-c005-499b-aa3a-5b8a088ea20e",
  "recipient": "anna@example.com",
  "occurredAt": "2026-09-15T12:00:03.000Z",
  "serverId": "0f8a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
  "streamId": "3d1a2b4c-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "broadcastId": null,
  "tag": "invoice",
  "metadata": {
    "invoiceId": "2026-0912"
  },
  "test": false,
  "event": "deferred",
  "details": {
    "code": 451,
    "response": "4.7.1 Greylisted, try again later"
  }
}

spam_complaint

The recipient reported the message as spam; only Sendora support lifts that suppression.

  • event"spam_complaint"required
  • detailsobjectrequired
    • feedbackTypestring or nullrequired

      The feedback type the receiver reported, such as abuse.

Example (19 lines)
{
  "id": "9b2f4c1e-7a3d-4e5f-8c6b-1d2e3f4a5b6c",
  "attempt": 1,
  "messageId": "5f432ffd-c005-499b-aa3a-5b8a088ea20e",
  "recipient": "anna@example.com",
  "occurredAt": "2026-09-15T12:00:03.000Z",
  "serverId": "0f8a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
  "streamId": "3d1a2b4c-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "broadcastId": null,
  "tag": "invoice",
  "metadata": {
    "invoiceId": "2026-0912"
  },
  "test": false,
  "event": "spam_complaint",
  "details": {
    "feedbackType": "abuse"
  }
}

unsubscribed

The recipient unsubscribed through the link or their mail client's one-click button; only Sendora support lifts that suppression.

  • event"unsubscribed"required
  • detailsobjectrequired
    • source"link" | "one_click"required

      link for the page's button, one_click for a mail client's request.

Example (17 lines)
{
  "id": "9b2f4c1e-7a3d-4e5f-8c6b-1d2e3f4a5b6c",
  "attempt": 1,
  "messageId": "5f432ffd-c005-499b-aa3a-5b8a088ea20e",
  "recipient": "anna@example.com",
  "occurredAt": "2026-09-15T12:00:03.000Z",
  "serverId": "0f8a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
  "streamId": "3d1a2b4c-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "broadcastId": "2c4e6a8b-0d1f-4a3b-8c5d-7e9f1a3b5c7d",
  "tag": "newsletter",
  "metadata": {},
  "test": false,
  "event": "unsubscribed",
  "details": {
    "source": "one_click"
  }
}

The monthly cap

Every event in this group carries these fields:

  • idstringrequired

    The delivery's id. A delivery may arrive more than once, so key your handling on it.

  • attemptintegerrequired

    The attempt number, from 1.

  • scope"account" | "server" | "test"required

    Whose cap it is: account for the account's, server for a server's own, test for the cap the account's test servers share.

  • serverIdstring or nullrequired

    The server whose cap it is; null for the account's or the test servers' cap.

  • capintegerrequired

    Emails the scope may send in the month.

  • usedintegerrequired

    Emails sent in the month so far.

  • periodStartstring (date-time)required

    The month the counts are for: its first moment, in UTC.

  • periodEndstring (date-time)required

    The first moment of the next month, in UTC.

  • occurredAtstring (date-time)required

    When it happened.

cap_warning

80 percent of the monthly cap is used.

  • event"cap_warning"required

Example

{
  "id": "4d6f8a0b-2c4e-4f6a-8b0c-2d4e6f8a0b1c",
  "attempt": 1,
  "scope": "account",
  "serverId": null,
  "cap": 50000,
  "periodStart": "2026-09-01T00:00:00.000Z",
  "periodEnd": "2026-10-01T00:00:00.000Z",
  "used": 40000,
  "occurredAt": "2026-09-24T09:30:00.000Z",
  "event": "cap_warning"
}

cap_reached

A send was refused because the monthly cap is used up.

  • event"cap_reached"required

Example

{
  "id": "4d6f8a0b-2c4e-4f6a-8b0c-2d4e6f8a0b1c",
  "attempt": 1,
  "scope": "account",
  "serverId": null,
  "cap": 50000,
  "periodStart": "2026-09-01T00:00:00.000Z",
  "periodEnd": "2026-10-01T00:00:00.000Z",
  "used": 50000,
  "occurredAt": "2026-09-29T16:05:00.000Z",
  "event": "cap_reached"
}

Received mail

inbound

A message was received on an inbound stream: the reference, or the reference and the message itself, as the webhook asks.

Both forms carry these fields:

  • idstringrequired

    The delivery's id. A delivery may arrive more than once, so key your handling on it.

  • attemptintegerrequired

    The attempt number, from 1.

  • event"inbound"required
  • inboundMessageIdstringrequired

    The received message; GET /v1/inbound/{id} reads it.

  • serverIdstringrequired

    The server whose stream received it.

  • streamIdstringrequired

    The inbound stream that received it.

  • receivedAtstring (date-time)required

    When the message was accepted from the sending server.

  • envelopeRecipientstringrequired

    The address of yours the message was sent to.

  • mailboxHashstring or nullrequired

    The text after + in the local part of that address, when the sender used one.

  • sizeBytesintegerrequired

    The size of the message as received, in bytes.

  • attachmentCountintegerrequired
  • hasTextbooleanrequired
  • hasHtmlbooleanrequired
  • authenticationobjectrequired

    What Sendora found when it checked the message's authentication; unchecked until the checks have run.

    • spf"pass" | "fail" | "softfail" | "neutral" | "none" | "temperror" | "permerror" | "unchecked"required

      SPF for the address in MAIL FROM.

    • spfHelo"pass" | "fail" | "softfail" | "neutral" | "none" | "temperror" | "permerror" | "unchecked"required

      SPF for the name the sending server gave in HELO.

    • dkim"pass" | "fail" | "none" | "temperror" | "permerror" | "unchecked"required
    • dmarc"pass" | "fail" | "none" | "temperror" | "permerror" | "unchecked"required
    • dmarcPolicy"none" | "quarantine" | "reject" or nullrequired

      What the sender's domain asks for; told only when DMARC failed.

    • arc"none" | "pass" | "fail" | "unchecked"required
    • checkedAtstring (date-time) or nullrequired

      When the checks ran; null until they have.

  • parseIssuestring or nullrequired

    What the parser met; null when the message parsed clean. A hard issue empties the parsed parts, and the raw message stays: no_headers, header_block_too_large, too_many_headers, too_many_parts, nesting_too_deep, missing_boundary, decoded_too_large, parse_timeout, parser_error. A soft issue keeps them and says what was changed or dropped: truncated_multipart, too_many_attachments, unknown_transfer_encoding, undecodable_container, filename_sanitised, control_chars_stripped, multiple_from, duplicate_header, malformed_header_dropped, address_list_truncated, header_value_truncated. A message with several names the hard one, or the first soft one in this order.

  1. The reference to the received message, for a webhook with inboundContent reference.

    Example (25 lines)
    {
      "id": "6e8a0c2e-4a6c-4e8a-8c0e-2a4c6e8a0c2e",
      "attempt": 1,
      "event": "inbound",
      "inboundMessageId": "1b3d5f7a-9c1e-4b3d-8f7a-9c1e3b5d7f9a",
      "serverId": "0f8a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
      "streamId": "8a0c2e4a-6c8e-4a0c-8e4a-6c8e0a2c4e6a",
      "receivedAt": "2026-09-15T12:00:00.000Z",
      "envelopeRecipient": "support@example.se",
      "mailboxHash": null,
      "sizeBytes": 18432,
      "attachmentCount": 1,
      "hasText": true,
      "hasHtml": true,
      "authentication": {
        "spf": "pass",
        "spfHelo": "pass",
        "dkim": "pass",
        "dmarc": "pass",
        "dmarcPolicy": null,
        "arc": "none",
        "checkedAt": "2026-09-15T12:00:00.000Z"
      },
      "parseIssue": null
    }
  2. The reference and the message itself, for a webhook with inboundContent full.

    • envelopeobject or nullrequired

      The envelope sender; null for a bounce.

      • senderstring or nullrequired
    • fromobject or nullrequired
      • addressstringrequired
      • namestring or nullrequired
    • replyToarray of objectrequired
      • addressstringrequired
      • namestring or nullrequired
    • toarray of objectrequired

      Up to 100 entries; toCount is the whole number.

      • addressstringrequired
      • namestring or nullrequired
    • toCountintegerrequired
    • ccarray of objectrequired

      Up to 100 entries; ccCount is the whole number.

      • addressstringrequired
      • namestring or nullrequired
    • ccCountintegerrequired
    • subjectstring or nullrequired
    • datestring (date-time) or nullrequired

      The sender's Date header, when it was a real moment.

    • messageIdHeaderstring or nullrequired
    • inReplyTostring or nullrequired
    • referencesarray of stringrequired
    • headersarray of objectrequired
      • namestringrequired
      • valuestringrequired
    • textstring or nullrequired

      The plain-text body; null when there is none, or over 1 MiB.

    • htmlstring or nullrequired

      The HTML body; null when there is none, or over 2 MiB.

    • attachmentsarray of objectrequired

      Each attachment, without its bytes.

      • positionintegerrequired

        Its place among the message's attachments, from 0.

      • namestringrequired
      • contentTypestringrequired
      • contentIdstring or nullrequired

        The Content-ID an HTML body refers to with cid:, when it has one.

      • sizeintegerrequired
      • inlinebooleanrequired

        True for a part shown in the body rather than offered as a file.

    • contentOmitted"too_large" | "expired" or nullrequired

      too_large when text or HTML was left out for size; expired when the content window has passed and the reference is all that remains.

    Example (74 lines)
    {
      "id": "6e8a0c2e-4a6c-4e8a-8c0e-2a4c6e8a0c2e",
      "attempt": 1,
      "event": "inbound",
      "inboundMessageId": "1b3d5f7a-9c1e-4b3d-8f7a-9c1e3b5d7f9a",
      "serverId": "0f8a1b2c-3d4e-4f5a-8b6c-7d8e9f0a1b2c",
      "streamId": "8a0c2e4a-6c8e-4a0c-8e4a-6c8e0a2c4e6a",
      "receivedAt": "2026-09-15T12:00:00.000Z",
      "envelopeRecipient": "support@example.se",
      "mailboxHash": null,
      "sizeBytes": 18432,
      "attachmentCount": 1,
      "hasText": true,
      "hasHtml": true,
      "authentication": {
        "spf": "pass",
        "spfHelo": "pass",
        "dkim": "pass",
        "dmarc": "pass",
        "dmarcPolicy": null,
        "arc": "none",
        "checkedAt": "2026-09-15T12:00:00.000Z"
      },
      "parseIssue": null,
      "envelope": {
        "sender": "anna@example.com"
      },
      "from": {
        "address": "anna@example.com",
        "name": "Anna Andersson"
      },
      "replyTo": [],
      "to": [
        {
          "address": "support@example.se",
          "name": null
        }
      ],
      "toCount": 1,
      "cc": [],
      "ccCount": 0,
      "subject": "A question about the invoice",
      "date": "2026-09-15T11:59:58.000Z",
      "messageIdHeader": "<20260915115958.a1b2@example.com>",
      "inReplyTo": null,
      "references": [],
      "headers": [
        {
          "name": "From",
          "value": "Anna Andersson <anna@example.com>"
        },
        {
          "name": "To",
          "value": "support@example.se"
        },
        {
          "name": "Subject",
          "value": "A question about the invoice"
        }
      ],
      "text": "Hi! I have a question about the invoice for September.",
      "html": "<p>Hi! I have a question about the invoice for September.</p>",
      "attachments": [
        {
          "position": 0,
          "name": "invoice.pdf",
          "contentType": "application/pdf",
          "contentId": null,
          "size": 15360,
          "inline": false
        }
      ],
      "contentOmitted": null
    }