<Header icon={<img src="/docs/logos/sendgrid.svg" className="w-9 h-9 inline" />}>SendGrid</Header>

[SendGrid](https://sendgrid.com) (Twilio SendGrid) is an email delivery and marketing platform. This destination
syncs your Jitsu users to SendGrid as **marketing contacts** and manages their list membership, using the
[Marketing Contacts API](https://www.twilio.com/docs/sendgrid/api-reference/contacts/add-or-update-a-contact).

## How it works

SendGrid identifies contacts by **email address**. Jitsu maps events to SendGrid contacts as follows:

| Event      | What Jitsu does                                                                                   |
|------------|---------------------------------------------------------------------------------------------------|
| `identify` | Upserts the contact (create or update) and reconciles its list membership.                        |
| `track` / `page` / `screen` | Updates the existing contact's fields (never creates a contact).                 |
| `group`    | Updates the existing contact with the group id and group traits as `group_*` fields.              |

Contact upserts in SendGrid are **asynchronous** — the API accepts the request (HTTP 202) and processes it in
the background, so changes may take a moment to appear in the dashboard.

### identify

Each `identify()` upserts a contact via `PUT /v3/marketing/contacts`:

* **Email** is read from `traits.email` (falling back to `context.traits.email`). Events without an email are
  skipped.
* **First / last name** come from `traits.firstName` / `traits.lastName`, or are split from `traits.name`.
* The Jitsu `userId` and `anonymousId` are stored as the custom fields `jitsu_user_id` and
  `jitsu_anonymous_id`. (Email is kept as the contact's only SendGrid identifier — SendGrid rejects an
  upsert that omits any identifier a matched contact already has, so `userId`/`anonymousId` are not used
  as SendGrid identifiers.)
* **All other traits** are stored as SendGrid
  [custom fields](https://www.twilio.com/docs/sendgrid/api-reference/custom-fields). SendGrid requires custom
  fields to be defined before use and references them by an internal field ID, so Jitsu automatically creates a
  `Text` field definition for each new trait key it sees and maps values to the right IDs. Traits whose names
  match a writable SendGrid reserved field (`city`, `country`, `postal_code`, …) are written to that reserved
  field instead.
* The contact's [list](#lists) membership is reconciled to match the configured set.

A repeated `identify()` for the same email **updates** the existing contact rather than creating a duplicate.

### Other events

`track`, `page`, `screen` and `group` events **update** an existing contact but never create one. Because the
SendGrid upsert would otherwise create a contact, Jitsu first checks that the contact exists (via the
[search API](https://www.twilio.com/docs/sendgrid/api-reference/contacts/search-contacts-by-email)) and skips
the event if it doesn't. An update happens only when the event carries fields explicitly set on it (typically
added by a [transformation function](/docs/functions)) — ambient identify traits echoed on the event are
ignored so the contact isn't rewritten on every event.

## Lists

Set **Lists** in the destination config to a **comma-separated list of list names** (not IDs) — for example
`Customers, Beta`. Jitsu resolves each name to a SendGrid Marketing list, **creating any that don't exist yet**.
Leave it empty to add contacts without a list.

You can override the lists per event by setting a `sendgridLists` trait (also comma-separated names) from a
[function](/docs/functions):

```javascript
export default async function (event) {
  if (event.traits?.plan === "enterprise") {
    event.traits.sendgridLists = "Customers, Enterprise";
  }
  return event;
}
```

### Membership is reconciled

On each `identify()`, Jitsu makes the contact's membership match the desired set. Lists it **previously added**
that are no longer listed are **removed**. For example, if a function returns `A, B` on one event and `B, C` on
the next, the contact is removed from `A`, kept in `B`, and added to `C`.

To do this safely, the connector records which lists it added in a custom field named `jitsu_managed_lists` — so
it only ever removes lists **it** added, never ones you added manually or via another tool. There's no separate
state stored in Jitsu; the record lives on the contact.

Notes:

* Reconciliation runs only when a desired set is provided for the event — i.e. the `sendgridLists` trait is
  present, or the destination has configured Lists. An event with neither leaves membership untouched.
* Setting `sendgridLists` to an empty string explicitly clears the connector-managed lists.
* Only `identify()` manages membership; `track` / `page` / `group` events never change it.

## Matching events by userId

SendGrid matches contacts by email. Enable **Resolve email from userId** to have every `identify()` cache a
`userId → email` mapping, so later `track` / `page` / `group` events that carry only a `userId` can be matched to
the contact via that cache. The cache is only populated from `identify()` calls seen after the flag is enabled.
If neither an email nor a cached `userId` resolves, the event is skipped.

## Notes

* Unsubscribe / suppression state is managed separately in SendGrid (suppression groups) and is not set by this
  destination.
* Contact upserts are processed asynchronously by SendGrid and can take up to a minute or two to appear. The
  first event that introduces a brand-new custom field is retried once (a just-created field definition isn't
  usable immediately); it lands automatically on the retry.
* SendGrid's free tier is a time-limited trial; a paid Marketing Campaigns plan is required for ongoing use.

## Configuration

<DestinationConfiguration type="sendgrid" />