Skip to main content

Basic Setup

To start, install the package:
Minimal supported React version is 18.2.0.
npm install @trycourier/react-designer
# or
yarn add @trycourier/react-designer
Import the required components and styles:
import "@trycourier/react-designer/styles.css";
import { TemplateEditor, TemplateProvider } from "@trycourier/react-designer";
Use the components in your app:
function App() {
  return (
    <TemplateProvider templateId="template-123" tenantId="tenant-123" token="your-jwt-token">
      <TemplateEditor />
    </TemplateProvider>
  );
}
The TemplateProvider creates the template automatically if it does not already exist.

Authentication

The token prop on TemplateProvider accepts a JWT issued by the Courier API. Generate this token server-side and pass it to your frontend; never expose your API key in the browser.
curl --request POST \
  --url https://api.courier.com/auth/issue-token \
  --header 'Authorization: Bearer $YOUR_AUTH_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "scope": "user_id:$YOUR_USER_ID tenants:read tenants:notifications:read tenants:notifications:write",
    "expires_in": "30 days"
  }'
The example above grants access to all tenants. To restrict the token to a single tenant, use tenant-scoped scopes like tenant:tenant-123:read instead. See the Authentication page for the full list of available scopes.

Publishing Hook

Users can override the default publish button with custom logic:
import { TemplateEditor, TemplateProvider, useTemplateActions } from "@trycourier/react-designer";

function SaveButtonComponent() {
  const { publishTemplate } = useTemplateActions();

  const handlePublishTemplate = async () => {
    // Your custom logic here
    await publishTemplate();
  };

  return (
    <TemplateProvider templateId="template-123" tenantId="tenant-123" token="your-jwt-token">
      <TemplateEditor hidePublish />
      <button onClick={handlePublishTemplate}>Save Template</button>
    </TemplateProvider>
  );
}

Theming Support

You can customize the editor’s appearance using the theme prop:
<TemplateEditor
  theme={{
    background: "#ffffff",
    foreground: "#292929",
    border: "#DCDEE4",
    primary: "#ffffff",
    primaryForeground: "#696F8C",
    radius: "6px",
    // Add more theme variables as needed
  }}
/>

Disabling Auto-Save

By default, the Courier Create editor auto-saves content. To disable this feature, configure the provider as follows:
const { saveTemplate, publishTemplate } = useTemplateActions();

const handleSaveTemplate = async () => {
  await saveTemplate(); // Save first
  await publishTemplate(); // Then publish
};

<TemplateProvider templateId="template-123" tenantId="tenant-123" token="your-jwt-token">
  <TemplateEditor autoSave={false} hidePublish />
  <button onClick={handleSaveTemplate}>Save Template</button>
</TemplateProvider>

Restricting Visible Channels

You can restrict which channels appear in the template editor by using the routing.channels prop. This is useful when your application only uses specific channels (e.g., email-only workflows). To show only email (hiding Slack, SMS, push, etc.):
<TemplateEditor
  routing={{
    method: "single",
    channels: ["email"]
  }}
/>
The channels array controls which channels appear in the editor - any channels not in that array will be hidden. You can include multiple channels if needed:
<TemplateEditor
  routing={{
    method: "single",
    channels: ["email", "sms"]
  }}
/>
The method property controls delivery strategy:
  • "single" - delivers via first available channel (fallback routing)
  • "all" - delivers via all configured channels simultaneously
Available channel values: "email", "sms", "push", "inbox", "slack", "msteams"

Switching Templates Dynamically

If your application lets users select which template to edit (for example, from a dropdown or list), pass the selected template ID to TemplateProvider. The provider creates templates automatically if they don’t exist, so you don’t need to check beforehand.
function App() {
  const [selectedTemplateId, setSelectedTemplateId] = useState("welcome-email");

  return (
    <>
      <select onChange={(e) => setSelectedTemplateId(e.target.value)}>
        <option value="welcome-email">Welcome Email</option>
        <option value="order-confirmation">Order Confirmation</option>
        <option value="password-reset">Password Reset</option>
      </select>

      <TemplateProvider
        key={selectedTemplateId}
        templateId={selectedTemplateId}
        tenantId="tenant-123"
        token={token}
      >
        <TemplateEditor />
      </TemplateProvider>
    </>
  );
}
The key prop on TemplateProvider forces React to remount the component when the template changes. Without it, the editor may show stale content from the previous template because the internal state does not fully reset on prop changes alone.
Always include the key prop when templateId changes dynamically. This applies whether the editor is on a regular page, inside a modal, or in any other context.

Using in Modals and Dialogs

Courier Create works inside modals and dialogs. The same key prop pattern applies; React portals (used by most modal libraries) can prevent proper state updates when props change.
import { Modal } from "@mantine/core"; // or any modal library

function App() {
  const [templateId, setTemplateId] = useState("welcome-email");
  const [opened, setOpened] = useState(false);

  return (
    <>
      <button onClick={() => setOpened(true)}>Edit Template</button>

      <Modal opened={opened} onClose={() => setOpened(false)} size="xl">
        <TemplateProvider
          key={templateId}
          templateId={templateId}
          tenantId="tenant-123"
          token={token}
        >
          <TemplateEditor />
        </TemplateProvider>
      </Modal>
    </>
  );
}

Using Variables

Variables are placeholders in your template that get replaced with actual data when the email is sent. For example, instead of writing Hello customer you can write Hello {{user.firstName}}, which will display the recipient’s actual name. The Courier Embeddable editor supports nested variable structures:
<TemplateEditor
  variables={{
    user: {
      firstName: "John",
      lastName: "Doe",
      email: "john@example.com",
    },
    company: {
      name: "Acme Inc",
      address: {
        street: "123 Main St",
        city: "San Francisco",
      },
    },
  }}
/>

How to Insert Variables

  1. When editing text, type {{ to open the variable suggestions dropdown. Select the variable you want to insert from the list.
  2. Via curly braces {} icon in top toolbar (if the variables are available for selected element).

Sending a Message

Ensure you include the tenant_id in the message context and template identifier.
import { CourierClient } from "@trycourier/courier";

const courier = new CourierClient({ authorizationToken: "your-auth-token" });

const { requestId } = await courier.send({
  message: {
    context: {
      tenant_id: "tenant-123",
    },
    to: {
      email: "user@example.com",
      data: {
        name: "Spike Spiegel",
      },
    },
    template: "tenant/template-123", //`tenant/` must be prefixed with the template_id
  },
});

Overview

Your sample implementation will look something like this:
import React from "react";
import "@trycourier/react-designer/styles.css";
import {
  TemplateEditor,
  TemplateProvider,
  BrandEditor,
  BrandProvider
} from "@trycourier/react-designer";

// Replace with your own values
const token = process.env.REACT_APP_COURIER_TOKEN;
const tenantId = "test-tenant";
const templateId = "template-123";

function App() {
  return (
    <div>
      <TemplateProvider templateId={templateId} tenantId={tenantId} token={token}>
        <TemplateEditor 
         variables={{
				  "user": {
				    "firstName": "John",
				    "lastName": "Doe",
				    "email": "john@example.com"
				  },
				  "company": {
				    "name": "Acme Inc",
				    "address": {
				      "street": "123 Main St",
				      "city": "San Francisco"
				    }
				  }
				}}
        />
      </TemplateProvider>
    </div>
  );
}

export default App;

Embeddable Designer

Troubleshooting

Template content not updating when switching templates

If the editor shows stale content after changing the templateId prop, add a key prop to TemplateProvider that changes with the template ID:
<TemplateProvider key={templateId} templateId={templateId} tenantId="tenant-123" token={token}>
  <TemplateEditor />
</TemplateProvider>
This forces React to remount the provider and reset its internal state. This issue is most common when the editor is inside a modal or dialog (React portal), but can occur in any context where templateId changes dynamically.

Editor not loading inside a modal

Verify that TemplateProvider receives a valid token when the modal opens. If you generate the JWT lazily, the token may not be ready on first render. Either generate the token before opening the modal or conditionally render the provider:
<Modal opened={opened} onClose={() => setOpened(false)}>
  {token && (
    <TemplateProvider key={templateId} templateId={templateId} tenantId="tenant-123" token={token}>
      <TemplateEditor />
    </TemplateProvider>
  )}
</Modal>

What’s Next

Authentication

JWT scopes and token generation for Courier Create

Editor Properties

Full list of configuration options for Template and Brand Editors