Appearance
Send an event
Record that an event happened to a contact. This is the signal an automation trigger listens for.
http
POST /v1/events/sendWorks with a Sending access API key — this is the one event endpoint an app embeds and calls in the course of doing its job, so it doesn't need a full-access key the way defining an event does.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
event | string | Yes | The event name, e.g. user.created. Unknown names are auto-created with no schema — see below. |
contact_id | string | Conditional | The contact this happened to, by its contact public id. Supply exactly one of contact_id or email. |
email | string | Conditional | The contact this happened to, by address. An unknown address is auto-created as a contact. Supply exactly one of contact_id or email. |
payload | object | No | The event's data. Validated against the event's schema when one is defined. |
Send exactly one of contact_id or email — neither is a 422, both is a 422.
Example
bash
curl -X POST 'https://api.sendgrail.com/v1/events/send' \
-H "Authorization: Bearer $SENDGRAIL_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"event": "user.created",
"email": "ada@example.com",
"payload": {
"plan": "pro",
"seats": 12,
"trial": true,
"signed_up_at": "2026-07-17T09:30:00Z"
}
}'js
const res = await fetch('https://api.sendgrail.com/v1/events/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SENDGRAIL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
event: 'user.created',
email: 'ada@example.com',
payload: {
plan: 'pro',
seats: 12,
trial: true,
signed_up_at: '2026-07-17T09:30:00Z',
},
}),
});
const result = await res.json();Response
200, echoing the event that was recorded:
json
{
"object": "event",
"event": "user.created"
}The response confirms the name that was recorded, not the event's stored object — fetch that with Retrieve an event if you need it.
Unknown names and unknown emails are created for you
Sending is deliberately forgiving, because the app doing the sending shouldn't have to keep a registry of what it has told SendGrail about:
- An
eventname that has never been seen is created on the spot, with no schema. It then exists exactly as if you had created it — it shows up in List events and can be retrieved. - An
emailthat doesn't match a contact creates the contact, withsourceofevent. Passcontact_idinstead when you already hold the id and want the call to fail rather than create.
Payload is checked against the schema
If the named event has a schema, the payload is validated against it: a field typed number that arrives as a string, or a date that isn't ISO 8601, is a 422. Fields the schema doesn't mention are allowed through — the schema is a floor, not a whitelist. An event with no schema accepts any payload.