Get Message
Get a specific message by ID.
If this message is a reply to another message, the quoted_message field will contain a preview of the original message.
Required Scopes: messages:read
Request
- Base URL: https://api.sendseven.com
- URL: https://api.sendseven.com/api/v1/messages/{message_id}
- Auth: HTTP bearer
Path parameters
Response
Successful Response
Conversation the message belongs to. May be null for campaign and flow messages sent to a contact that has no conversation on that channel (campaigns never create conversations; flow messages are attached once the contact replies).
Message direction (lowercase to match MySQL ENUM values)
Message content type (lowercase to match MySQL ENUM values).
MUST stay in lockstep with app.models.message.MessageType AND the MySQL ENUM column messages.message_type. Validation here is what gates the API response — any value the DB column accepts MUST be a member of this enum, or MessageResponse.model_validate raises ValidationError and the whole list endpoint 500s. Bug found 2026-05-30: prod conversations carrying button rows from a template-quick-reply click crashed GET /api/v1/messages.
Interactive message types:
- quick_reply: Cross-platform quick reply buttons (Messenger, Instagram, Telegram)
- interactive: WhatsApp interactive messages (buttons/lists)
- carousel: Multi-card carousel messages
- button: WhatsApp legacy template-button reply (callback button payload)
Adapter-level types:
- file: Generic file attachment fallback (Messenger/Instagram unknown content_type)
- reaction: Emoji reaction (merged into target message metadata)
- referral: Messenger m.me deep link / ad referral event
Worker-emitted types:
- whatsapp_template: Campaign-dispatched WhatsApp template message
- kb_response: Team-chat AI knowledge-base response
- notification: System notification message
Provider-echoed sender profile captured from the inbound channel webhook at receipt time (inbound message.received only; not a DB lookup). Fields vary by channel: WhatsApp {name, phone}, Telegram {name, username, profile_picture}, Messenger {name, profile_picture}, Instagram {name?, username, profile_picture}. NOTE: phone (WhatsApp-only) is deprecated — use data.contact_method.value for the authoritative DB-sourced identifier of the method actually used.
Message status (lowercase to match MySQL ENUM values)
IDs of the additional messages created by the same send request, in delivery order. Non-empty only when a send carried several attachments on a channel that delivers one attachment per message — the response body itself is the FIRST message (it holds the caption and attachments[0]), and each id here is a further message carrying the next attachment. Each has its own status and its own delivery webhooks, so poll them individually via GET /api/v1/messages/{id}. Empty array for every other message.
How this message's body is formatted for the send pipeline: markdown (canonical markdown, rendered per-channel) or plain (human text, passed through untouched). Derived from meta.content_format; defaults to plain for every message that did not explicitly opt into markdown.
Resolved media URL for an interactive image/video header
Durable attachment id for an interactive image/video header
Platform message ID of quoted message
Machine-readable failure code for failed messages, e.g. 'contact_blocked' (the contact is blocked) or a provider error code.
FlowRun UUID — set when this message was sent by an automation flow. Frontends use this (together with flow_name) to render a 'Sent by flow' badge in the conversation timeline.
FlowRunStep UUID — identifies the specific Send-node step inside the flow that produced this message. Optional companion to flow_run_id.
Display name of the flow that sent this message. NOT stored on the message row — populated by the API layer via a single batch lookup against flow_runs JOIN flows and only set when flow_run_id is non-null.
When this message was last edited by the sender (real column on messages, stamped when an edit event is applied). Non-null is the ONLY signal that a message was edited — text is silently replaced in place, so without this a client cannot tell an edited message from an original one. Null for every message that was never edited (the overwhelming majority).
Body this message had immediately BEFORE its most recent edit. Not stored on the message row — resolved by the API layer from the newest APPLIED message_revisions row in one batched lookup per page. Null means 'edited, but the superseded body is unknown': most edits arrive before/without the message they target, so the original body was often never captured. Clients must therefore treat null as valid and fall back to a plain non-interactive 'edited' marker instead of an edit-history UI.
Number of edits that have actually been APPLIED to this message. Pending revisions (an edit whose target message we never resolved) are deliberately excluded — they describe no visible change to this row. 0 for un-edited messages; a value >1 tells the client the body was edited repeatedly, so previous_text is only the last step of a longer history.
Example response
{
"attachments": [],
"channel_id": "channel-123-456-789",
"contact_id": "contact-123-456-789",
"conversation_id": "conv-123-456-789",
"created_at": "2025-01-15T10:30:00Z",
"delivered_at": "2025-01-15T10:30:05Z",
"direction": "inbound",
"from": "+1234567890",
"id": "msg-123-456-789",
"message_type": "text",
"platform": "whatsapp",
"status": "delivered",
"tenant_id": "tenant-123-456-789",
"text": "Hello, I need help!"
}Changes
No recorded changes to this operation across all 3 revisions of this API.