Email Service
Overview
Section titled “Overview”Applications can send emails through the core EmailManager system using the context.email service available in server functions. This allows plugins to send templated emails (e.g. welcome messages, approval notifications) without direct access to the platform schema or the EmailManager class.
The feature has two parts:
- Declarative email template registration — JSON files in the
email-templates/folder are automatically registered during installation - Email sending via
context.email— server functions callcontext.email.send()to send emails using registered templates
Required permission: notifications in manifest.json.
Quick Start
Section titled “Quick Start”1. Add the notifications permission
Section titled “1. Add the notifications permission”{ "id": "my-app", "permissions": ["database", "remote_functions", "notifications"]}2. Create an email template
Section titled “2. Create an email template”{ "name": "Welcome Email", "locales": { "en": { "subject": "Welcome to the system!", "html": "<h1>Hello {{name}}!</h1><p>Your account has been created.</p>", "text": "Hello {{name}}! Your account has been created." }, "hu": { "subject": "Üdvözöljük a rendszerben!", "html": "<h1>Kedves {{name}}!</h1><p>Fiókja létrejött.</p>", "text": "Kedves {{name}}! Fiókja létrejött." } }, "requiredData": ["name", "email"], "optionalData": ["position"]}3. Send the email from a server function
Section titled “3. Send the email from a server function”export async function createUser(params, context) { // ... create user logic ...
// Send welcome email const result = await context.email.send({ to: params.email, template: 'welcome', // Just the template name — no prefix needed data: { name: params.name, email: params.email }, locale: 'en' });
if (!result.success) { console.warn('Email sending failed:', result.error); }
return { success: true };}The context.email API
Section titled “The context.email API”The email property is available on the context object in server functions when the application has the notifications permission.
context.email.send(params)
Section titled “context.email.send(params)”Sends a templated email through the core EmailManager.
| Parameter | Type | Required | Description |
|---|---|---|---|
to | string | string[] | Yes | Recipient email address(es) |
template | string | Yes | Template name (without app ID prefix) |
data | Record<string, unknown> | Yes | Template variables |
locale | string | No | Locale code (default: 'hu') |
Returns: Promise<{ success: boolean; messageId?: string; error?: string }>
const result = await context.email.send({ to: 'user@example.com', template: 'order_confirmation', data: { orderId: 1234, total: '€99.00' }, locale: 'en'});Automatic template name prefixing
Section titled “Automatic template name prefixing”The template parameter is automatically prefixed with the application ID. You only need to provide the template name as defined in your email-templates/ folder:
| You write | What gets resolved |
|---|---|
'welcome' | 'my-app:welcome' |
'order_confirmation' | 'my-app:order_confirmation' |
This means you never need to know or use the full prefixed name in your code.
Permission check
Section titled “Permission check”If the application does not have the notifications permission, context.email is undefined. Always check before using:
if (context.email) { await context.email.send({ /* ... */ });}Or handle it gracefully:
export async function sendNotification(params, context) { if (!context.email) { throw new Error('Email service is not available — check notifications permission'); } // ...}Email Template Format
Section titled “Email Template Format”Templates are JSON files in the email-templates/ directory of your application.
File structure
Section titled “File structure”my-app/├── email-templates/│ ├── welcome.json│ ├── order_confirmation.json│ └── password_reset.json├── manifest.json└── ...JSON schema
Section titled “JSON schema”{ "name": "Human-readable template name", "locales": { "en": { "subject": "Email subject with {{variable}} support", "html": "<h1>HTML body with {{variable}} support</h1>", "text": "Plain text body with {{variable}} support" }, "hu": { "subject": "Email tárgya {{variable}} támogatással", "html": "<h1>HTML törzs {{variable}} támogatással</h1>", "text": "Szöveges törzs {{variable}} támogatással" } }, "requiredData": ["variable"], "optionalData": ["optionalVariable"]}| Field | Type | Description |
|---|---|---|
name | string | Display name for the template |
locales | Record<string, LocaleData> | Locale-specific content (subject, html, text) |
requiredData | string[] | Required template variables |
optionalData | string[] | Optional template variables |
Each locale entry contains:
| Field | Type | Description |
|---|---|---|
subject | string | Email subject line (supports {{variable}} syntax) |
html | string | HTML email body |
text | string | Plain text fallback body |
Template variables
Section titled “Template variables”Use {{variableName}} syntax in subject, html, and text fields. Variables are replaced with values from the data parameter when sending.
Template Registration Lifecycle
Section titled “Template Registration Lifecycle”Installation
Section titled “Installation”When a plugin is installed, the core PluginInstaller automatically:
- Reads all
.jsonfiles from theemail-templates/directory - For each file and each locale, creates a row in
platform.email_templates - The
typecolumn is set to{appId}:{fileName}(e.g.my-app:welcome) - Uses upsert (ON CONFLICT DO UPDATE) — reinstalling updates existing templates
Uninstallation
Section titled “Uninstallation”When a plugin is removed, all email template records with the {appId}:% prefix are deleted from platform.email_templates.
Update flow
Section titled “Update flow”Reinstalling or updating a plugin re-registers all templates using upsert. Changed templates are updated, new ones are added, but templates that were removed from the email-templates/ folder are not automatically deleted — they remain in the database until the plugin is fully uninstalled.
Error Handling
Section titled “Error Handling”Email sending failures do not throw exceptions. Instead, context.email.send() returns an error object:
const result = await context.email.send({ to: 'user@example.com', template: 'welcome', data: { name: 'John' }});
if (!result.success) { // Log the error, show a toast, or ignore console.error('Email failed:', result.error); // The calling function decides how to handle it}Complete Example
Section titled “Complete Example”export async function createEmployeeWithUser(params, context) { const { db, email } = context; const { name, emailAddress, position, department } = params;
// 1. Create user and employee in a transaction const employee = await db.execute(` -- ... insert logic ... `);
// 2. Send welcome email (non-blocking, non-critical) if (email) { const emailResult = await email.send({ to: emailAddress, template: 'employee_welcome', data: { name, email: emailAddress, position, department }, locale: 'hu' });
if (!emailResult.success) { console.warn(`Welcome email failed for ${emailAddress}: ${emailResult.error}`); } }
return { employee: employee.rows[0] };}