Flow · 11 steps

Design your own emails

Design the emails your customers and suppliers receive — invoices, quotes, order confirmations and purchase orders — in react-email's own components, or start from one of Manifold's.

A template is a small TSX module: a subject, an optional inbox preview, and the email itself, a function of the kind's data (the invoice as issued, the customer, your shop). Start by choosing a style — one look for every kind — then make any kind your own. It runs in a sandbox, so it can only shape the email, and every save is checked against sample data first and refused with the line that went wrong. Each save is a new version; choose which template a kind sends, and send yourself a test to see it arrive.

Services involved

A filled dot is a read; a ring is a call that changes something.

  • Notifications: https://notifications.getmanifold.io

Every call carries Authorization: Bearer $MANIFOLD_TOKEN unless its step says otherwise (getting a token). Run a flow in your sandbox, not your live organization: see Before you start.

1 Name the sender

Tests, like every message, go out under the organization's sender: its name and accounts email.

PUT /settings/sender Notifications

Request · application/json
{
  "senderName": "Ridgeline Outdoor",
  "accountsEmail": "accounts@ridgeline-outdoor.example"
}

What the answer holds: the operation's example.

Records notifications.sender-saved.

2 Read the catalogue

Each kind with the data it renders and a sample of it, Manifold's starters with their whole source, and the declarations of @manifold/email — what an editor completes and an agent reads first.

GET /email-templates/catalog Notifications

What the answer holds: the operation's example.

3 Choose a style

One look for every kind at once — classic, bold or minimal, or custom: your own layout (a template of kind layout) drawn around each kind's content. Every kind without a template of its own renders that style's starter from now.

PUT /email-templates/style Notifications

Request · application/json
{
  "style": "bold"
}

What the answer holds: the operation's example.

Records notifications.template-style-chosen.

4 Preview a draft

Rendered against the kind's sample and kept nowhere: the editor's live preview.

POST /email-templates/render Notifications

Request · application/json
{
  "kind": "invoice",
  "source": "import { Button, Container, Heading, Text, date, money } from \"@manifold/email\";\nexport const subject = (data) => `Invoice ${data.invoice.number} from ${data.shop.name}`;\nexport const preview = (data) => `${money(data.invoice.totals.totalCents, data.invoice.currency)} due`;\nexport default function Invoice(data) {\n  return (\n    <Container style={{ padding: \"32px\", fontFamily: \"Helvetica, Arial, sans-serif\" }}>\n      <Heading style={{ color: \"#0f5132\" }}>Thanks, {data.customer?.firstName ?? \"there\"}</Heading>\n      <Text>Invoice {data.invoice.number} comes to {money(data.invoice.totals.totalCents, data.invoice.currency)}{data.invoice.dueOn ? `, due ${date(data.invoice.dueOn, \"long\")}` : \"\"}.</Text>\n      <Button href=\"https://ridgeline-outdoor.example/pay\" style={{ backgroundColor: \"#0f5132\", color: \"#ffffff\", padding: \"12px 20px\", borderRadius: \"999px\" }}>Pay now</Button>\n    </Container>\n  );\n}\n"
}

What the answer holds: the operation's example.

Records nothing.

5 Save it

Checked against the sample first; kept as version 1 with its whole source.

PUT /email-templates/etp_01a15a02-7c41-7a6e-9d10-3f6c2b8e4a12 Notifications

Request · application/json
{
  "kind": "invoice",
  "name": "Invoice — our colours",
  "source": "import { Button, Container, Heading, Text, date, money } from \"@manifold/email\";\nexport const subject = (data) => `Invoice ${data.invoice.number} from ${data.shop.name}`;\nexport const preview = (data) => `${money(data.invoice.totals.totalCents, data.invoice.currency)} due`;\nexport default function Invoice(data) {\n  return (\n    <Container style={{ padding: \"32px\", fontFamily: \"Helvetica, Arial, sans-serif\" }}>\n      <Heading style={{ color: \"#0f5132\" }}>Thanks, {data.customer?.firstName ?? \"there\"}</Heading>\n      <Text>Invoice {data.invoice.number} comes to {money(data.invoice.totals.totalCents, data.invoice.currency)}{data.invoice.dueOn ? `, due ${date(data.invoice.dueOn, \"long\")}` : \"\"}.</Text>\n      <Button href=\"https://ridgeline-outdoor.example/pay\" style={{ backgroundColor: \"#0f5132\", color: \"#ffffff\", padding: \"12px 20px\", borderRadius: \"999px\" }}>Pay now</Button>\n    </Container>\n  );\n}\n",
  "basedOn": "invoice-classic"
}

What the answer holds: the operation's example.

Records notifications.template-saved.

6 Save it again

The same content is the template as it stands: nothing appended.

PUT /email-templates/etp_01a15a02-7c41-7a6e-9d10-3f6c2b8e4a12 Notifications

Request · application/json
{
  "kind": "invoice",
  "name": "Invoice — our colours",
  "source": "import { Button, Container, Heading, Text, date, money } from \"@manifold/email\";\nexport const subject = (data) => `Invoice ${data.invoice.number} from ${data.shop.name}`;\nexport const preview = (data) => `${money(data.invoice.totals.totalCents, data.invoice.currency)} due`;\nexport default function Invoice(data) {\n  return (\n    <Container style={{ padding: \"32px\", fontFamily: \"Helvetica, Arial, sans-serif\" }}>\n      <Heading style={{ color: \"#0f5132\" }}>Thanks, {data.customer?.firstName ?? \"there\"}</Heading>\n      <Text>Invoice {data.invoice.number} comes to {money(data.invoice.totals.totalCents, data.invoice.currency)}{data.invoice.dueOn ? `, due ${date(data.invoice.dueOn, \"long\")}` : \"\"}.</Text>\n      <Button href=\"https://ridgeline-outdoor.example/pay\" style={{ backgroundColor: \"#0f5132\", color: \"#ffffff\", padding: \"12px 20px\", borderRadius: \"999px\" }}>Pay now</Button>\n    </Container>\n  );\n}\n",
  "basedOn": "invoice-classic"
}

What the answer holds: the operation's example.

Records nothing.

7 Make it what invoices send

From now every invoice email renders this template and names the version it went out with.

PUT /email-templates/kinds/invoice/active Notifications

Request · application/json
{
  "templateId": "etp_01a15a02-7c41-7a6e-9d10-3f6c2b8e4a12"
}

What the answer holds: the operation's example.

Records notifications.template-activated.

8 Send yourself a test

Rendered with the sample and sent to the address you signed in with, subject prefixed [Test].

POST /email-templates/etp_01a15a02-7c41-7a6e-9d10-3f6c2b8e4a12/test Notifications

Request · application/json
{
  "notificationId": "ntf_01a15889-0b42-7e6d-8a5c-2f1b0e9d8c7c"
}

What the answer holds: the operation's example.

Records notifications.requested, notifications.sent.

9 List your templates

Most recently saved first, each with its version and whether its kind sends it.

GET /email-templates Notifications

What the answer holds: the operation's example.

10 Read one

Its latest version, whole: the source, its name and kind, and the starter it came from.

GET /email-templates/etp_01a15a02-7c41-7a6e-9d10-3f6c2b8e4a12 Notifications

What the answer holds: the operation's example.

11 Archive it

Kept with every version but no longer offered; invoices go back to Manifold's default.

POST /email-templates/etp_01a15a02-7c41-7a6e-9d10-3f6c2b8e4a12/archive Notifications

What the answer holds: the operation's example.

Records notifications.template-archived.

What a recorded fact is: Facts and the journal. Why a repeated step changes nothing: Idempotency and retries.