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

# Elemental text element

> Render body text with formatting, styles, and inline links or images.

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

The text element renders a body of text. It supports formatting and styling, and can contain inline text content elements (string, link, img). The type value is `"text"`.

**When to use:**

* Display body text, paragraphs, and descriptions
* Create headings and subheadings
* Show formatted text with styling (bold, italic, colors)
* Include inline links and images within text
* Display dynamic content with Handlebars variables

**Basic example**

<CodeGroup>
  ```json Elemental theme={null}
  {
    "type": "text",
    "content": "Thanks for signing up!"
  }
  ```

  ```javascript Node.js highlight={9-12} theme={null}
  const { requestId } = await client.send.message({
    message: {
      to: {
        email: "sarah@acme-corp.com",
      },
      content: {
        version: "2022-01-01",
        elements: [
          {
            type: "text",
            content: "Thanks for signing up!",
          },
        ],
      },
    },
  });
  ```

  ```python Python highlight={9-12} theme={null}
  response = client.send.message(
      message={
        "to": {
          "email": "sarah@acme-corp.com",
        },
        "content": {
          "version": "2022-01-01",
          "elements": [
            {
              "type": "text",
              "content": "Thanks for signing up!",
            },
          ],
        },
      },
  )
  ```

  ```bash cURL highlight={12-15} 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"
          },
          "content": {
            "version": "2022-01-01",
            "elements": [
              {
                "type": "text",
                "content": "Thanks for signing up!"
              }
            ]
          }
        }
      }'
  ```

  ```ruby Ruby highlight={9-12} theme={null}
  response = courier.send_.message(
    message: {
      to: {
        email: "sarah@acme-corp.com"
      },
      content: {
        version: "2022-01-01",
        elements: [
          {
            type: "text",
            content: "Thanks for signing up!"
          }
        ]
      }
    }
  )
  ```

  ```go Go highlight={5-8} theme={null}
  // Elemental nodes carry no typed content fields, so pass the document as raw JSON.
  content := param.Override[shared.ElementalContentParam](json.RawMessage(`{
    "version": "2022-01-01",
    "elements": [
      {
        "type": "text",
        "content": "Thanks for signing up!"
      }
    ]
  }`))

  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"),
  			},
  		},
  		Content: courier.SendMessageParamsMessageContentUnion{OfElementalContent: &content},
  	},
  })
  ```

  ```java Java highlight={7-9} theme={null}
  SendMessageParams params = SendMessageParams.builder()
      .message(SendMessageParams.Message.builder()
          .to(JsonValue.from(java.util.Map.of("email", "sarah@acme-corp.com")))
          .content(JsonValue.from(java.util.Map.of(
                  "version", "2022-01-01",
                  "elements", java.util.List.of(
                    java.util.Map.of(
                      "type", "text",
                      "content", "Thanks for signing up!")))))
          .build())
      .build();
  SendMessageResponse response = client.send().message(params);
  ```

  ```php PHP highlight={9-12} theme={null}
  $response = $client->send->message(
    message: [
      'to' => [
        'email' => 'sarah@acme-corp.com',
      ],
      'content' => [
        'version' => '2022-01-01',
        'elements' => [
          [
            'type' => 'text',
            'content' => 'Thanks for signing up!',
          ],
        ],
      ],
    ],
  );
  ```

  ```csharp C# highlight={12-15} theme={null}
  // Elemental nodes expose no typed content fields, so build the document from raw JSON.
  SendMessageParams parameters = new()
  {
      Message = new()
      {
          To = new UserRecipient { Email = "sarah@acme-corp.com" },
          Content = ElementalContent.FromRawUnchecked(
              JsonSerializer.Deserialize<Dictionary<string, JsonElement>>("""
              {
                "version": "2022-01-01",
                "elements": [
                  {
                    "type": "text",
                    "content": "Thanks for signing up!"
                  }
                ]
              }
              """)),
      },
  };

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

  ```bash CLI highlight={4} theme={null}
  courier send message \
    --api-key "$COURIER_API_KEY" \
    --message.to '{"email": "sarah@acme-corp.com"}' \
    --message.content '{"version": "2022-01-01", "elements": [{"type": "text", "content": "Thanks for signing up!"}]}'
  ```

  ```text MCP theme={null}
  With Courier MCP, send an email to sarah@acme-corp.com with a text element that greets them by name.
  ```
</CodeGroup>

**Fields**

<ParamField path="content" type="string">
  The text content displayed in the notification. Either this field or the `elements` field must be specified. Supports Handlebars variables.
</ParamField>

<ParamField path="elements" type="TextContentElement[]">
  An array of Text Content Elements (string, link, img). Either this field or `content` must be specified, or both. When both are present, `elements` takes precedence and `content` is ignored. See the **Text content elements** section below.
</ParamField>

<ParamField path="align" type="string">
  Text alignment. One of `"left"`, `"center"`, or `"right"`. Defaults to `"left"`.
</ParamField>

<ParamField path="text_style" type="string">
  Renders the text as a heading level. One of `"text"`, `"h1"`, `"h2"`, or `"subtext"`. Defaults to `"text"`.
</ParamField>

<ParamField path="color" type="string">
  The text color. Any valid CSS color value (e.g., `"#007bff"`, `"rgb(0, 123, 255)"`).
</ParamField>

<ParamField path="bold" type="boolean">
  Apply bold formatting to the text.
</ParamField>

<ParamField path="italic" type="boolean">
  Apply italic formatting to the text.
</ParamField>

<ParamField path="strikethrough" type="boolean">
  Apply a strikethrough to the text.
</ParamField>

<ParamField path="underline" type="boolean">
  Apply an underline to the text.
</ParamField>

<ParamField path="locales" type="object">
  Region-specific content for localization. A text-node locale entry can include `content` (a string), `elements` (a structured array of inline nodes), or both. When both are provided, `elements` takes precedence. See the <Doc href="/docs/design/elemental/locales">Locales documentation</Doc>.
</ParamField>

<ParamField path="channels" type="string[]">
  An array of channel names. The text renders only for the listed channels. See <Doc href="/docs/design/elemental/control-flow#channels">Control flow</Doc>.
</ParamField>

**Examples and variants**

**Basic text**

Simple text content:

```json theme={null}
{
  "type": "text",
  "content": "Thanks for signing up!"
}
```

**Text with Handlebars**

Dynamic text with variables:

```json theme={null}
{
  "type": "text",
  "content": "This is a notification I sent to {{first_name}}"
}
```

Variables come from `message.data` (e.g., `data.first_name`).

**Styled text**

Text with formatting:

```json theme={null}
{
  "type": "text",
  "content": "Important Notice",
  "text_style": "h2",
  "bold": true,
  "color": "#007bff",
  "align": "center"
}
```

**Localized text**

Text with translations using the `content` string format:

```json theme={null}
{
  "type": "text",
  "content": "This is a notification I sent to {{first_name}}",
  "locales": {
    "es": {
      "content": "Esta es una notificación que envié a {{first_name}}"
    },
    "fr": {
      "content": "Ceci est une notification que j'ai envoyée à {{first_name}}"
    }
  }
}
```

**Localized text with structured elements**

When your text node uses the `elements` array, provide locale translations as `elements` arrays to preserve inline formatting across languages:

```json theme={null}
{
  "type": "text",
  "elements": [
    { "type": "string", "content": "Your order " },
    { "type": "string", "content": "#{{order.id}}", "bold": true },
    { "type": "string", "content": " has been confirmed." }
  ],
  "locales": {
    "fr": {
      "elements": [
        { "type": "string", "content": "Votre commande " },
        { "type": "string", "content": "#{{order.id}}", "bold": true },
        { "type": "string", "content": " a été confirmée." }
      ]
    }
  }
}
```

**When both `content` and `elements` are present**

If a text node includes both `content` and `elements`, only `elements` is used. Choose one format per node. The same applies to locale entries.

<Note>
  If a text node uses `elements` but a locale only provides `content`, Courier wraps that string into a single-element array for backward compatibility. Rendering still works, but any inline formatting is lost. Provide `elements` in your locale translations to keep it. See the <Doc href="/docs/design/elemental/locales#text-node-resolution">Locales documentation</Doc> for the full resolution table.
</Note>

**Heading styles**

Use text as headings:

```json theme={null}
{
  "type": "text",
  "content": "Welcome to Our Platform",
  "text_style": "h1"
},
{
  "type": "text",
  "content": "Get started in minutes",
  "text_style": "subtext"
}
```

**Text content elements**

The text element can contain an array of text content elements instead of, or in addition to, the `content` field. These sub-elements build inline text with links, images, and formatted strings.

<Note>
  Text content elements (string, link, img) must be children of a text element. They cannot stand alone as top-level elements.
</Note>

**String element**

Renders a simple string. Behaves like default text, but formats inline within a text element.

**Fields:**

<ParamField path="type" type="string" required>
  Must be `"string"`.
</ParamField>

<ParamField path="content" type="string" required>
  The text content displayed in the notification.
</ParamField>

<ParamField path="align" type="string">
  Text alignment. One of `"left"`, `"center"`, or `"right"`.
</ParamField>

<ParamField path="text_style" type="string">
  Renders the text as a heading level. One of `"text"`, `"h1"`, `"h2"`, or `"subtext"`.
</ParamField>

<ParamField path="color" type="string">
  The text color. Any valid CSS color value.
</ParamField>

<ParamField path="bold" type="boolean">
  Apply bold formatting to the text.
</ParamField>

<ParamField path="italic" type="boolean">
  Apply italic formatting to the text.
</ParamField>

<ParamField path="strikethrough" type="boolean">
  Apply a strikethrough to the text.
</ParamField>

<ParamField path="underline" type="boolean">
  Apply an underline to the text.
</ParamField>

<ParamField path="locales" type="object">
  Region-specific content for localization.
</ParamField>

**Link element**

Renders a clickable link within a body of text.

**Fields:**

<ParamField path="type" type="string" required>
  Must be `"link"`.
</ParamField>

<ParamField path="content" type="string" required>
  The text content of the link (the clickable text).
</ParamField>

<ParamField path="href" type="string">
  The address to link to. When provided, the link becomes clickable.
</ParamField>

<ParamField path="disable_tracking" type="boolean">
  Disable click tracking for the link. By default, Courier tracks link clicks.
</ParamField>

<ParamField path="align" type="string">
  Text alignment. One of `"left"`, `"center"`, or `"right"`.
</ParamField>

<ParamField path="text_style" type="string">
  Renders the text as a heading level. One of `"text"`, `"h1"`, `"h2"`, or `"subtext"`.
</ParamField>

<ParamField path="color" type="string">
  The text color. Any valid CSS color value.
</ParamField>

<ParamField path="bold" type="boolean">
  Apply bold formatting to the text.
</ParamField>

<ParamField path="italic" type="boolean">
  Apply italic formatting to the text.
</ParamField>

<ParamField path="strikethrough" type="boolean">
  Apply a strikethrough to the text.
</ParamField>

<ParamField path="underline" type="boolean">
  Apply an underline to the text.
</ParamField>

<ParamField path="locales" type="object">
  Region-specific content for localization.
</ParamField>

**Img element**

Renders an image inline within a body of text.

**Fields:**

<ParamField path="type" type="string" required>
  Must be `"img"`.
</ParamField>

<ParamField path="src" type="string" required>
  The source address of the image. Must be a publicly accessible URL.
</ParamField>

<ParamField path="alt_text" type="string">
  Text used for screen readers and displayed on mouse hover. Important for accessibility.
</ParamField>

<ParamField path="width" type="string">
  How wide the image renders. Any valid CSS width value (e.g., `"50px"`, `"100%"`).
</ParamField>

<ParamField path="href" type="string">
  An address to link to. Makes the image clickable.
</ParamField>

<ParamField path="disable_tracking" type="boolean">
  Disable click tracking for the link (if `href` is provided).
</ParamField>

<ParamField path="align" type="string">
  Text alignment. One of `"left"`, `"center"`, or `"right"`.
</ParamField>

<ParamField path="text_style" type="string">
  Renders the text as a heading level. One of `"text"`, `"h1"`, `"h2"`, or `"subtext"`.
</ParamField>

<ParamField path="color" type="string">
  The text color, for any text overlay.
</ParamField>

<ParamField path="bold" type="boolean">
  Apply bold formatting (for any text overlay).
</ParamField>

<ParamField path="italic" type="boolean">
  Apply italic formatting (for any text overlay).
</ParamField>

<ParamField path="strikethrough" type="boolean">
  Apply a strikethrough (for any text overlay).
</ParamField>

<ParamField path="underline" type="boolean">
  Apply an underline (for any text overlay).
</ParamField>

<ParamField path="locales" type="object">
  Region-specific content for localization. Can localize `src` and `href`.
</ParamField>

**Text with inline links**

Combine strings and links:

```json theme={null}
{
  "type": "text",
  "elements": [
    { "type": "string", "content": "Hey! " },
    { "type": "link", "content": "Check out this site.", "href": "https://www.example.com" },
    { "type": "string", "content": " It's awesome!" }
  ]
}
```

**Text with inline images**

Include images within text:

```json theme={null}
{
  "type": "text",
  "elements": [
    { "type": "string", "content": "Check out this emoji: " },
    { "type": "img", "src": "https://emoji.com/cool-emoji", "width": "20px", "alt_text": "Cool emoji" },
    { "type": "string", "content": " Pretty cool, right?" }
  ]
}
```

**Rich formatted text**

Mix strings, links, and formatting:

```json theme={null}
{
  "type": "text",
  "elements": [
    { "type": "string", "content": "Welcome, ", "bold": true },
    { "type": "string", "content": "{{user_name}}", "bold": true, "color": "#007bff" },
    { "type": "string", "content": "! Visit our " },
    { "type": "link", "content": "documentation", "href": "https://docs.example.com", "bold": true },
    { "type": "string", "content": " to get started." }
  ]
}
```

**Channel support**

* **Email**: ✅ Full support with all formatting options
* **Push**: ✅ Supported (formatting may be limited)
* **SMS**: ⚠️ Limited support (plain text only)
* **Inbox**: ✅ Full support with rich formatting

### Best practices

* **Use `content` for simple text**: plain text needs nothing more
* **Use `elements` for rich formatting**: inline links, images, or mixed styling
* **Avoid specifying both**: `elements` takes precedence and `content` is ignored. Pick one format per node
* **Match locale format to root format**: if your text node uses `elements`, provide `elements` in your locales. A `content`-only locale works but loses inline formatting
* **Keep text concise**: long paragraphs are hard to read, especially in email
* **Use headings appropriately**: use `text_style` for a proper heading hierarchy
