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
| Type | Studio label | Sent when | Variables |
|---|---|---|---|
otp_verification | OTP Verification | Email sign-up and email OTP sign-in | otp, app_name, user_email, logo_url, header_html |
password_reset | Password Reset | Password recovery | otp, app_name, user_email, logo_url, header_html |
welcome | Welcome Email | A new user completes email verification | app_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
| Variable | Value |
|---|---|
{{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.
{{app_name}} — Your verification codeBuilt-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:
| Type | Default subject |
|---|---|
otp_verification | <project name> — Your verification code |
password_reset | <project name> — Password reset code |
welcome | Welcome 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.
- Pick a type: OTP Verification, Password Reset or Welcome Email. Types with a saved custom template are marked.
- Edit the Subject. Click a variable chip to copy it, for example
{{otp}}. - Choose a starter: Elegant, Vibrant or Corporate. Custom is selected automatically once you edit the HTML.
- Edit in Visual (WYSIWYG), HTML (raw source) or Preview (rendered in the page) mode.
- 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 blockdata: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:
| Variable | Value in a test send |
|---|---|
otp | 1234 |
app_name | Your project name |
user_email | The address you entered |
user_name | Test User |
logo_url | Your project logo, as in real sends |
header_html | Your 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:
400with 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.
| Method | Path | Role | Body | Description |
|---|---|---|---|---|
GET | /api/projects/:projectId/email-templates | Any member | List the saved custom templates | |
GET | /api/projects/:projectId/email-templates/:type | Any member | Get one template. data is null if the type is not customized | |
PUT | /api/projects/:projectId/email-templates/:type | Admin | subject, html_body, text_body?, name? | Create or replace a template |
DELETE | /api/projects/:projectId/email-templates/:type | Admin | Revert to the default. Returns 404 if there is no custom template | |
POST | /api/projects/:projectId/email-templates/:type/preview | Admin | email, subject?, html_body? | Send a test email. If html_body is left out, the saved template is used |
GET | /api/projects/:projectId/email-templates/settings | Any member | Get the brand color. data is null if none has been saved | |
PUT | /api/projects/:projectId/email-templates/settings | Admin | brand_color | Set 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" }.
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."
}'{
"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:
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.