Webhook event: message.sent
The payload Cooledge sends when someone on the team sends a text or email from Messages, including a draft.
message.sent
Sent when someone on the team sends a text or email from Messages, on the portal or in the app. That covers a reply from the composer, a Send later message when it goes, a message held by undo send once it really goes and a draft a person sends. Delivered to your webhook subscriptions. Deliveries are at-least-once, so dedupe on X-Cooledge-Event-Id.
Quotes and invoices sent from their own screens have their own events (quote.sent and invoice.sent) and do not fire it.
It is best effort. Cooledge records the event as the message goes. It never holds up the send to do it. In rare cases, such as a slow moment in Cooledge's database, the event is not recorded and is lost, with no retry. Once it is recorded, delivery to your endpoint is retried like any other event. If you need a complete record of what was sent, read the conversation with GET /v1/conversations/{id}/messages.
Subscribing to this event through the API needs the messages:read scope. See the events index.
Envelope
Every event shares the flat envelope: event_type, version ("v1"), event_id, business_id, occurred_at, plus the fields below. See the events overview for the envelope detail and webhooks for signature verification and delivery semantics.
Fields
| Field | Type | Notes |
|---|---|---|
message | object | The message. |
message.id | string | UUID of the message. The same id the messages endpoint returns. |
message.direction | string | Always "out" for this event. |
message.channel | string | "sms" or "email". |
message.subject | string or null | The email subject. Null for a text. |
message.text | string or null | The plain text body. |
message.html | string or null | The email's HTML. Null for a text. |
message.from | string or null | The business number or address it was sent from. |
message.to | string or null | The phone number or email address it was sent to. |
message.cc | array of strings | Email CC addresses. Empty for a text. |
message.at | string | ISO 8601 timestamp for when the message was sent. |
message.attachments | array | The files on the message, each { filename, content_type, size } with content_type and size (in bytes) possibly null. Inline email images are left out. Empty when there are none. |
conversation | object or null | The conversation the message is in: { id, state, is_unmatched }. state is one of open, snoozed, archived, trashed or spam. is_unmatched is true when the recipient is not matched to a customer. |
customer | object or null | The matched customer. Null when the recipient is an unknown sender. |
customer.id | string | UUID of the customer. |
customer.name | string or null | The customer's display name. |
customer.email | string or null | The customer's saved email, which may differ from the address the message went to. |
customer.phone | string or null | The customer's saved phone, which may differ from the number the message went to. |
contact | object or null | The customer's contact the message went to, when it went to one. Null otherwise. |
contact.id | string | UUID of the contact. |
contact.name | string or null | The contact's first and last name. |
sender_address | string or null | An unknown sender's phone number or email address. Null when the conversation is matched to a customer. |
sent_by | object | The team member who sent it. For a Send later message, the person who scheduled it. |
sent_by.id | string | UUID of the team member. |
sent_by.name | string or null | The team member's name. |
draft | object or null | Set when the message was a draft. Null for any other message. |
draft.id | string | The id the draft had when it was sent, as GET /v1/drafts/{id} reads it. |
draft.drafted_by.kind | string | user, api_key or oauth. |
draft.drafted_by.name | string or null | The person, API key or connected app that wrote it, as named when the draft was saved. |
A draft a person sent and then undid comes back with a new id, so if that new draft is sent later, draft.id is the new id, not the one your agent created. See Following a draft.
Example
{
"version": "v1",
"event_type": "message.sent",
"event_id": "f2c9a6d1-8e43-5b7a-9d10-3e6b8c4a2f57",
"business_id": "a7f3c9d1-2e84-4b6f-8c01-5d9e2a1f7b30",
"occurred_at": "2026-10-11T03:31:08.442Z",
"message": {
"id": "7d1e5b93-2c48-4a6f-b0e7-9f3a2d8c6b14",
"direction": "out",
"channel": "sms",
"subject": null,
"text": "Hi Priya, Thursday works. We can be there between 8 and 10 am. Does that suit?",
"html": null,
"from": "+61480012345",
"to": "+61498765432",
"cc": [],
"at": "2026-10-11T03:31:07.915Z",
"attachments": []
},
"conversation": {
"id": "8f3a1c52-6d47-4b9e-a210-5c7e9d2b4f18",
"state": "open",
"is_unmatched": false
},
"customer": {
"id": "c4a9e1b2-7d3f-4a8c-9e2b-1f6a0d5c3b7e",
"name": "Priya Nair",
"email": "priya.nair@example.com",
"phone": "+61498765432"
},
"contact": null,
"sender_address": null,
"sent_by": {
"id": "5b2e7d90-1c34-4f8a-9e61-0d3c7a2b5e49",
"name": "Jess Taylor"
},
"draft": {
"id": "e71c4b28-3f95-4d0a-8c6e-2a9b5d7f1e30",
"drafted_by": { "kind": "api_key", "name": "Office assistant" }
}
}
See webhooks for signature verification and delivery semantics. See all events for the full list.
Need a hand with an integration? Contact support