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: QUEUED → ROUTED → SENT → DELIVERED. 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 status | Resendable? | Notes |
|---|---|---|
DELIVERED | Yes | |
READ | Yes | WhatsApp and RCS only |
FAILED | Yes | |
BLOCKED | Yes | For example, after topping up a balance that caused the block |
SENT (more than 15 min old) | Yes | Treated as stalled in transit |
SENT (less than 15 min old) | No | The provider may still deliver it |
QUEUED, PROCESSED, ROUTED | No | Still in flight |
SCHEDULED | No | Held for a scheduled_at release time or quiet hours |
FILTERED | No | Permanent: 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
SCHEDULEDuntil 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 status | Cause |
|---|---|
400 | The message ID is not a valid UUID |
401 | Missing or invalid API key |
404 | Message not found, or it belongs to a different account |
409 | Message 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) |
429 | Rate limit exceeded |
500 | Unexpected error |
The 409 response body includes a message that explains the reason:
- For a
FILTEREDmessage:"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 codeCONFLICT_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.