> ## 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 Gmail through Courier

> Connect a Gmail account over OAuth, and fix the invalid_grant error when the token expires.

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>;
};

<Warning>
  **Gmail is for testing and small internal sends.**<br />
  Google caps daily sends at 500 on consumer Gmail and 2,000 on Workspace. It may suspend accounts sending bulk email through the API, and OAuth tokens expire. For production use <Doc href="/docs/integrations/email/sendgrid">SendGrid</Doc>, <Doc href="/docs/integrations/email/aws-ses">AWS SES</Doc>, or <Doc href="/docs/integrations/email/postmark">Postmark</Doc>.
</Warning>

## Prerequisites

* [A Gmail or Google Workspace inbox](https://mail.google.com/)

## Setup

When you connect your Gmail account, Courier requests permission to send emails on your behalf. Courier sends nothing until you make a send request using the provider.

### OAuth authorization

Google APIs use OAuth for authentication and authorization. Once you grant permission, Courier requests an access token from the Google Authorization Server and sends the token to the Google Gmail API on your behalf.

To grant permission, sign into the Gmail inbox you want to send from and consent to Courier's requested Gmail scopes.

<Note>
  Gmail always sends as the authorized inbox, so a template's **From** field has no effect here. Set the display name with the integration's **From Name**, or override it per send with `override.body.fromName`. To change the address, authorize a different inbox.
</Note>

### Updating authorized account

On the <AppLink href="https://app.courier.com/integrations/gmail">Gmail Integration</AppLink> page, click "Authorize a different Gmail inbox" to send from another account. You must grant permissions again every time you change the account.

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>.

## 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.

In `override.body` you can set `subject`, `text`, `fromName`, `replyTo`, `cc`, and `bcc`. Each replaces the value Courier rendered. You can also override the request itself with `override.headers`, `override.method`, and `override.url`, and supply a token directly with `override.config.access_token`.

<CodeGroup>
  ```javascript Node.js highlight={8-16} theme={null}
  const { requestId } = await courier.send.message({
    message: {
      to: {
        email: "sarah@acme-corp.com",
      },
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      providers: {
        gmail: {
          override: {
            body: {
              subject: "Overridden subject",
              replyTo: "support@acme-corp.com",
              bcc: "archive@acme-corp.com",
            },
          },
        },
      },
    },
  });
  ```

  ```python Python highlight={8-16} theme={null}
  response = client.send.message(
      message={
          "to": {
              "email": "sarah@acme-corp.com",
          },
          "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
          "providers": {
              "gmail": {
                  "override": {
                      "body": {
                          "subject": "Overridden subject",
                          "replyTo": "support@acme-corp.com",
                          "bcc": "archive@acme-corp.com",
                      },
                  },
              },
          },
      },
  )
  ```

  ```bash cURL highlight={11-19} 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": {
          "gmail": {
            "override": {
              "body": {
                "subject": "Overridden subject",
                "replyTo": "support@acme-corp.com",
                "bcc": "archive@acme-corp.com"
              }
            }
          }
        }
      }
    }'
  ```

  ```ruby Ruby highlight={8-16} theme={null}
  response = courier.send_.message(
    message: {
      to: {
        email: "sarah@acme-corp.com"
      },
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      providers: {
        gmail: {
          override: {
            body: {
              subject: "Overridden subject",
              replyTo: "support@acme-corp.com",
              bcc: "archive@acme-corp.com"
            }
          }
        }
      }
    }
  )
  ```

  ```go Go highlight={10-18} 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{
  			"gmail": shared.MessageProvidersTypeParam{
  				Override: map[string]any{
  					"body": map[string]any{
  						"subject": "Overridden subject",
  						"replyTo": "support@acme-corp.com",
  						"bcc": "archive@acme-corp.com",
  					},
  				},
  			},
  		},
  	},
  })
  ```

  ```java Java highlight={6-12} 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("gmail", JsonValue.from(java.util.Map.of("override", java.util.Map.of(
                      "body", java.util.Map.of(
                          "subject", "Overridden subject",
                          "replyTo", "support@acme-corp.com",
                          "bcc", "archive@acme-corp.com"
                      )
                  ))))
              .build())
          .build())
      .build();
  SendMessageResponse response = client.send().message(params);
  ```

  ```php PHP highlight={8-16} theme={null}
  $response = $client->send->message(
    message: [
      'to' => [
        'email' => 'sarah@acme-corp.com',
      ],
      'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
      'providers' => [
        'gmail' => [
          'override' => [
            'body' => [
              'subject' => 'Overridden subject',
              'replyTo' => 'support@acme-corp.com',
              'bcc' => 'archive@acme-corp.com',
            ],
          ],
        ],
      ],
    ],
  );
  ```

  ```csharp C# highlight={9-25} theme={null}
  SendMessageParams parameters = new()
  {
      Message = new()
      {
          To = new UserRecipient { Email = "sarah@acme-corp.com" },
          Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
          Providers = new Dictionary<string, MessageProvidersType>()
          {
              {
                  "gmail",
                  new()
                  {
                      Override = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
                          """
                          {
                            "body": {
                              "subject": "Overridden subject",
                              "replyTo": "support@acme-corp.com",
                              "bcc": "archive@acme-corp.com"
                            }
                          }
                          """
                      ),
                  }
              },
          },
      },
  };

  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 '{"gmail": {"override": {"body": {"subject": "Overridden subject", "replyTo": "support@acme-corp.com", "bcc": "archive@acme-corp.com"}}}}'
  ```

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

Courier handles OAuth authorization and token refresh, so you rarely need `override.config.access_token`.

## Troubleshooting

### All Gmail sends failing with "request failed with status code 400"

This error means Courier's stored OAuth refresh token is no longer valid. Google rejected the token refresh with a `400 invalid_grant` response, so all subsequent sends fail as `UNDELIVERABLE`.

**Common causes:**

* The Gmail account password was changed
* A Google Workspace admin revoked Courier's OAuth access
* Google revoked the token due to inactivity or a security review
* The Google Cloud project's OAuth consent screen is in "Testing" mode (tokens expire after 7 days)

**How to confirm:** In Courier, check the message timeline for a Gmail-routed message. If you see `UNDELIVERABLE` with the error `"Request failed with status code 400"` and `willRetry: false`, the refresh token is dead.

**Fix:** Go to **Channels → Gmail** in Courier Studio, click **Update** next to the authorized account, and re-authorize with your Google account. This issues a fresh access token and refresh token, restoring sends immediately.

<Note>
  If the Gmail channel is configured with routing fallback, messages that fail on Gmail will fall through to the next configured channel (e.g., Courier Inbox). Only the Gmail delivery path is affected.
</Note>

## Provider details

```text theme={null}
gmail
```

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>
