StartImplementation Guides

Resending Messages

POST /v3/messages/{id}/resend triggers a new delivery attempt for an existing message. The original message is re-queued and all delivery policies run again: consent, balance, template approval, and quiet hours. The channel is preserved from the original send.

Common use cases:

  • A DELIVERED message that the recipient says they never received (carrier silent drop).
  • A FAILED message you want to retry after the underlying issue (carrier outage, provider timeout) is resolved.
  • A BLOCKED message that failed due to insufficient balance after you top up your account.
  • A READ message (WhatsApp and RCS) that the recipient explicitly requests be resent.

Making a Request

The request body is empty. The message to resend is identified by the id path parameter.

curl -X POST "https://api.sent.dm/v3/messages/8ba7b830-9dad-11d1-80b4-00c04fd430c8/resend" \
  -H "x-api-key: $SENT_API_KEY" \
  -H "Content-Type: application/json"
import SentDm from '@sentdm/sentdm';

const client = new SentDm();

const response = await client.messages.resend('8ba7b830-9dad-11d1-80b4-00c04fd430c8');

console.log(response.data.status); // "QUEUED"
import os
from sentdm import SentDm

client = SentDm(api_key=os.environ.get("SENT_API_KEY"))

response = client.messages.resend("8ba7b830-9dad-11d1-80b4-00c04fd430c8")

print(response.data.status)  # "QUEUED"
package main

import (
    "context"
    "fmt"
    "os"

    sentdm "github.com/sentdm/sentdm-go"
)

func main() {
    client := sentdm.NewClient(os.Getenv("SENT_API_KEY"))

    response, err := client.Messages.Resend(context.Background(), "8ba7b830-9dad-11d1-80b4-00c04fd430c8")
    if err != nil {
        panic(err)
    }

    fmt.Println(response.Data.Status) // "QUEUED"
}
import com.sentdm.SentDm;
import com.sentdm.models.MessageResponse;

public class ResendExample {
    public static void main(String[] args) {
        SentDm client = new SentDm(System.getenv("SENT_API_KEY"));

        MessageResponse response = client.messages().resend("8ba7b830-9dad-11d1-80b4-00c04fd430c8");

        System.out.println(response.getData().getStatus()); // "QUEUED"
    }
}
using SentDm;

var client = new SentDmClient(Environment.GetEnvironmentVariable("SENT_API_KEY"));

var response = await client.Messages.ResendAsync("8ba7b830-9dad-11d1-80b4-00c04fd430c8");

Console.WriteLine(response.Data.Status); // "QUEUED"
<?php

use SentDm\SentDmClient;

$client = new SentDmClient(getenv('SENT_API_KEY'));

$response = $client->messages->resend('8ba7b830-9dad-11d1-80b4-00c04fd430c8');

echo $response->data->status; // "QUEUED"
require 'sentdm'

client = SentDm::Client.new(api_key: ENV['SENT_API_KEY'])

response = client.messages.resend('8ba7b830-9dad-11d1-80b4-00c04fd430c8')

puts response.data.status # "QUEUED"

Response

A 202 Accepted response means the resend was accepted and the message is queued again. The response shape matches POST /v3/messages. The example below is for a template-based message; free-form messages omit template_id and template_name:

{
  "success": true,
  "data": {
    "status": "QUEUED",
    "template_id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
    "template_name": "order_confirmation",
    "recipients": [
      {
        "message_id": "8ba7b830-9dad-11d1-80b4-00c04fd430c8",
        "to": "+14155551234",
        "channel": "sms",
        "body": "Hi John, your order has been confirmed."
      }
    ]
  },
  "error": null,
  "meta": {
    "request_id": "req_9Y0aLq3kEx",
    "timestamp": "2026-09-24T14:00:00Z",
    "version": "v3"
  }
}

The message_id in the response is the same as the original message's ID. After the resend is accepted, the message follows the normal status lifecycle: QUEUEDROUTEDSENTDELIVERED. See Message status tracking for details on each status and the webhooks they emit.


What Can Be Resent

Not every message state is resendable. A resend is accepted only when the message has reached a terminal or near-terminal state that confirms the original attempt is no longer in progress.

Message statusResendable?Notes
DELIVEREDYes
READYesWhatsApp and RCS only
FAILEDYes
BLOCKEDYesFor example, after topping up a balance that caused the block
SENT (more than 15 min old)YesTreated as stalled in transit
SENT (less than 15 min old)NoThe provider may still deliver it
QUEUED, PROCESSED, ROUTEDNoStill in flight
SCHEDULEDNoHeld for a scheduled_at release time or quiet hours
FILTEREDNoPermanent: the recipient opted out or a DENY routing rule refused the message

FILTERED messages can never be resent. If a recipient has opted out, resending would violate their opt-out. If a routing DENY rule blocked the message, the rule will block the resend for the same reason.


Channel Behavior

The resend uses the same channel the original send requested. If the original send specified channel: ["sms"], the resend also routes on SMS. If the original used auto-detect (channel: ["sent"]), the resend re-runs auto-detect from the current routing rules.

There is no channel override parameter on this endpoint. To send a message on a different channel, create a new send with POST /v3/messages.


Policies That Run Again

Every delivery policy runs on the resend as if it were a fresh send:

  • Consent: if the recipient opted out since the original send, the resend lands FILTERED.
  • Balance: if your account balance is insufficient, the resend lands BLOCKED.
  • Template approval: if the template's approval status changed (for example, paused or rejected on a channel), routing drops that channel.
  • Quiet hours: if the resend time falls inside the recipient's quiet hours window, the message is held and transitions to SCHEDULED until the window lifts.

A successfully accepted resend (202) does not guarantee a DELIVERED outcome. Monitor webhooks or poll GET /v3/messages/{id} to confirm delivery.


Billing

Each accepted resend is a new delivery attempt and incurs a new charge at the standard rate for the channel and destination. A resend of a DELIVERED message results in two charges: one for the original send and one for the resend.


Rate Limiting

This endpoint is in the Sensitive tier: 10 requests per minute per account, on a fixed 60-second window, compared with 10,000 per minute for POST /v3/messages. It shares that tier with webhook secret rotation and webhook test requests. If you exceed the limit, the API returns 429 Too Many Requests. Back off and retry after the window passes. See Rate limits for the full tier list and Handling rate limits for retry strategies.


Idempotency

The Idempotency-Key header is honored. If you retry a resend request with the same key, the API returns the original 202 response without triggering a second delivery attempt. Use this when your network layer may retry on timeout, to avoid double-sending.


Sandbox

This endpoint does not support sandbox mode. Sandbox mode is read from a sandbox field in the request body, and a resend has no body. Every accepted resend is a real delivery attempt and is charged, so test your resend logic against messages you are prepared to pay to resend.


Error Reference

HTTP statusCause
400The message ID is not a valid UUID
401Missing or invalid API key
404Message not found, or it belongs to a different account
409Message is not resendable in its current state (see What can be resent), or a request with the same Idempotency-Key is still being processed (CONFLICT_001)
429Rate limit exceeded
500Unexpected error

The 409 response body includes a message that explains the reason:

  • For a FILTERED message: "Message was filtered because the recipient opted out or a routing rule refused it, and cannot be resent."
  • For any other non-resendable state: "Message is not resendable in its current state: it is still in flight, held for quiet hours, or was sent less than 15 minutes ago."
  • For a concurrent retry with the same Idempotency-Key (error code CONFLICT_001): "A request with this Idempotency-Key is currently being processed. Please retry shortly."

The first two are state conflicts: retrying the same message returns the same 409 until its status changes. CONFLICT_001 is transient: retry with the same key once the original request completes, and you receive its cached response. See Idempotency.

A message ID from another account returns 404, not 403, to avoid revealing whether the ID exists.

On this page