message_partially_sent
Some of the channel messages a send produced reached the guest and some did not. `parts` says which; resend only the failed ones.
When it fires
Airbnb carries one file per message and no text beside it, so a send with attachments goes out as several Airbnb messages: each file in order, then the text. If Airbnb refuses a later message after earlier ones were delivered, the send is neither a success nor a clean failure. The guest already has part of it.
parts lists every message the send attempted, in order. Each part says which files it carried (attachmentIndexes, indexes into your request's attachments), whether it carried the text (hasText), whether it arrived (sent) and, if not, why (error).
It comes from POST /v1/conversations/{id}/messages and from POST /v1/channels/airbnb/messaging/{threadId}/messages with mediaUrl, where the file can arrive and the text be refused. Booking.com sends everything as one message, so it never returns this code.
Response shape
Every Repull error follows the same envelope. The code is stable and safe to switch on.
{
"error": {
"code": "message_partially_sent",
"message": "Airbnb rejected this image — try a different file or re-save it as JPEG",
"fix": "Some parts reached the guest — see `parts` (`sent: true`). Do NOT resend the whole request; resend only the attachments whose part has `sent: false`, fixing what its `error` names.",
"docs_url": "https://repull.dev/docs/errors/message_partially_sent",
"request_id": "req_01J5X7Y8Z9ABCDEF12345678",
"channel": "airbnb",
"parts": [
{ "attachmentIndexes": [0], "hasText": false, "sent": true, "messageId": "1847910", "externalMessageId": "32837172380", "error": null },
{ "attachmentIndexes": [1], "hasText": false, "sent": false, "messageId": "1847912", "externalMessageId": null, "error": "Airbnb rejected this image — try a different file or re-save it as JPEG" }
],
"attachments": []
}
}How to fix
- Do not resend the whole request: the guest would receive the delivered parts twice.
- Find the part with `sent: false` and read its `error`. Fix what it names (for a refused image, re-save it as JPEG or pick another file).
- Work out what did not arrive: every file whose index is not in a `sent: true` part's `attachmentIndexes`, plus the text if no delivered part has `hasText: true`.
- Send a new request carrying only that. Use a new `Idempotency-Key`: it is a different request.
Common gotchas
- The delivered parts are recorded in the conversation, so
GET /v1/conversations/{id}/messagesshows exactly what the guest has. - Sending stops at the first refusal. Files after the refused one, and the text, are never attempted and do not appear in
partsat all, which is why you work out what to resend from what was delivered, not from the failed parts alone.
Examples
curl
# The first file arrived, the second did not: resend only the second, with the text
curl -X POST https://api.repull.dev/v1/conversations/164743/messages \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Idempotency-Key: msg-164743-resend-1" \
-H "Content-Type: application/json" \
-d '{"message": "And here is the gate.", "attachments": [{"url": "https://cdn.example.com/gate.jpg"}]}'TypeScript
const res = await fetch('https://api.repull.dev/v1/conversations/164743/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REPULL_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': key,
},
body: JSON.stringify({ message, attachments }),
})
if (res.status === 422) {
const { error } = await res.json()
if (error.code === 'message_partially_sent') {
const delivered = error.parts.filter((p: any) => p.sent)
const deliveredIdx = new Set(delivered.flatMap((p: any) => p.attachmentIndexes))
// Everything not delivered: the refused file, the files after it, and the text
const retryFiles = attachments.filter((_: unknown, i: number) => !deliveredIdx.has(i))
const retryText = delivered.some((p: any) => p.hasText) ? undefined : message
const reason = error.parts.find((p: any) => !p.sent)?.error
// Fix what `reason` names, then send only these, with a NEW Idempotency-Key
return { retryFiles, retryText, reason }
}
}If you're an AI agent
Part of the send reached the guest. Never resend the whole request. Fix what the `sent: false` part's `error` names, then resend only the files not carried by a `sent: true` part (plus the text if no delivered part has hasText) with a new Idempotency-Key.
Related
- Error reference — the full table of error codes
- Using Repull from AI agents — patterns for handling errors in agent loops
- Sending attachments
- message_not_sent
Hit an error that isn't covered? Email hello@repull.dev with the request id from the response headers.