> ## Documentation Index
> Fetch the complete documentation index at: https://www.courier.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Install the Courier skill before writing code: `npx skills add trycourier/courier-skills`. It carries the verified SDK shapes and the rules you cannot get wrong.
> Authenticate every request with `Authorization: Bearer <API_KEY>`. A workspace has several environments and each has its own keys, which are plain `pk_` strings with no environment prefix. Start with Test.
> Send with `client.send.message`, the default import of the v7 Node SDK. Reference a template by its `nt_` id or its alias.
> A send accepts a bare Elemental element list, but storing content on a template requires the top-level elements wrapped in a channel element.
> Templates and journeys can be built in the Courier app or created through the API. Either way they live in the workspace and are referenced by ID when you send.
> The hosted MCP server is https://mcp.courier.com. For a briefing on what Courier is and when to use it, read https://www.courier.com/llms.txt.
> Prefer the Guides tab for how-do-I questions and the Docs tab for how-does-it-behave questions. The API reference lives under /api-reference.

# Send email with SendGrid through Courier

> Connect SendGrid with a Mail Send API key, track delivery with the event webhook, override fields.

export const AppLink = ({href, children, name, bare}) => {
  const label = children || name || "Open in Courier";
  if (bare) {
    return <a href={href} target="_blank" rel="noreferrer">{label}</a>;
  }
  return <a className="cx-endpoint" data-kind="app" href={href} target="_blank" rel="noreferrer">
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method" aria-hidden="true">↗</span>
    </a>;
};

export const Doc = ({href, children, name, bare}) => {
  const label = children || name || href;
  if (bare) {
    return <a href={href}>{label}</a>;
  }
  return <a className="cx-endpoint" data-kind="doc" href={href}>
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method">DOC</span>
    </a>;
};

## Prerequisites

* <AppLink href="https://app.courier.com/signup">A Courier account</AppLink>
* [A SendGrid account](https://signup.sendgrid.com/)

## Setup

<Steps>
  <Step title="Create an API key in SendGrid">
    In SendGrid, open [API Keys](https://app.sendgrid.com/settings/api_keys) and create a key with **Mail Send** permission. Copy it.

    <Note>
      Mail Send is all Courier needs to send email, and all the [event webhook](#delivery-tracking) needs to report `delivered`. Two optional features want more: template import needs **Template Engine**, and Email Activity polling needs read access to Email Activity.
    </Note>
  </Step>

  <Step title="Add it to Courier">
    Open the <AppLink href="https://app.courier.com/integrations">**Integrations**</AppLink> page, select the SendGrid integration, and paste the key into **API Key**.
  </Step>

  <Step title="Set a From Address and save">
    Add the address SendGrid sends from, such as `noreply@acme-corp.com`. Select **Add Integration**, then **Save**.
  </Step>
</Steps>

Addressing a recipient and sending are the same on every email provider, so they are documented once: <Doc href="/docs/integrations/email/overview#profile-requirements">profile requirements</Doc> and <Doc href="/docs/integrations/email/overview#send-to-a-recipient">send to a recipient</Doc>.

## Send an email

Address the recipient by `email` and Courier routes it to SendGrid.

<CodeGroup>
  ```javascript Node.js highlight={4} theme={null}
  const { requestId } = await courier.send.message({
    message: {
      to: {
        email: "sarah@acme-corp.com",
      },
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      data: {
        name: "Sarah Bennett",
      },
    },
  });
  ```

  ```python Python highlight={4} theme={null}
  response = client.send.message(
      message={
          "to": {
              "email": "sarah@acme-corp.com",
          },
          "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
          "data": {
              "name": "Sarah Bennett",
          },
      },
  )
  ```

  ```bash cURL highlight={7} wrap theme={null}
  curl -X POST https://api.courier.com/send \
    -H "Authorization: Bearer $COURIER_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "message": {
        "to": {
          "email": "sarah@acme-corp.com"
        },
        "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
        "data": { "name": "Sarah Bennett" }
      }
    }'
  ```

  ```ruby Ruby highlight={4} theme={null}
  response = courier.send_.message(
    message: {
      to: {
        email: "sarah@acme-corp.com"
      },
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      data: { name: "Sarah Bennett" }
    }
  )
  ```

  ```go Go highlight={5} theme={null}
  response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
  	Message: courier.SendMessageParamsMessage{
  		To: courier.SendMessageParamsMessageToUnion{
  			OfUserRecipient: &shared.UserRecipientParam{
  				Email: courier.String("sarah@acme-corp.com"),
  			},
  		},
  		Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
  		Data: map[string]any{
  			"name": "Sarah Bennett",
  		},
  	},
  })
  ```

  ```java Java highlight={3} theme={null}
  SendMessageParams params = SendMessageParams.builder()
      .message(SendMessageParams.Message.builder()
          .to(UserRecipient.builder().email("sarah@acme-corp.com").build())
          .template("nt_01kx4h2jdafq8bk9aftxak4b40")
          .data(JsonValue.from(java.util.Map.of("name", "Sarah Bennett")))
          .build())
      .build();
  SendMessageResponse response = client.send().message(params);
  ```

  ```php PHP highlight={4} theme={null}
  $response = $client->send->message(
    message: [
      'to' => [
        'email' => 'sarah@acme-corp.com',
      ],
      'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
      'data' => ['name' => 'Sarah Bennett'],
    ],
  );
  ```

  ```csharp C# highlight={5} theme={null}
  SendMessageParams parameters = new()
  {
      Message = new()
      {
          To = new UserRecipient { Email = "sarah@acme-corp.com" },
          Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
          Data = new Dictionary<string, JsonElement>()
          {
              { "name", JsonSerializer.SerializeToElement("Sarah Bennett") },
          },
      },
  };

  var response = await client.Send.Message(parameters);
  ```

  ```bash CLI highlight={3} wrap theme={null}
  courier send message \
    --api-key "$COURIER_API_KEY" \
    --message.to '{"email": "sarah@acme-corp.com"}' \
    --message.template nt_01kx4h2jdafq8bk9aftxak4b40 \
    --message.data '{"name": "Sarah Bennett"}'
  ```

  ```text MCP theme={null}
  With Courier MCP, send my nt_01kx4h2jdafq8bk9aftxak4b40 template to sarah@acme-corp.com by email.
  ```
</CodeGroup>

## Overrides

<Doc href="/docs/send/overrides#how-overrides-work">How overrides work</Doc> covers the two levels and which one wins. <Doc href="/docs/integrations/email/overview#channel-overrides">Email channel overrides</Doc> lists the fields every email provider takes.

An override changes the request Courier sends to SendGrid. Use one to set a field Courier does not support yet, or to replace a value Courier generates.

| Key                      | What it replaces                                                                                                                                             |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `override.body`          | Merged into SendGrid's [`/mail/send` request body](https://docs.sendgrid.com/api-reference/mail-send/mail-send). Any field that endpoint accepts works here. |
| `override.config.apiKey` | The API key for this one send, instead of the key saved on the integration.                                                                                  |

Nest either under `providers.sendgrid` on the message:

<CodeGroup>
  ```javascript Node.js highlight={8-21} theme={null}
  const { requestId } = await courier.send.message({
    message: {
      to: {
        email: "sarah@acme-corp.com",
      },
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      providers: {
        sendgrid: {
          override: {
            body: {
              subject: "Your appointment reminder",
              attachments: [
                {
                  content: "eyJmb28iOiJiYXIifQ==",
                  type: "application/json",
                  filename: "appointment-details.json",
                },
              ],
            },
          },
        },
      },
    },
  });
  ```

  ```python Python highlight={8-21} theme={null}
  response = client.send.message(
      message={
          "to": {
            "email": "sarah@acme-corp.com",
          },
          "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
          "providers": {
              "sendgrid": {
                  "override": {
                    "body": {
                      "subject": "Your appointment reminder",
                      "attachments": [
                        {
                          "content": "eyJmb28iOiJiYXIifQ==",
                          "type": "application/json",
                          "filename": "appointment-details.json",
                        },
                      ],
                    },
                  },
              },
          },
      },
  )
  ```

  ```bash cURL highlight={11-24} wrap theme={null}
  curl -X POST https://api.courier.com/send \
    -H "Authorization: Bearer $COURIER_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
        "message": {
          "to": {
            "email": "sarah@acme-corp.com"
          },
          "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
          "providers": {
            "sendgrid": {
              "override": {
                "body": {
                  "subject": "Your appointment reminder",
                  "attachments": [
                    {
                      "content": "eyJmb28iOiJiYXIifQ==",
                      "type": "application/json",
                      "filename": "appointment-details.json"
                    }
                  ]
                }
              }
            }
          }
        }
      }'
  ```

  ```ruby Ruby highlight={8-21} theme={null}
  response = courier.send_.message(
    message: {
      to: {
        email: "sarah@acme-corp.com"
      },
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      providers: {
        "sendgrid" => {
          override: {
            body: {
              subject: "Your appointment reminder",
              attachments: [
                {
                  content: "eyJmb28iOiJiYXIifQ==",
                  type: "application/json",
                  filename: "appointment-details.json"
                }
              ]
            }
          }
        }
      }
    }
  )
  ```

  ```go Go highlight={10-23} theme={null}
  response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
  	Message: courier.SendMessageParamsMessage{
  		To: courier.SendMessageParamsMessageToUnion{
  			OfUserRecipient: &shared.UserRecipientParam{
  				Email: courier.String("sarah@acme-corp.com"),
  			},
  		},
  		Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
  		Providers: shared.MessageProvidersParam{
  			"sendgrid": shared.MessageProvidersTypeParam{
  				Override: map[string]any{
  					"body": map[string]any{
  						"subject": "Your appointment reminder",
  						"attachments": []any{
  							map[string]any{
  								"content": "eyJmb28iOiJiYXIifQ==",
  								"type": "application/json",
  								"filename": "appointment-details.json",
  							},
  						},
  					},
  				},
  			},
  		},
  	},
  })
  ```

  ```java Java highlight={6-7} theme={null}
  SendMessageParams params = SendMessageParams.builder()
      .message(SendMessageParams.Message.builder()
          .to(UserRecipient.builder().email("sarah@acme-corp.com").build())
          .template("nt_01kx4h2jdafq8bk9aftxak4b40")
          .providers(MessageProviders.builder()
              .putAdditionalProperty("sendgrid", JsonValue.from(java.util.Map.of(
                  "override", java.util.Map.of("body", java.util.Map.of("subject", "Your appointment reminder", "attachments", java.util.List.of(java.util.Map.of("content", "eyJmb28iOiJiYXIifQ==", "type", "application/json", "filename", "appointment-details.json")))))))
              .build())
          .build())
      .build();
  SendMessageResponse response = client.send().message(params);
  ```

  ```php PHP highlight={8-21} theme={null}
  $response = $client->send->message(
    message: [
      'to' => [
        'email' => 'sarah@acme-corp.com',
      ],
      'template' => "nt_01kx4h2jdafq8bk9aftxak4b40",
      'providers' => [
        'sendgrid' => [
          'override' => [
            'body' => [
              'subject' => 'Your appointment reminder',
              'attachments' => [
                [
                  'content' => 'eyJmb28iOiJiYXIifQ==',
                  'type' => 'application/json',
                  'filename' => 'appointment-details.json',
                ],
              ],
            ],
          ],
        ],
      ],
    ],
  );
  ```

  ```csharp C# highlight={9-18} theme={null}
  SendMessageParams parameters = new()
  {
      Message = new()
      {
          To = new UserRecipient { Email = "sarah@acme-corp.com" },
          Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
          Providers = new Dictionary<string, MessageProvidersType>()
          {
              {
                  "sendgrid",
                  new()
                  {
                      Override = new Dictionary<string, JsonElement>()
                      {
                          { "body", JsonSerializer.SerializeToElement(new { subject = "Your appointment reminder", attachments = new[] { new { content = "eyJmb28iOiJiYXIifQ==", type = "application/json", filename = "appointment-details.json" } } }) },
                      },
                  }
              },
          },
      },
  };

  var response = await client.Send.Message(parameters);
  ```

  ```bash CLI highlight={5} wrap theme={null}
  courier send message \
    --api-key "$COURIER_API_KEY" \
    --message.to '{"email": "sarah@acme-corp.com"}' \
    --message.template nt_01kx4h2jdafq8bk9aftxak4b40 \
    --message.providers '{"sendgrid": {"override": {"body": {"subject": "Your appointment reminder", "attachments": [{"content": "eyJmb28iOiJiYXIifQ==", "type": "application/json", "filename": "appointment-details.json"}]}}}}'
  ```

  ```text MCP theme={null}
  With Courier MCP, send my nt_01kx4h2jdafq8bk9aftxak4b40 template to sarah@acme-corp.com and override the email subject.
  ```
</CodeGroup>

## Template import

Import your SendGrid templates from the <AppLink href="https://app.courier.com/integrations/catalog/sendgrid">Courier SendGrid configuration page</AppLink>.

<Note>
  Importing SendGrid Dynamic Templates needs three things:

  * Templates saved as [SendGrid Dynamic Templates](https://docs.sendgrid.com/ui/sending-email/how-to-send-an-email-with-dynamic-templates).
  * A SendGrid API key with [full access permissions](https://docs.sendgrid.com/ui/account-and-settings/api-keys) for `Template Engine`.
  * Your SendGrid credentials on the configuration page, so Courier can retrieve the templates.
</Note>

### Import process

With those permissions, the import tool lists your templates as checkboxes. Select the ones to import.

<Frame caption="Template Import Page">
  <img src="https://mintcdn.com/courier-4f1f25dc/rYENcCCTyPPrDtw0/assets/template-import.webp?fit=max&auto=format&n=rYENcCCTyPPrDtw0&q=85&s=1e8c8f189a398d3ed6b692cf1c180e20" width="1010" height="700" data-path="assets/template-import.webp" />
</Frame>

## Delivery tracking

Delivery tracking is **off until you set it up**. Until then a working SendGrid integration leaves every message at `SENT`, because Courier has sent the email but has heard nothing back. See <Doc href="/docs/send/statuses#delivery-tracking-is-per-provider">delivery statuses</Doc>.

SendGrid can report two ways, and the webhook below is the better one:

|                          | Event webhook  | Email Activity polling                    |
| ------------------------ | -------------- | ----------------------------------------- |
| Speed                    | Real time      | On a schedule                             |
| API key permission       | Mail Send only | Read access to Email Activity             |
| SendGrid add-on          | None           | The email-history add-on (paid)           |
| Available on EU SendGrid | Yes            | No, EU SendGrid has no Email Activity API |

Use the webhook. Reach for polling only if you cannot add one, and not at all on EU SendGrid.

<Steps>
  <Step title="Get Webhook URL">
    In Courier, open the <AppLink href="https://app.courier.com/integrations/catalog/sendgrid">Courier SendGrid provider configuration screen</AppLink> from the Channels menu. It shows a Webhook URL.

    <Frame>
      <img src="https://mintcdn.com/courier-4f1f25dc/fN1MbwzHtG3Vhos5/assets/courier-ui.webp?fit=max&auto=format&n=fN1MbwzHtG3Vhos5&q=85&s=17a4e6c0a797076c7f5371e7c9310abf" width="659" height="926" data-path="assets/courier-ui.webp" />
    </Frame>
  </Step>

  <Step title="Configure SendGrid Webhook">
    Copy the webhook URL, then log in to SendGrid. Choose Settings, then Mail Settings, then Event Webhooks.

    Click Create new webhook and fill out the form:

    * **Friendly Name** - Whatever you like
    * **Post URL** - Paste the URL you copied from Courier.
    * **Deliverability Data** - Check all 5 boxes.
    * **Security features** - Leave these disabled. The HTTPS URL you pasted carries a cryptographic token Courier uses to verify that incoming events are yours.

    <Frame>
      <img src="https://mintcdn.com/courier-4f1f25dc/_N31KC_ZrU1lo5hN/assets/webhook-form.webp?fit=max&auto=format&n=_N31KC_ZrU1lo5hN&q=85&s=070dc4daae5275441e084d68aad7288c" width="2562" height="1610" data-path="assets/webhook-form.webp" />
    </Frame>

    Press Save.
  </Step>

  <Step title="Turn polling off">
    With the webhook live you no longer need Courier to poll. Wait about an hour first, so in-flight messages do not lose their status updates.

    Then return to the <AppLink href="https://app.courier.com/integrations/catalog/sendgrid">Courier SendGrid configuration screen</AppLink> and switch off **Enable polling for status updates** and **Enable Email Activity Tracking via Polling**. Press Save.
  </Step>
</Steps>

### Email Activity polling

Polling is the fallback when you cannot add a webhook. Switch on **Enable Email Activity Tracking via Polling** in the integration settings, and Courier queries SendGrid's Email Activity API on a schedule for each message's status.

It needs two things on the SendGrid side, and both are easy to miss:

<Frame caption="Read access to Email Activity, on the API key.">
  <img src="https://mintcdn.com/courier-4f1f25dc/rYENcCCTyPPrDtw0/assets/sendgrid-api-key-permissions.webp?fit=max&auto=format&n=rYENcCCTyPPrDtw0&q=85&s=331eddbb05fef3c0c180e4173788fa03" width="1030" height="861" data-path="assets/sendgrid-api-key-permissions.webp" />
</Frame>

<Frame caption="The email-history add-on, on the SendGrid plan.">
  <img src="https://mintcdn.com/courier-4f1f25dc/rYENcCCTyPPrDtw0/assets/sendgrid-addons.webp?fit=max&auto=format&n=rYENcCCTyPPrDtw0&q=85&s=d026432076b6e8f6996266059ae15c14" width="569" height="571" data-path="assets/sendgrid-addons.webp" />
</Frame>

<Warning>
  If either is missing, SendGrid rejects the query and **Courier turns the toggle back off**, showing a "Tracking Disabled" callout while messages stay at `SENT`. EU accounts hit this every time, since EU SendGrid has no Email Activity API. Use the webhook there.
</Warning>

## Troubleshooting

Check the <AppLink href="https://app.courier.com/logs">Courier Logs page</AppLink> to debug provider errors. For anything else, contact [Courier Support](mailto:support@courier.com).

<AccordionGroup>
  <Accordion title="SendGrid API Key Access Forbidden">
    Your API key does not cover the request. Check it against [SendGrid's allowed API key actions](https://docs.sendgrid.com/api-reference/api-key-permissions/api-key-permissions).

    #### Solution

    Either stay within the actions your key allows, or create a new key that covers the ones you need.

    **Creating a new SendGrid API key with the permissions you want**

    In the SendGrid API keys console, select "Create API Key," then [select the permissions in SendGrid's API key settings](https://docs.sendgrid.com/ui/account-and-settings/api-keys).

    <Frame caption="Creating a new SendGrid API key">
      <img src="https://mintcdn.com/courier-4f1f25dc/rYENcCCTyPPrDtw0/assets/sendgrid-create-api-key.webp?fit=max&auto=format&n=rYENcCCTyPPrDtw0&q=85&s=c1428e2b8fd09de2b6364f9b049edc44" width="2872" height="1548" data-path="assets/sendgrid-create-api-key.webp" />
    </Frame>

    SendGrid offers three permission scopes:

    * Full Access
    * Restricted Access
    * Billing Access.

    You can also set the access level for each one.
  </Accordion>

  <Accordion title="Error: sendgrid invalid email">
    You get this error when the address breaks internet email formatting standards, or does not exist on the recipient's mail server. It can come from your server or the receiver's.

    SendGrid checks the format before sending. If the receiving server cannot find the address, it returns a 550 bounce.

    Addresses go invalid for several reasons:

    * Typos or misformatting, so the address never reaches a real inbox.

    * The user changed addresses and left the old one empty. Inactivity and lack of engagement are the biggest cause.

    * The inbox provider went out of business, or its server went down for good. Every address on that dead domain is invalid.

    #### Solution

    * Scrub your email lists with an email verification tool to raise deliverability and engagement.

    * Sort your list by why each address signed up. Irrelevant or out-of-date email lowers open and click-through rates.

    * Group contacts by level of engagement. If an address looks like graymail, confirm it is still valid. If not, add it to the unsubscribe list.

    * Create a sunset policy, so you find disengaged contacts regularly and either remove or re-engage them.
  </Accordion>

  <Accordion title="Maximum Credits Exceeded SendGrid">
    SendGrid credits are the emails you can send. One credit per email, renewed at the start of each month. Exceeding your account's limit raises [this error in SendGrid's SMTP troubleshooting guide](https://docs.sendgrid.com/for-developers/sending-email/smtp-errors-and-troubleshooting).

    #### Solution

    Wait for your quota to renew, or upgrade your SendGrid plan for more credits.
  </Accordion>

  <Accordion title="SendGrid 535 Authentication Failed Bad Username Password">
    Three causes:

    * The username or password in the email client is wrong. The wrong mail server can also cause this.

    * The account is disabled, often for past-due payment or spam complaints.

    * SMTP authentication is not enabled in your email client.

    #### Solution

    * Check your username and password, billing plan, and account status.

    * Check that you confirmed your email address.

    * Configure SMTP authentication.

    * Store your API keys as environment variables. You then change a key in one place instead of hunting for every use.

    * Set up sender authentication for your domains, which gives you SPF and DKIM on the Twilio SendGrid account.
  </Accordion>

  <Accordion title="Sendgrid From Field Not Working">
    The `from` address does not match a verified Sender Identity. No email sends until you fix it.

    #### Solution

    [Authenticate your sender identities in SendGrid's sender authentication guide](https://docs.sendgrid.com/glossary/sender-authentication). A sender identity is the address recipients see as the sender. Authenticate one or more with Domain Authentication or Single Sender Verification.
  </Accordion>
</AccordionGroup>

## Provider details

```text theme={null}
sendgrid
```

Courier recommends routing to the channel. Naming this key in `routing.channels` instead is supported, and sends through just this provider.

<Card title="Send to a specific provider" icon="bullseye-arrow" href="/docs/send/send-to-a-provider" horizontal arrow="true">
  When that is worth doing, and what you give up: failover, channel priority, and providers you add later.
</Card>
