Delivery receipts

Find out whether each message reached the handset.

Set registeredDelivery to true when you send. When the network reports back, we send you a receipt by webhook, or hold it for you to collect. Receipts refer to the id returned when you sent, plus your own reference.

Most receipts arrive within seconds. Some networks take longer, and a small number of messages are delivered but never receipted by the network.

Statuses

statusMeaning
DELIVEREDDelivered to the handset.
EXPIREDNot delivered before the expiry time, for example because the phone was off.
UNDELIVEREDThe network could not deliver it.
REJECTEDRefused by the network, for example an unknown or barred number.
FAILEDNot delivered: failed, cancelled or deleted before sending.
UNKNOWNThe network gave a status we could not map.

Webhook

We POST receipts to the replyUrl you gave when sending, or to your account’s default receipt URL. Ask us to set a default.

POSThttps://your-server.example.com/…
{
  "list": [
    {
      "type": "DELIVERY_RECEIPT",
      "messageId": 48211,
      "reference": "booking-1042",
      "recipient": "+447700900123",
      "status": "DELIVERED",
      "dateTime": "2026-10-11T09:30:07.112Z"
    }
  ]
}
FieldTypeDescription
listarrayOne or more receipts.
typestringDELIVERY_RECEIPT.
messageIdintegerThe id returned by /v1/submit.
referencestringYour reference from the request. May be null.
recipientstringThe mobile number.
statusstringSee statuses.
dateTimestringWhen the network reported the outcome, ISO 8601 in UTC.

Reply with any 2xx status. Retries and security work the same way as for inbound messages.

Collect by API

GEThttps://api.everymessage.com/v1/delivery_receipts

Returns receipts you have not collected yet, and marks them collected. Each receipt is returned once. When there is nothing new, responseStatusReason is NO_CURRENT_ENTRIES.

Collect at least once a day. Receipts are kept for 3 days. One not collected in that time is removed.

ParameterTypeDescription
limitintegerMaximum receipts per call. Default 500, maximum 1000.
{
  "apiName": "everymessage Web API",
  "apiVersion": "1.0.2",
  "apiTime": "11 October 2026 10:35:00",
  "apiState": "RUNNING",
  "responseStatusCode": 200,
  "processingTime": 9,
  "body": [
    {
      "type": "DELIVERY_RECEIPT",
      "messageId": 48211,
      "reference": "booking-1042",
      "recipient": "447700900123",
      "status": "DELIVERED",
      "datetime": "2026-10-11 10:30:07"
    }
  ]
}

The fields match the webhook, except that the time field is named datetime (lower case) and is UK local time in the form yyyy-MM-dd HH:mm:ss, and recipient has no +. To check one message at any time, use GET /v1/status_message.

Code examples

Webhook receiver

The webhook receiver on the Receive SMS page handles receipts too, at /everymessage/receipts/{token}. Pass that URL as replyUrl when you send.

Collect by API

Polls every five minutes and prints each new receipt.

C#

// Collect delivery receipts with the everymessage v1 API (.NET 8 or later)
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;

var apiKey = Environment.GetEnvironmentVariable("EM_API_KEY")!;
var secretKey = Environment.GetEnvironmentVariable("EM_SECRET_KEY")!;

using var http = new HttpClient { BaseAddress = new Uri("https://api.everymessage.com/") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{apiKey}:{secretKey}")));

// Uncollected receipts are kept for 3 days: poll at least daily (every few minutes is typical).
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
do
{
    var result = await http.GetFromJsonAsync<ApiEnvelope<List<DeliveryReceipt>>>(
        "v1/delivery_receipts?limit=1000")
        ?? throw new InvalidOperationException("Empty response from everymessage");

    switch (result.ResponseStatusReason)
    {
        case null:
            foreach (var r in result.Body ?? [])
                Console.WriteLine($"{r.Datetime} message {r.MessageId} ({r.Reference}) to {r.Recipient}: {r.Status}");
            break;
        case "NO_CURRENT_ENTRIES":
            break;
        default:
            throw new InvalidOperationException($"Collect failed: {result.ResponseStatusReason}");
    }
}
while (await timer.WaitForNextTickAsync());

record ApiEnvelope<T>(int ResponseStatusCode, string? ResponseStatusReason, int ProcessingTime, T? Body);
record DeliveryReceipt(string Type, long MessageId, string? Reference, string Recipient,
    string Status, string Datetime);

Python

"""Collect delivery receipts with the everymessage v1 API (Python 3.10+, pip install httpx)."""
import os
import time

import httpx

with httpx.Client(
    base_url="https://api.everymessage.com",
    auth=(os.environ["EM_API_KEY"], os.environ["EM_SECRET_KEY"]),
    timeout=30,
) as client:
    while True:
        # Uncollected receipts are kept for 3 days: poll at least daily.
        response = client.get("/v1/delivery_receipts", params={"limit": 1000})
        response.raise_for_status()
        result = response.json()

        reason = result.get("responseStatusReason")
        if reason is None:
            for r in result["body"]:
                print(f"{r['datetime']} message {r['messageId']} ({r.get('reference')}) "
                      f"to {r['recipient']}: {r['status']}")
        elif reason != "NO_CURRENT_ENTRIES":
            raise RuntimeError(f"Collect failed: {reason}")

        time.sleep(300)