HomeTech NewsSMS Delivery Receipts on AWS Lambda: The Essential Guide

SMS Delivery Receipts on AWS Lambda: The Essential Guide

  • SMS delivery receipts on AWS Lambda close the gap between a message being sent and actually reaching a recipient’s device.
  • Building SMS delivery receipts with Sinch’s Conversation API means your app can retry failed messages or fall back to email automatically.
  • A single sent SMS typically generates two to three webhook callbacks as it moves through queued, delivered, and failed states.
  • Signature validation using HMAC-SHA256 is non-negotiable — unauthenticated webhooks are an open door for spoofed delivery events.

Why SMS Delivery Receipts Actually Matter

SMS delivery receipts on AWS Lambda might sound like a developer convenience feature — but they’re closer to a business-critical safeguard. Think about what happens without them. A password reset link silently fails to arrive. An appointment reminder bounces because a number was disconnected months ago. A fraud alert never reaches the customer while a suspicious transaction clears. In each of those scenarios, a 200 OK response from your messaging API lied to you. It told you the message was accepted, not that it was delivered.

That distinction is everything. Sinch’s Conversation API makes it possible to track a message beyond the point of acceptance — through carrier handoff, device delivery, and in some cases even read receipts. The mechanism is a webhook callback system, and wiring it up inside a Lambda function is both cleaner and more scalable than running a persistent server just to receive status pings.

Cover image for SMS Delivery Receipts on AWS Lambda
via dev.to

Understanding the SMS Delivery Lifecycle

Before writing a single line of code, it’s worth understanding what you’re actually tracking with SMS delivery receipts. When you send a message through Sinch, it doesn’t just teleport to a handset. It moves through a series of states, each of which can trigger a callback to your registered webhook URL.

  • QUEUED_ON_CHANNEL — Sinch accepted the message and handed it off to the SMS carrier.
  • DELIVERED — The carrier confirmed the message reached the recipient’s device.
  • FAILED — Delivery didn’t happen, and Sinch includes a reason code explaining why.
  • READ — The recipient opened the message. This is rare for standard SMS but more common on WhatsApp and RCS channels.

A typical sent message generates two to three of these callbacks. That means your endpoint needs to handle bursts, process each event quickly, and return a 200 fast enough that Sinch doesn’t flag your webhook as unresponsive. Lambda is well-suited to exactly this kind of stateless, short-lived work.

SMS Delivery Receipts: Building the Lambda Endpoint

The webhook itself is a standard Lambda function exposed over HTTP — most likely via API Gateway or a Lambda function URL. What makes SMS delivery receipts useful rather than just informational is the logic you attach to each incoming status.

The first thing the function does is validate the incoming request’s signature. When you register a webhook with Sinch using a secret key, every callback it sends is signed. The signature is computed as an HMAC-SHA256 hash of the raw request body concatenated with a nonce and a timestamp, then base64-encoded. Those three values arrive in request headers: x-sinch-webhook-signature, x-sinch-webhook-signature-nonce, and x-sinch-webhook-signature-timestamp.

If any of those headers are missing, the function returns 401 immediately. If they’re present but the computed signature doesn’t match, same response. This isn’t paranoia — it’s the only thing preventing anyone who discovers your endpoint URL from spoofing delivery confirmations into your system. The implementation should use timingSafeEqual for the comparison rather than a standard string equality check, which avoids timing-based attacks that could theoretically leak information about your secret.

The webhook secret itself should live in AWS Systems Manager Parameter Store, encrypted at rest, and retrieved once per Lambda cold start then cached in memory. Fetching it from SSM on every invocation would add unnecessary latency and cost, and there’s no reason to do it — secrets don’t rotate mid-execution.

Parsing and Acting on Delivery Status

Once the signature checks out, the function parses the JSON body and looks for a message_delivery_report object. This contains the message ID, the delivery status, the channel and recipient identity, and — on failure — a structured reason object. SMS delivery receipts arrive in this exact format regardless of whether the outcome was a success or a failure.

A successfully delivered message payload looks something like this: the report carries a status of DELIVERED, a message_id that ties back to the original send request, and the recipient’s phone number under channel_identity. A failed delivery includes a reason block with a code field. The most common failure codes you’ll encounter are RECIPIENT_NOT_REACHABLE — typically a disconnected or invalid number — RECIPIENT_INVALID_CHANNEL_IDENTITY, OUTSIDE_ALLOWED_SENDING_WINDOW, and CHANNEL_FAILURE, which usually indicates a carrier-side problem.

That failure reason is where SMS delivery receipts stop being logging infrastructure and start being actionable intelligence. RECIPIENT_NOT_REACHABLE is a strong signal to flag the number as invalid in your database and fall back to email. OUTSIDE_ALLOWED_SENDING_WINDOW tells you to requeue and retry at an appropriate local time. CHANNEL_FAILURE might warrant an immediate retry through a different carrier route.

Tying Delivery Status Back to Your Application

The message_id in every delivery receipt matches the ID returned when you originally sent the message. If you included message_metadata in your outbound send request — an order ID, a user ID, a notification record key — that metadata comes back in the receipt too. This is how you avoid maintaining a separate lookup table just to map Sinch message IDs back to your own records.

In production, the handler function that receives the message ID, status, and reason becomes the integration point for your application logic. On a DELIVERED status, you might write a confirmation timestamp to your database, start a countdown for an expected user response, or log proof of delivery for regulatory compliance — something that matters a lot in financial services and healthcare. On FAILED, you trigger your fallback flow. On READ, if you’re on a channel that supports it, you might suppress a follow-up reminder that was queued to send in 24 hours.

What This Looks Like in Practice

The Sinch Conversation API sends SMS delivery receipts as HTTP POST requests to whatever URL you registered when setting up the webhook. The triggers array in your webhook configuration controls which event types get routed to which URL. Setting it to MESSAGE_DELIVERY scopes the endpoint to delivery receipts only, which keeps the function focused and makes its IAM permissions easier to reason about.

Lambda’s event-driven model maps cleanly onto this pattern. Each callback is an independent invocation. There’s no shared state to manage, no long-running process to keep alive. If your endpoint suddenly gets a spike in delivery callbacks — say, a bulk campaign finishes sending — Lambda scales horizontally without any configuration changes on your part.

The one operational concern worth flagging: Sinch will retry failed webhook deliveries if your endpoint returns a non-200 response or times out. That means your handler needs to be idempotent. Processing the same SMS delivery receipts twice shouldn’t create duplicate database records or fire duplicate alerts. A simple check against a processed message IDs table — or using a database upsert rather than an insert — handles this cleanly.

Beyond SMS: The Bigger Picture

The same webhook architecture applies across every channel Sinch supports. WhatsApp, RCS, and in-app messaging all flow through the same Conversation API callback structure. If you’re building a multi-channel notification system — which most serious platforms eventually become — the Lambda endpoint you build for SMS delivery receipts today can extend to handle read receipts on WhatsApp or delivery confirmations on RCS with minimal changes. The status codes and payload structure are consistent across channels by design.

This matters because the industry is clearly moving toward richer messaging channels. SMS remains the most universally reachable, but its delivery receipt granularity is limited compared to what WhatsApp or RCS can report. Building a solid, validated, idempotent webhook handler now means you’re ready to consume that richer telemetry when your channel mix evolves. The infrastructure doesn’t need to change — just the business logic that acts on each status.

Source: https://dev.to/gunnargrosch/sms-delivery-receipts-on-aws-lambda-2e8m

Muhammad Zayn Emad
Muhammad Zayn Emad
Hi! I am Zayn 21-year-old boy immersed in the world of blogging, I blend creativity with digital savvy. Hailing from a diverse background, I bring fresh perspectives to every post. Whether crafting compelling narratives or diving deep into niche topics, I strive to engage and inspire readers, making every word count.
RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular