Skip to content

Send an event

Record that an event happened to a contact. This is the signal an automation trigger listens for.

http
POST /v1/events/send

Works 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

ParameterTypeRequiredDescription
eventstringYesThe event name, e.g. user.created. Unknown names are auto-created with no schema — see below.
contact_idstringConditionalThe contact this happened to, by its contact public id. Supply exactly one of contact_id or email.
emailstringConditionalThe contact this happened to, by address. An unknown address is auto-created as a contact. Supply exactly one of contact_id or email.
payloadobjectNoThe 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 event name 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 email that doesn't match a contact creates the contact, with source of event. Pass contact_id instead 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.

Transactional email on your own domain.