Skip to content

Emails

Send transactional emails using Handlebars templates with layouts and partials. Delivery status, the rendered HTML, and every provider event land in Email Logs.

POST/api/v1/emails/sendemails:send

Send a transactional email using a registered template. Supports idempotency keys to prevent duplicate sends, and an optional sendAt to schedule the send up to 30 days ahead.

Request

const result = await helmdesk.emails.send({
  templateKey: 'welcome',
  to: { email: 'user@example.com', name: 'Jane' },
  variables: { activationUrl: 'https://...' },
}, { idempotencyKey: 'signup-jane-2024' })

// Schedule instead of sending now:
const scheduled = await helmdesk.emails.send({
  templateKey: 'trial-expiring',
  to: { email: 'user@example.com' },
  sendAt: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000), // in 3 days
})
// scheduled.status === 'scheduled', scheduled.scheduledAt set

Response

{
  "id": "d41f9a6b-88c5-4e2a-b7d0-1e5c3f9a2b64",
  "status": "queued",
  "to": "user@example.com",
  "subject": "Welcome to Acme",
  "deduplicated": false,
  "blocked": false,
  "blockReason": null,
  "scheduledAt": null,
  "createdAt": "2026-07-21T09:15:00.000Z"
}
POST/api/v1/emails/batchemails:send

Send up to 100 transactional emails in one request. Each item is delivered independently — one failed item does not fail the batch. Idempotency is a per-item field (idempotencyKey) rather than a header. Requires the emails:send scope and the Starter plan or higher.

Request

const result = await helmdesk.emails.sendBatch({
  emails: [
    {
      templateKey: 'welcome',
      to: { email: 'a@example.com', name: 'Ann' },
      variables: { firstName: 'Ann' },
      idempotencyKey: 'welcome-a-2026',
    },
    {
      templateKey: 'welcome',
      to: { email: 'b@example.com' },
      variables: { firstName: 'Bo' },
    },
  ],
})
// result.total / result.sent / result.blocked / result.failed
// result.results[i] -> { index, id, status, to, ... } on success,
//                      { index, to, error } on failure

Response

{
  "total": 2,
  "sent": 1,
  "blocked": 0,
  "failed": 1,
  "results": [
    {
      "index": 0,
      "id": "d41f9a6b-88c5-4e2a-b7d0-1e5c3f9a2b64",
      "status": "queued",
      "to": "a@example.com",
      "subject": "Welcome",
      "deduplicated": false,
      "blocked": false,
      "blockReason": null,
      "createdAt": "2026-07-21T09:15:00.000Z"
    },
    { "index": 1, "to": "b@example.com", "error": "Template \"welcome\" not found" }
  ]
}
POST/api/v1/emails/previewemails:send

Preview a rendered email template without sending. Uses sample data if no variables are provided.

Request

const { subject, html } = await helmdesk.emails.preview({
  templateKey: 'welcome',
  variables: { name: 'Test User' },
})

Response

{
  "subject": "Welcome, Test User",
  "html": "<html><body><h1>Welcome, Test User</h1>…</body></html>"
}
GET/api/v1/email-templatesemails:send

List the project's email templates (with current-version subject and detected variables) plus your account's shared templates (keys already @account/-prefixed). This is how integrations discover valid templateKey values for send and preview. Requires the emails:send scope.

Request

const { templates, accountTemplates } = await helmdesk.emails.templates.list()
// templates[0] -> { key: 'welcome', subject: 'Welcome!', variables: [...] }

Response

{
  "templates": [
    {
      "key": "welcome",
      "name": "Welcome",
      "description": "Subject: Welcome to {{brandName}}",
      "subject": "Welcome to {{brandName}}",
      "variables": ["name", "activationUrl"],
      "updatedAt": "2026-07-20T16:31:12.004Z"
    }
  ],
  "accountTemplates": [
    {
      "key": "@account/invoice-paid",
      "name": "Invoice Paid",
      "description": null,
      "subject": "Your invoice is paid",
      "updatedAt": "2026-06-02T10:00:00.000Z"
    }
  ]
}
GET/api/v1/email-templates/:key/schemaemails:manage

The variables a template expects, detected from its current version. Built-in branding variables (brandName, brandLogoUrl, supportEmail, …) are excluded — Helmdesk fills those in at send time.

Request

const schema = await helmdesk.emails.getTemplateSchema('welcome')
// schema.variables -> ['name', 'activationUrl']

Response

{
  "key": "welcome",
  "name": "Welcome",
  "subject": "Welcome to {{brandName}}",
  "variables": ["name", "activationUrl"]
}

Creating templates, and wrapping them in a layout

Templates are created by importing a .hbs file, not by a JSON POST. The import call is also where you attach a layout — see Email Templates for the full flow, including why a template can come back with layoutLinked: false.

Batch sending

POST /api/v1/emails/batch accepts an emails array (min 1, max 100 items). Each item has the same shape as a single send — templateKey and to.email are required; to.name, variables, and environment are optional — plus an optional per-item idempotencyKey (batch idempotency is a field on each item, not the Idempotency-Key header used by single send).

The response returns summary counts (total, sent, blocked, failed) plus a results array. Each item is delivered independently — a failed item does not fail the batch. On success a slot looks like { index, id, status, to, subject, deduplicated, blocked, blockReason, createdAt }; on failure it is { index, to, error }. Always inspect each result's error and blocked fields. Batch sending requires the emails:send scope and the Starter plan or higher (the Free plan returns 403). One batch counts as a single request against the 100 requests/minute rate limit.

Via MCP, use the send_email_batch tool with an emails array where each item is { templateKey, to_email, to_name?, variables?, idempotencyKey? }.

Scheduled sends

Pass sendAt (ISO timestamp or Date, up to 30 days ahead) on a single or batch send. The email renders immediately and waits in status scheduled; suppression, pause, and allowlist rules are re-checked at fire time. A scheduled send can be canceled from Email Logs any time before it fires. A past sendAt sends immediately.

Environments & sending modes

Which data plane a send lands in is decided by your API key — sk_sandbox_ keys capture emails without delivering them (see Sandbox). For live sending, modes include live, test recipient (redirects to a single address), allowlist (only certain domains), and paused (mail is held until you resume).

See Email Templates for the template import API and shared template system.