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

# Courier Create

> Embed the template and brand editors in your React app so customers design their own messages.

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

Courier Create is a template and brand editor you mount in your React app.

Your customers design and publish their <Doc href="/docs/tenants/overview">Tenant's</Doc> notifications without leaving your product.

Two editors, each wrapped by a provider that handles auth and state:

* **`TemplateProvider`** wraps **`TemplateEditor`** for content.
* **`BrandProvider`** wraps **`BrandEditor`** for logo, colors, and footer.

Everything they write is a Tenant-scoped Template. Email is fully supported.

<CardGroup cols={2}>
  <Card title="Template editor" icon="pen-ruler" href="/docs/design/embedded-designer/template-editor">
    Mount it, restrict channels, switch templates, offer variables, publish.
  </Card>

  <Card title="Brand editor" icon="palette" href="/docs/design/embedded-designer/brand-editor">
    Logo, colors, and footer, with their own scopes.
  </Card>

  <Card title="Tenant Templates API" icon="code" href="/docs/design/embedded-designer/api">
    The endpoints both editors write through.
  </Card>
</CardGroup>

## Install the package

Requires React 18.2.0 or newer.

```bash theme={null}
npm install @trycourier/react-designer
```

```jsx theme={null}
import "@trycourier/react-designer/styles.css";
import { TemplateProvider, TemplateEditor } from "@trycourier/react-designer";
```

Import the stylesheet once in your app. An editor mounted without it renders unstyled,
which reads as a broken layout rather than a missing import.

## Issue a scoped token

The editor runs in the browser, so it authenticates with a JWT, never your API key. Issue one from your backend with <Endpoint method="POST" path="/auth/issue-token" name="Create a JWT" href="/docs/api-reference/authentication/create-a-jwt" />, scoped to the Tenant the customer may edit:

<Doc href="/docs/in-app/authenticate-users">Authenticate users</Doc> is the source of truth for the mint-and-sign-in flow. The scopes below are the designer's own, and they differ from the Inbox scopes documented there.

<CodeGroup>
  ```javascript Node.js theme={null}
  const { token } = await client.auth.issueToken({
    scope: "user_id:user_123 tenant:acme-corp:read tenant:acme-corp:notifications:read tenant:acme-corp:notifications:write",
    expires_in: "30 days",
  });
  ```

  ```python Python theme={null}
  response = client.auth.issue_token(
      scope="user_id:user_123 tenant:acme-corp:read tenant:acme-corp:notifications:read tenant:acme-corp:notifications:write",
      expires_in="30 days",
  )
  ```

  ```bash cURL theme={null}
  curl --request POST \
    --url https://api.courier.com/auth/issue-token \
    --header "Authorization: Bearer $COURIER_API_KEY" \
    --header 'Content-Type: application/json' \
    --data '{
      "scope": "user_id:user_123 tenant:acme-corp:read tenant:acme-corp:notifications:read tenant:acme-corp:notifications:write",
      "expires_in": "30 days"
    }'
  ```

  ```ruby Ruby theme={null}
  response = courier.auth.issue_token(
    scope: "user_id:user_123 tenant:acme-corp:read tenant:acme-corp:notifications:read tenant:acme-corp:notifications:write",
    expires_in: "30 days"
  )
  ```

  ```go Go theme={null}
  response, err := client.Auth.IssueToken(context.TODO(), courier.AuthIssueTokenParams{
    Scope:     "user_id:user_123 tenant:acme-corp:read tenant:acme-corp:notifications:read tenant:acme-corp:notifications:write",
    ExpiresIn: "30 days",
  })
  ```

  ```java Java theme={null}
  AuthIssueTokenParams params = AuthIssueTokenParams.builder()
      .scope("user_id:user_123 tenant:acme-corp:read tenant:acme-corp:notifications:read tenant:acme-corp:notifications:write")
      .expiresIn("30 days")
      .build();

  var response = client.auth().issueToken(params);
  ```

  ```php PHP theme={null}
  $response = $client->auth->issueToken(
      scope: 'user_id:user_123 tenant:acme-corp:read tenant:acme-corp:notifications:read tenant:acme-corp:notifications:write',
      expiresIn: '30 days',
  );
  ```

  ```csharp C# theme={null}
  var response = await client.Auth.IssueToken(new AuthIssueTokenParams
  {
      Scope = "user_id:user_123 tenant:acme-corp:read tenant:acme-corp:notifications:read tenant:acme-corp:notifications:write",
      ExpiresIn = "30 days"
  });
  ```

  ```bash CLI theme={null}
  courier auth issue-token \
    --scope "user_id:user_123 tenant:acme-corp:read tenant:acme-corp:notifications:read tenant:acme-corp:notifications:write" \
    --expires-in "30 days"
  ```

  ```text MCP theme={null}
  With Courier MCP, issue a 30-day token for user_123 that can read and write acme-corp's templates.
  ```
</CodeGroup>

### Scopes

Scope a token to one Tenant so a customer edits only their own. Plural forms grant workspace-wide access.

| Scope                                       | Grants                   |
| ------------------------------------------- | ------------------------ |
| `tenant:<id>:read`                          | Read one Tenant          |
| `tenant:<id>:notifications:read` / `:write` | That Tenant's Templates  |
| `tenant:<id>:brand:read` / `:write`         | That Tenant's Brand      |
| `tenants:read`                              | All Tenants              |
| `tenants:notifications:read` / `:write`     | Templates across Tenants |
| `tenants:brand:read` / `:write`             | Brands across Tenants    |

The per-Tenant Template scope is `notifications`, plural. Always include a `user_id:<id>` scope.

**A scope that is too narrow fails at save, not at mount.** A token with Template scopes and
no Brand scopes renders a Brand editor the customer cannot save from. Nothing reports the
gap until the write is rejected.

## Verify

Mount the editor and confirm it renders. A blank editor means the provider created a new
draft. Publish a change, then open the Tenant's Template in Courier and confirm it reflects
what the customer wrote.

## Limits & behavior

* **Issue the token server-side.** Shipping your API key to the browser exposes the whole workspace.
* **A scope grants exactly what it names.** A token with only `notifications` scopes cannot write a Brand, even with the brand editor mounted.
* **Tenant-scoped, not workspace.** These Templates belong to a Tenant. For workspace Templates use the <Doc href="/docs/design/templates/api">Templates API</Doc>.
* **Topics are set at send time.** Pass `preferences.subscription_topic_id` on the send. See <Doc href="/docs/recipients/preferences/model">Topics</Doc>.

## FAQ

<AccordionGroup>
  <Accordion title="What scopes does the token need?">
    A `user_id:<id>` scope, plus `tenant:<id>:notifications:read` and `:write` for Templates, and `tenant:<id>:brand:read` / `:write` if you show the brand editor.
  </Accordion>

  <Accordion title="Is my API key exposed in the browser?">
    The API key never reaches the browser as long as you issue the JWT from your backend
    and pass only that token to the component.
  </Accordion>

  <Accordion title="Can I embed only the brand editor?">
    Mount `BrandProvider` with `BrandEditor` and a token scoped to the Brand. See
    <Doc href="/docs/design/embedded-designer/brand-editor">Brand editor</Doc>.
  </Accordion>

  <Accordion title="Which channels can my customers edit?">
    Email is fully supported. Pass `routing` to limit which channels appear, as
    <Doc href="/docs/design/embedded-designer/template-editor#restrict-the-channels">Template editor</Doc> describes.
  </Accordion>
</AccordionGroup>
