NorthRelay Studio
One place for email templates and brands — visual or HTML editing, versioned releases, brand previews, and the same templates from the dashboard, the API, SDKs, MCP and embedded apps
NorthRelay Studio
NorthRelay Studio is where your email templates and brands live. Every template, whether you build it in the dashboard, adopt it from the gallery or create it from code, is a versioned design: you edit a draft, review it, and publish an immutable release that sends use. Studio is included from the Micro plan ($12/mo).
Open it from the sidebar: Studio → Overview, Templates, Brands, Assets, Applications and Activity.
What you get
| Capability | What it means for you |
|---|---|
| Templates | Build visually with blocks or write HTML with Handlebars variables. Start blank or from the gallery (authentication, notifications, billing, marketing). |
| Brands | Colours, logo, fonts, sender, compliance contact, footer, social links, custom variables and the unsubscribe page. Every template reads them as {{theme.*}}. Contrast checks warn when text would be hard to read. |
| Preview | Rendered by the same engine that sends: desktop, mobile, simulated dark mode and plain text, with sample data from the variable schema or your own values. Preview any template with another brand before you switch. |
| Review & publish | A field-by-field diff against the live release before you publish. Drafts autosave and never affect live mail. |
| Releases | Each publish is an immutable snapshot. Restore any earlier release in one click. |
| Usage | See which campaigns use a template and how many messages it sent, and which templates and campaigns use a brand. |
| Deliveries | Every send through a template, from the API or Studio, with its delivery events. |
| Test sends | Send yourself a test from a verified sender, safely retried with an idempotency key. |
| Assets | Upload a logo (PNG, JPEG or WebP up to 700 KiB). NorthRelay resizes it for email and serves it from an immutable HTTPS URL. |
| Applications | Products that send through NorthRelay (for example Alteriom Command Center) keep their own templates and brands under an application key, reached with a scoped nr_app_ credential. |
Account templates and application templates
Your own templates, the ones you create in the dashboard, belong to the reserved application account. Products can own templates under their own application key. The two differ in how they treat brands and variables:
Account templates (account) | Application templates (e.g. command-center) | |
|---|---|---|
| Brand | Uses the bound brand live: edit the brand and the next send uses it. | The brand is frozen in each release. After a brand change, the brand editor lists the templates to publish again and can publish them for you. |
| Variables | Lenient: a missing variable renders empty, extra variables are ignored. | Strict: required variables must be present, unknown variables are refused. |
| Sender | Optional on the template; the brand sender is the default, and a send can choose another verified sender. | Must be one of the credential's allowed senders. |
Sending with a template
Use the template id with the email API. Nothing changes if you already send this way:
curl -X POST https://app.northrelay.ca/api/v1/emails/send \
-H "Authorization: Bearer $NORTHRELAY_API_KEY" -H "Content-Type: application/json" \
-d '{
"from": { "email": "hello@acme.example", "name": "Acme" },
"to": [{ "email": "ana@example.com" }],
"content": { "templateId": "tmpl_123" },
"variables": { "first_name": "Ana" }
}'The send uses the template's live release. A retired template is refused with TEMPLATE_RETIRED, a template that was never published with TEMPLATE_NOT_PUBLISHED, and missing required variables with MISSING_TEMPLATE_VARIABLES; each error names the fix.
Campaigns pick a template in the campaign wizard and render it with the contact's details, the brand and the unsubscribe links.
From code
Everything in Studio is available through /api/v1/designs, the SDKs and the MCP server.
# Create a draft for your application
curl -X POST https://app.northrelay.ca/api/v1/designs \
-H "Authorization: Bearer $NORTHRELAY_APP_KEY" -H "Content-Type: application/json" \
-d '{
"applicationKey": "command-center",
"key": "daily-digest",
"locale": "en",
"draft": {
"name": "Daily digest",
"subject": "{{title}} — {{date}}",
"html": "<h1>{{title}}</h1>{{#each stories}}<p><a href=\"{{url}}\">{{headline}}</a></p>{{/each}}",
"category": "NOTIFICATION",
"variables": {
"title": { "type": "string", "required": true, "example": "Digest" },
"stories": { "type": "array", "items": { "headline": { "type": "string" }, "url": { "type": "string", "format": "url" } } }
}
}
}'
# Publish revision 1, then send the live release with an idempotency key
curl -X POST https://app.northrelay.ca/api/v1/designs/$DESIGN_ID/publish \
-H "Authorization: Bearer $NORTHRELAY_APP_KEY" -H "Content-Type: application/json" \
-d '{ "expectedRevision": 1 }'
curl -X POST https://app.northrelay.ca/api/v1/designs/$DESIGN_ID/send \
-H "Authorization: Bearer $NORTHRELAY_APP_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: digest-2026-10-08" \
-d '{ "to": [{ "email": "ops@example.com" }], "variables": { "title": "Digest", "date": "8 Oct", "stories": [] } }'import { NorthRelayClient } from '@northrelay/sdk';
const nr = new NorthRelayClient({ apiKey: process.env.NORTHRELAY_API_KEY! });
const { data: usage } = await nr.designs.usage(designId); // campaigns and send counts
await nr.designs.preview(designId, { sample: true, brandId }); // try another brand
const { data: brand } = await nr.designs.brandUsage(brandId); // what a brand change affects
await nr.designs.uploadAsset({ imageBase64 }); // logo for a brandEvery response is { success: true, data } or { success: false, error: { code, message } }. Writes carry expectedRevision (designs) or expectedUpdatedAt (brands), so two editors get a 409 REVISION_CONFLICT instead of overwriting each other.
API surface
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/capabilities | Permissions, application and sender restrictions, plan capacity |
| GET, POST | /api/v1/designs | List templates (filters, cursor) / create a draft |
| GET, PATCH, DELETE | /api/v1/designs/{id} | Read / replace the draft (expectedRevision) / delete a template that never sent |
| POST | /api/v1/designs/{id}/preview | Render the draft, the live release or a version; brandId previews another brand |
| POST | /api/v1/designs/{id}/publish | Publish expectedRevision |
| POST | /api/v1/designs/{id}/rollback | Copy an earlier version into a new release |
| GET | /api/v1/designs/{id}/releases | Release history; ?version= for a full snapshot |
| POST | /api/v1/designs/{id}/send | Send the live release (Idempotency-Key required) |
| GET | /api/v1/designs/{id}/deliveries | Sends through this template and their events |
| GET | /api/v1/designs/{id}/usage | Campaigns using the template, send counts |
| PATCH | /api/v1/designs/{id}/lifecycle | Retire or restore, with an optional replacement |
| POST | /api/v1/designs/{id}/brand | Bind a brand |
| POST | /api/v1/designs/{id}/sync | Compare with or apply updates from the gallery source |
| GET, POST | /api/v1/designs/catalog | Browse the gallery and shared templates / inspect one |
| POST | /api/v1/designs/catalog/adopt | Adopt a starter (asBlocks for the visual editor) |
| GET, POST | /api/v1/designs/brands | List / create brands |
| GET, PATCH, DELETE | /api/v1/designs/brands/{id} | Read / update (expectedUpdatedAt) / delete an unused brand |
| GET | /api/v1/designs/brands/{id}/usage | Templates and campaigns using the brand |
| POST | /api/v1/designs/brands/{id}/default | Make an account brand the default |
| GET, PUT, DELETE | /api/v1/designs/brands/{id}/tracking-domain | Custom tracking domain (POST …/verify checks the CNAME) |
| GET, POST, DELETE | /api/v1/designs/assets | List / upload / delete images |
| GET, POST | /api/v1/designs/manifest | Export (with a version that changes when any draft changes), plan or atomically apply drafts; publish: true then publishes each changed entry |
New releases render with handlebars-email-v2: values are escaped, template sources are checked on save and publish (no script, iframe, form, event handlers or javascript: links), and MJML styles and Outlook conditional comments are kept.
The TypeScript and Python SDKs expose these as client.designs, and the MCP server as the *_email_design*, *_email_brand* and *_email_asset* tools.
Installing templates into another account
A product that sends through its users' own NorthRelay accounts can ship its templates as a manifest: export the application's manifest from the product's account, then apply it with the user's credential into their account (dryRun: true to show the plan, then dryRun: false, publish: true to install and publish in one call). The export's version tells you when an installed copy is behind.
Embedding Studio in your product
@northrelay/studio-react renders the same Studio screens (template library, workspace, brands) inside another React app. Your server proxies the Studio API with an application credential, so no NorthRelay key reaches the browser. Alteriom Command Center uses it.
Legacy APIs
/api/v1/templates and /api/v1/brand-theme keep working until 30 April 2027 and return Deprecation, Sunset and Link: rel="successor-version" headers. Every existing template is now a Studio template with the same id, so templateId values in your code stay valid. Move template and brand management to /api/v1/designs.
Plans
| Free | Starter | Micro | Pro | Scale | |
|---|---|---|---|---|---|
| Studio access | – | – | Yes | Yes | Yes |
| Templates | – | – | 25 | 100 | 500 |
| Brands | – | – | 3 | 10 | 25 |
| Application credentials | – | – | Yes | Yes | Yes |
See Plans & Pricing for the full ladder.