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

> Connect an SMS provider, store a phone number on the profile, and send a text.

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 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="SMS, Users" />

Three things stand between you and a delivered text: a connected provider, a phone number on the profile, and a send.

<Warning>
  Unlike email, SMS has **no default provider in Test**. A new workspace can email you immediately, but a text needs your own Twilio or other SMS account connected first. That is the most common reason a first SMS goes nowhere.
</Warning>

## Prerequisites

* <AppLink href="https://app.courier.com/~/test/platform/api-keys">A Courier Test API key</AppLink>
* An account with an SMS provider, such as [Twilio](https://www.twilio.com/), and a number that can send
* A phone you can receive a text on

## Send it

<Steps>
  <Step title="Connect your SMS provider">
    In <AppLink href="https://app.courier.com/integrations/catalog">Integrations</AppLink>, add your provider and paste its credentials. For Twilio that is the Account SID, Auth Token, and a Messaging Service SID or sending number.

    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.

    <Doc href="/docs/integrations/sms/twilio">Twilio</Doc> covers the credential fields, and <Doc href="/docs/integrations/sms/overview">SMS providers</Doc> lists every alternative.
  </Step>

  <Step title="Put a phone number on the profile">
    SMS addresses a recipient by `phone_number`, and it must be **E.164**: a plus sign, country code, then the number, with no spaces, dashes, or brackets. <Doc href="/docs/recipients/overview#what-each-channel-needs">Users</Doc> covers the field every other channel reads.

    <CodeGroup>
      ```javascript Node.js highlight={2} theme={null}
      await client.profiles.create("user_123", {
        profile: { phone_number: "+12025550156" },
      });
      ```

      ```python Python highlight={3} theme={null}
      client.profiles.create(
          "user_123",
          profile={"phone_number": "+12025550156"},
      )
      ```

      ```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": { "phone_number": "+12025550156" } }'
      ```

      ```ruby Ruby highlight={3} theme={null}
      courier.profiles.create(
        "user_123",
        profile: {phone_number: "+12025550156"}
      )
      ```

      ```go Go highlight={2} theme={null}
      _, err := client.Profiles.New(context.TODO(), "user_123", courier.ProfileNewParams{
      	Profile: map[string]any{"phone_number": "+12025550156"},
      })
      ```

      ```java Java highlight={4} theme={null}
      ProfileCreateParams params = ProfileCreateParams.builder()
          .userId("user_123")
          .profile(ProfileCreateParams.Profile.builder()
              .putAdditionalProperty("phone_number", JsonValue.from("+12025550156"))
              .build())
          .build();

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

      ```php PHP highlight={3} theme={null}
      $client->profiles->create(
        'user_123',
        profile: ['phone_number' => '+12025550156'],
      );
      ```

      ```csharp C# highlight={7} theme={null}
      await client.Profiles.Create(
          "user_123",
          new()
          {
              Profile = new Dictionary<string, JsonElement>
              {
                  { "phone_number", JsonSerializer.SerializeToElement("+12025550156") },
              },
          }
      );
      ```

      ```bash CLI highlight={3} theme={null}
      courier profiles create user_123 \
        --api-key "$COURIER_API_KEY" \
        --profile '{"phone_number": "+12025550156"}'
      ```

      ```text MCP theme={null}
      With Courier MCP, set the phone number +12025550156 on user_123.
      ```
    </CodeGroup>

    A number without the country code is the single most common cause of an `UNROUTABLE` SMS. `2025550156` is not a valid recipient, and `+12025550156` is.
  </Step>

  <Step title="Send the text">
    Route to `sms` 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: ["sms"] },
          content: { title: "Delivery update", body: "Your order is out for delivery." },
        },
      });
      ```

      ```python Python highlight={4} theme={null}
      response = client.send.message(
          message={
              "to": {"user_id": "user_123"},
              "routing": {"method": "single", "channels": ["sms"]},
              "content": {"title": "Delivery update", "body": "Your order is out for delivery."},
          },
      )
      ```

      ```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": ["sms"] },
            "content": { "title": "Delivery update", "body": "Your order is out for delivery." }
          }
        }'
      ```

      ```ruby Ruby highlight={4} theme={null}
      response = courier.send_.message(
        message: {
          to: {user_id: "user_123"},
          routing: {method: "single", channels: ["sms"]},
          content: {title: "Delivery update", body: "Your order is out for delivery."}
        }
      )
      ```

      ```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("sms")}},
      		},
      		Content: courier.SendMessageParamsMessageContentUnion{
      			OfElementalContentSugar: &shared.ElementalContentSugarParam{
      				Title: "Delivery update",
      				Body:  "Your order is out for delivery.",
      			},
      		},
      	},
      })
      ```

      ```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("sms")))
              .build())
          .content(ElementalContentSugar.builder()
              .title("Delivery update")
              .body("Your order is out for delivery.")
              .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' => ['sms']],
          'content' => ['title' => 'Delivery update', 'body' => 'Your order is out for delivery.'],
        ],
      );
      ```

      ```csharp C# highlight={6} theme={null}
      SendMessageParams parameters = new()
      {
          Message = new()
          {
              To = new UserRecipient { UserID = "user_123" },
              Routing = new() { Method = "single", Channels = ["sms"] },
              Content = new ElementalContentSugar
              {
                  Title = "Delivery update",
                  Body = "Your order is out for delivery.",
              },
          },
      };

      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": ["sms"]}' \
        --message.content '{"title": "Delivery update", "body": "Your order is out for delivery."}'
      ```

      ```text MCP theme={null}
      With Courier MCP, send user_123 an SMS saying their order is out for delivery.
      ```
    </CodeGroup>

    SMS ignores `title` and sends `body`, so write the body to stand alone. Other channels use both, which is why the shorthand carries them together.
  </Step>
</Steps>

## Verify

<Steps>
  <Step title="Check the phone">
    The text arrives from the number or messaging service you connected.
  </Step>

  <Step title="Read the status">
    Open <AppLink href="https://app.courier.com/logs">Logs</AppLink>. `SENT` means the provider accepted it. `DELIVERED` means the carrier confirmed it, and only some providers report that.
  </Step>
</Steps>

If the message is `UNROUTABLE`, the profile had no `phone_number` Courier could use. Check the number is E.164 and that the profile you sent to is the one you wrote. <Doc href="/docs/send/statuses#why-a-message-is-unroutable">Why a message is UNROUTABLE</Doc> lists every reason.

## What changes in production

**Numbers need provisioning.** Trial accounts at most providers only text verified numbers, which is a provider restriction rather than a Courier one.

**Long messages split.** Over 160 GSM-7 characters a message becomes multiple segments and is billed per segment. Unicode, including emoji, drops the limit to 70.

**Regulations vary.** Several countries require sender registration, and the United States requires it for application-to-person traffic. Your provider handles that registration, so check its rules before a launch.

**Opt-out is not optional.** Carriers expect STOP to work. Courier applies <Doc href="/docs/recipients/preferences/overview">preferences</Doc> on every send, so wire your provider's opt-out webhook to them rather than tracking it separately.
