Email

Email templates

Customize the verification, password reset and welcome emails your project sends, using HTML templates, variables, a logo and a brand color.

You can replace the built-in auth emails with your own HTML. Each project has three template types. Any type you have not customized uses a built-in default design, colored with your brand color and headed with your logo.

Custom templates need your own SMTP

Saved custom templates are used only when the project sends through its own SMTP settings. Projects without SMTP send auth email through the shared OrbitNest sender, which always uses the built-in defaults.

Template types

TypeStudio labelSent whenVariables
otp_verificationOTP VerificationEmail sign-up and email OTP sign-inotp, app_name, user_email, logo_url, header_html
password_resetPassword ResetPassword recoveryotp, app_name, user_email, logo_url, header_html
welcomeWelcome EmailA new user completes email verificationapp_name, user_name, user_email, logo_url, header_html

These are the only types. Saving any other type returns 400 Invalid template type. See Email & SMTP for the exact trigger of each email.

Variables

VariableValue
{{otp}}The code: otp_config.code_length digits (6 by default). It expires after otp_config.expiry_minutes (10 by default)
{{app_name}}The project name
{{user_email}}The recipient's email address
{{user_name}}user_metadata.full_name, or user_metadata.name if that is missing. Empty if neither is set. Welcome only
{{logo_url}}The project logo: a cid: reference to an inline image for uploaded logos, the URL for a logo set as a URL, or empty if there is no logo
{{header_html}}Ready-made header markup: the logo image if there is one, otherwise the project name as a text wordmark

How variables are rendered:

  • Variables work in the subject, the HTML body and the plain-text body.
  • Write them without spaces: {{otp}} works, {{ otp }} is left as-is.
  • Unknown variables, and variables a type does not support, become an empty string.
  • In the HTML body, every value is HTML-escaped except {{header_html}}, which is ready-made markup. A user name such as <b>Ada</b> arrives as text, not markup.
  • The subject and the plain-text body are plain text, so values are inserted unescaped.
Example subject
{{app_name}} — Your verification code

Built-in defaults

If a type has no custom template, OrbitNest renders its built-in design. This is the same layout as the Elegant starter in Studio, with your logo or project name as the header and colors derived from your brand color. The default subjects are:

TypeDefault subject
otp_verification<project name> — Your verification code
password_reset<project name> — Password reset code
welcomeWelcome to <project name>

The verification and reset defaults include a plain-text part. The welcome default is HTML only.

Edit templates in Studio

Open your project and go to Settings → Email Templates. Only workspace admins can save or reset templates, change the brand color, and send previews.

  1. Pick a type: OTP Verification, Password Reset or Welcome Email. Types with a saved custom template are marked.
  2. Edit the Subject. Click a variable chip to copy it, for example {{otp}}.
  3. Choose a starter: Elegant, Vibrant or Corporate. Custom is selected automatically once you edit the HTML.
  4. Edit in Visual (WYSIWYG), HTML (raw source) or Preview (rendered in the page) mode.
  5. Click Save template.

If a type has no saved template, the editor opens with the Elegant starter already filled in. Nothing is stored until you click Save template. Until then, the server keeps sending the built-in default.

Note

Studio saves the subject and HTML body only. To add a plain-text body (text_body), use the API.

Logo and brand color

  • Logo: upload it in Settings → General → Logo (PNG or SVG, up to 200 KB). Uploaded logos are attached to each auth email as an inline image and referenced with cid:, because Gmail and other clients block data: image URLs. {{header_html}} and {{logo_url}} handle this for you.
  • Brand color: set Brand Color in the template editor and click Save color. It must be a 6-digit hex value such as #b8754a, which is also the default. It colors the built-in defaults and the starters.

Tip

The colors in a saved custom template are fixed in its HTML when you apply a starter. After you change the brand color, apply the starter again and save so the template picks up the new color.

Preview and test

  • Preview mode shows the HTML in a sandboxed frame inside Studio. Variables are not replaced in this view.
  • To send a real test, enter an address next to the Preview button at the bottom of the editor and click it. The current, unsaved subject and HTML are sent through your project's SMTP with the same escaping as real sends and these values:
VariableValue in a test send
otp1234
app_nameYour project name
user_emailThe address you entered
user_nameTest User
logo_urlYour project logo, as in real sends
header_htmlYour real header: the logo, or the project name as a wordmark

A test send needs your own SMTP settings. The shared sender never sends previews.

  • No SMTP configured: 400 with a message asking you to add SMTP settings.
  • The SMTP server rejects the email: 502. Use Test in the SMTP settings to find out why.
  • Test sends are limited to 10 per minute per IP address.

Reset to default

Reset to default appears when a type has a saved custom template. It deletes the template, and the project goes back to the built-in default for that type.

Templates API

These endpoints use a Studio admin access token, which is the access_token returned by POST /api/auth/signin. :projectId can be the project UUID or its slug. Any workspace member can read. Writes and previews need the admin role, and developers get 403.

MethodPathRoleBodyDescription
GET/api/projects/:projectId/email-templatesAny memberList the saved custom templates
GET/api/projects/:projectId/email-templates/:typeAny memberGet one template. data is null if the type is not customized
PUT/api/projects/:projectId/email-templates/:typeAdminsubject, html_body, text_body?, name?Create or replace a template
DELETE/api/projects/:projectId/email-templates/:typeAdminRevert to the default. Returns 404 if there is no custom template
POST/api/projects/:projectId/email-templates/:type/previewAdminemail, subject?, html_body?Send a test email. If html_body is left out, the saved template is used
GET/api/projects/:projectId/email-templates/settingsAny memberGet the brand color. data is null if none has been saved
PUT/api/projects/:projectId/email-templates/settingsAdminbrand_colorSet the brand color as a 6-digit hex value

Responses are wrapped as { "success": true, "data": ... }, except preview, which returns { "success": true, "message": "Preview sent to …" }. For preview, email must be a valid address (400 otherwise). If you leave out html_body and the type has no saved template, preview returns 200 with { "success": false, "message": "No template found" }.

Terminal
curl -X PUT "https://api.orbitnest.io/api/projects/my-app/email-templates/otp_verification" \
  -H "Authorization: Bearer $ORBITNEST_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "{{app_name}} — Your verification code",
    "html_body": "{{header_html}}<p>Your code is <strong>{{otp}}</strong>. It expires in 10 minutes.</p>",
    "text_body": "Your {{app_name}} code is {{otp}}. It expires in 10 minutes."
  }'
JSON
{
  "success": true,
  "data": {
    "id": "6f1c…",
    "project_id": "…",
    "type": "otp_verification",
    "name": "OTP Verification",
    "subject": "{{app_name}} — Your verification code",
    "html_body": "{{header_html}}<p>Your code is …</p>",
    "text_body": "Your {{app_name}} code is {{otp}}. It expires in 10 minutes.",
    "is_active": true,
    "created_at": "…",
    "updated_at": "…"
  }
}

Send a test of the saved template:

Terminal
curl -X POST "https://api.orbitnest.io/api/projects/my-app/email-templates/otp_verification/preview" \
  -H "Authorization: Bearer $ORBITNEST_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "email": "you@example.com" }'

PUT replaces the whole template. If you leave out text_body, any stored plain-text body is cleared.