> ## 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` from the Node SDK (`@trycourier/courier` v7 and later, where the client is the default import). 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 your first email

> Connect an email provider and your own domain, then send a real email and verify it.

export const Tags = ({items}) => {
  const routes = {
    Email: "/integrations/email/overview",
    SMS: "/integrations/sms/overview",
    Push: "/integrations/push/overview",
    Inbox: "/in-app/overview",
    Chat: "/integrations/direct-message/overview",
    Templates: "/design/templates/overview",
    Variables: "/design/templates/variables",
    Elemental: "/design/elemental/overview",
    Brands: "/design/brands",
    Translations: "/design/elemental/locales",
    Routing: "/send/routing",
    Preferences: "/recipients/preferences/overview",
    Journeys: "/journeys/overview",
    Broadcasts: "/broadcasts/overview",
    Tenants: "/tenants/overview",
    Logs: "/monitor/overview",
    Webhooks: "/monitor/webhooks/outbound",
    Lists: "/recipients/lists-and-audiences/overview",
    Users: "/recipients/overview",
    Digests: "/journeys/nodes/digest",
    Environments: "/workspaces/overview",
    MCP: "/resources/mcp"
  };
  const icons = {
    Email: "envelope",
    SMS: "comment",
    Push: "mobile",
    Inbox: "inbox",
    Chat: "comments",
    Templates: "pen-ruler",
    Variables: "pen-ruler",
    Elemental: "pen-ruler",
    Brands: "pen-ruler",
    Translations: "pen-ruler",
    Routing: "paper-plane",
    Preferences: "users",
    Journeys: "route",
    Broadcasts: "bullhorn",
    Tenants: "building",
    Logs: "chart-simple",
    Webhooks: "chart-simple",
    Lists: "users",
    Users: "users",
    Digests: "route",
    Environments: "briefcase",
    MCP: "toolbox"
  };
  const base = "https://d3gk2c5xim1je2.cloudfront.net/fontawesome/v7.2.0/regular/";
  const names = String(items || "").split(",").map(entry => entry.trim()).filter(Boolean);
  return <div className="cx-tags">
      {names.map(name => {
    const href = routes[name];
    const icon = icons[name];
    const url = icon ? "url(" + base + icon + ".svg)" : null;
    const style = url ? {
      "--cx-tag-icon": url
    } : null;
    if (!href) {
      return <span className="cx-tag" data-icon={icon} style={style} key={name}>
              {name}
            </span>;
    }
    return <a className="cx-tag" data-icon={icon} style={style} href={href} key={name}>
            {name}
          </a>;
  })}
    </div>;
};

export const Endpoint = ({method, path, name, href, children, bare}) => {
  const verb = String(method || "").toUpperCase();
  const title = verb + " " + path;
  const label = children || name || path;
  if (bare) {
    return href ? <a href={href}><code>{title}</code></a> : <code>{title}</code>;
  }
  if (!href) {
    return <span className="cx-endpoint" data-method={verb} title={title}>
        <span className="cx-endpoint-label">{label}</span>
        <span className="cx-endpoint-method">{verb}</span>
      </span>;
  }
  return <a className="cx-endpoint" data-method={verb} href={href} title={title}>
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method">{verb}</span>
    </a>;
};

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 Guide = ({href, children, name, bare}) => {
  const label = children || name || href;
  if (bare) {
    return <a href={href}>{label}</a>;
  }
  return <a className="cx-endpoint" data-kind="guide" href={href}>
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method">GUIDE</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>;
};

<Tags items="Email, Users" />

Every workspace can email you on day one. Reaching anyone else takes your own provider and your own domain.

<Note>
  Only need to prove the API works? The <Doc href="/docs/get-started/send-a-test-message">quickstart</Doc> sends an email in one call with no provider setup. It reaches your signup address only, which is why this guide exists.
</Note>

## Prerequisites

* <AppLink href="https://app.courier.com/~/test/platform/api-keys">A Courier Test API key</AppLink>
* An account with an email provider, such as [SendGrid](https://sendgrid.com/), [Postmark](https://postmarkapp.com/), or [Resend](https://resend.com/)
* A domain you can add DNS records to

## Send it

<Steps>
  <Step title="Connect your own provider">
    Add your provider from <AppLink href="https://app.courier.com/integrations/catalog">Integrations</AppLink> and paste its API key. <Doc href="/docs/integrations/email/overview">Email providers</Doc> lists every one, and each page carries that provider's credential fields.

    Integrations are per environment, so connect it in the same environment your API key belongs to. A provider added in Production does nothing for a Test key.

    <Note>
      The **Courier Test Email Provider** stays connected alongside yours. Once you add your own, <Doc href="/docs/send/routing#channel-priority">channel priority</Doc> decides which one sends, so set yours first.
    </Note>
  </Step>

  <Step title="Verify your sending domain">
    Your provider has to prove it may send as you. That means DKIM and SPF records on your domain, added at your DNS host and verified in the provider's dashboard.

    Skipping this is the single biggest cause of mail landing in spam. An unauthenticated message from a domain that publishes no policy is exactly what a receiving server is built to distrust.

    <Guide href="/docs/guides/send-from-your-domain">Send from your own domain</Guide> walks the records and setting the From address.
  </Step>

  <Step title="Put an email address on the profile">
    Email addresses a recipient by `email`. Nothing else is required.

    <CodeGroup>
      ```javascript Node.js highlight={2} theme={null}
      await client.profiles.create("user_123", {
        profile: { email: "sarah@acme-corp.com" },
      });
      ```

      ```python Python highlight={3} theme={null}
      client.profiles.create(
          "user_123",
          profile={"email": "sarah@acme-corp.com"},
      )
      ```

      ```bash cURL highlight={4} theme={null}
      curl -X POST https://api.courier.com/profiles/user_123 \
        -H "Authorization: Bearer $COURIER_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{ "profile": { "email": "sarah@acme-corp.com" } }'
      ```

      ```ruby Ruby highlight={3} theme={null}
      courier.profiles.create(
        "user_123",
        profile: {email: "sarah@acme-corp.com"}
      )
      ```

      ```go Go highlight={2} theme={null}
      _, err := client.Profiles.New(context.TODO(), "user_123", courier.ProfileNewParams{
      	Profile: map[string]any{"email": "sarah@acme-corp.com"},
      })
      ```

      ```java Java highlight={4} theme={null}
      ProfileCreateParams params = ProfileCreateParams.builder()
          .userId("user_123")
          .profile(ProfileCreateParams.Profile.builder()
              .putAdditionalProperty("email", JsonValue.from("sarah@acme-corp.com"))
              .build())
          .build();

      client.profiles().create(params);
      ```

      ```php PHP highlight={3} theme={null}
      $client->profiles->create(
        'user_123',
        profile: ['email' => 'sarah@acme-corp.com'],
      );
      ```

      ```csharp C# highlight={7} theme={null}
      await client.Profiles.Create(
          "user_123",
          new()
          {
              Profile = new Dictionary<string, JsonElement>
              {
                  { "email", JsonSerializer.SerializeToElement("sarah@acme-corp.com") },
              },
          }
      );
      ```

      ```bash CLI highlight={3} theme={null}
      courier profiles create user_123 \
        --api-key "$COURIER_API_KEY" \
        --profile '{"email": "sarah@acme-corp.com"}'
      ```

      ```text MCP theme={null}
      With Courier MCP, set the email sarah@acme-corp.com on user_123.
      ```
    </CodeGroup>

    Addressing a profile rather than an address is what lets <Doc href="/docs/recipients/preferences/overview">preferences</Doc> apply and what keeps one send working when the person changes their address. <Doc href="/docs/recipients/overview#what-each-channel-needs">Users</Doc> covers the field every other channel reads.
  </Step>

  <Step title="Send the email">
    Route to `email` and Courier picks the provider you connected.

    <CodeGroup>
      ```javascript Node.js highlight={4} theme={null}
      const { requestId } = await client.send.message({
        message: {
          to: { user_id: "user_123" },
          routing: { method: "single", channels: ["email"] },
          content: { title: "Your order shipped", body: "It should arrive Thursday." },
        },
      });
      ```

      ```python Python highlight={4} theme={null}
      response = client.send.message(
          message={
              "to": {"user_id": "user_123"},
              "routing": {"method": "single", "channels": ["email"]},
              "content": {"title": "Your order shipped", "body": "It should arrive Thursday."},
          },
      )
      ```

      ```bash cURL highlight={7} theme={null}
      curl -X POST https://api.courier.com/send \
        -H "Authorization: Bearer $COURIER_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "message": {
            "to": { "user_id": "user_123" },
            "routing": { "method": "single", "channels": ["email"] },
            "content": { "title": "Your order shipped", "body": "It should arrive Thursday." }
          }
        }'
      ```

      ```ruby Ruby highlight={4} theme={null}
      response = courier.send_.message(
        message: {
          to: {user_id: "user_123"},
          routing: {method: "single", channels: ["email"]},
          content: {title: "Your order shipped", body: "It should arrive Thursday."}
        }
      )
      ```

      ```go Go highlight={8} theme={null}
      response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
      	Message: courier.SendMessageParamsMessage{
      		To: courier.SendMessageParamsMessageToUnion{
      			OfUserRecipient: &shared.UserRecipientParam{UserID: courier.String("user_123")},
      		},
      		Routing: courier.SendMessageParamsMessageRouting{
      			Method:   string(shared.MessageRoutingMethodSingle),
      			Channels: []shared.MessageRoutingChannelUnionParam{{OfString: courier.String("email")}},
      		},
      		Content: courier.SendMessageParamsMessageContentUnion{
      			OfElementalContentSugar: &shared.ElementalContentSugarParam{
      				Title: "Your order shipped",
      				Body:  "It should arrive Thursday.",
      			},
      		},
      	},
      })
      ```

      ```java Java highlight={6} theme={null}
      SendMessageParams.Message message = SendMessageParams.Message.builder()
          .to(SendMessageParams.Message.To.ofUserRecipient(
              UserRecipient.builder().userId("user_123").build()))
          .routing(SendMessageParams.Message.Routing.builder()
              .method(SendMessageParams.Message.Routing.Method.SINGLE)
              .channels(List.of(MessageRoutingChannel.ofString("email")))
              .build())
          .content(ElementalContentSugar.builder()
              .title("Your order shipped")
              .body("It should arrive Thursday.")
              .build())
          .build();

      client.send().message(SendMessageParams.builder().message(message).build());
      ```

      ```php PHP highlight={4} theme={null}
      $response = $client->send->message(
        message: [
          'to' => ['user_id' => 'user_123'],
          'routing' => ['method' => 'single', 'channels' => ['email']],
          'content' => ['title' => 'Your order shipped', 'body' => 'It should arrive Thursday.'],
        ],
      );
      ```

      ```csharp C# highlight={6} theme={null}
      SendMessageParams parameters = new()
      {
          Message = new()
          {
              To = new UserRecipient { UserID = "user_123" },
              Routing = new() { Method = "single", Channels = ["email"] },
              Content = new ElementalContentSugar
              {
                  Title = "Your order shipped",
                  Body = "It should arrive Thursday.",
              },
          },
      };

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

      ```bash CLI highlight={4} theme={null}
      courier send message \
        --api-key "$COURIER_API_KEY" \
        --message.to '{"user_id": "user_123"}' \
        --message.routing '{"method": "single", "channels": ["email"]}' \
        --message.content '{"title": "Your order shipped", "body": "It should arrive Thursday."}'
      ```

      ```text MCP theme={null}
      With Courier MCP, email user_123 to say their order shipped.
      ```
    </CodeGroup>

    Email uses `title` as the subject line and `body` as the message, so both do work here. SMS ignores the title, which is why the same shorthand carries them together.
  </Step>
</Steps>

## Verify

<Steps>
  <Step title="Check your email">
    The message arrives from the address you set on the integration.
  </Step>

  <Step title="Read the status">
    Open <AppLink href="https://app.courier.com/logs">Logs</AppLink>. `SENT` means your provider accepted it. `DELIVERED` means the recipient's mail server did.
  </Step>

  <Step title="Confirm it came from your provider">
    Open the message and check the provider on the send event. If it says Courier Test Email Provider, channel priority is still preferring it over yours.
  </Step>
</Steps>

`DELIVERED` with nothing in your mail client usually means spam or a corporate gateway rather than a failed send. <Guide href="/docs/guides/debug-delivery">Debug delivery</Guide> works that case from Logs outward.

## What changes in production

**A bounce is not a retry.** An `UNDELIVERABLE` message with a `BOUNCED` reason means the provider rejected the recipient, and Courier does not try again. A hard bounce means the address does not exist.

**Suppression lists are the provider's, not ours.** After a bounce or complaint, most providers stop accepting that address. Nothing you send reaches it until you remove it there.

**Add a backup provider.** Two email providers on the channel means an outage at one fails over rather than failing. <Doc href="/docs/send/routing#failover">Routing and failover</Doc> covers the order.

**Unsubscribes belong in preferences.** An `UNSUBSCRIBED` or `OPT_IN_REQUIRED` reason means <Doc href="/docs/recipients/preferences/overview">preferences</Doc> stopped the send, which is the system working. Wire your provider's unsubscribe webhook to them rather than keeping a second list.
