Skip to content

api.sendEmail() sends an internal workflow notification to the notification address configured in Settings. It is intended for operational alerts: notifying you or your team when a workflow reaches a specific state, needs attention, completes with a useful summary, or encounters a condition worth flagging.

This API is not a general email-sending feature. It always sends to the single notification email address in Settings and is designed for internal workflow notifications, not customer or external messaging.

  • To send emails successfully, a notification email address should be set in Settings.
  • The shop must be on a paid plan (Starter or above).
  • Each plan includes a sending quota that resets at the start of each billing cycle.
PlanQuota
FreeNot available
Starter500 emails / billing cycle
Growth2,000 emails / billing cycle
Business5,000 emails / billing cycle
Enterprise10,000 emails / billing cycle
await api.sendEmail({ subject, text, html });
OptionTypeRequiredDescription
subjectstringyesEmail subject line
textstringnoPlain-text body
htmlstringnoHTML body

At least one of text or html must be provided. If both are given, email clients that support HTML will show the HTML version; others fall back to plain text.

No per-message routing fields are supported. Do not pass to, cc, bcc, replyTo, or custom sender fields to api.sendEmail().

Returns Promise<{ sent: boolean, reason?: string }>.

ResultWhen
{ sent: true }Email was sent successfully
{ sent: false, reason }Email was skipped (free plan, quota reached, no notification email set). The workflow continues normally.

An exception is thrown only when the platform itself is unable to send the email due to an internal configuration or delivery error.

The to address is always the notification email from Settings. It cannot be changed per-call.

The strongest uses for api.sendEmail() are internal operational notifications such as:

  • alert me when something needs attention
  • send me a completion summary
  • notify me when a workflow failed or skipped important records

These are the kinds of notifications this API is designed for: a workflow can notify your team, but it cannot email arbitrary recipients.

api.sendEmail() is available in both regular workflow steps and in the onWorkflowComplete and onWorkflowError lifecycle hooks. Sending an alert from onWorkflowError is a common pattern: it fires regardless of which step failed.

Example: alert when something needs attention

Section titled “Example: alert when something needs attention”
export class Workflow {
async start(data, headers, api) {
if (data.refund_amount > 500) {
await api.scheduleNextStep({
delay: 10,
action: 'flagForReview',
payload: { orderId: data.order_id, amount: data.refund_amount },
});
}
}
async flagForReview(data, headers, api) {
await api.sendEmail({
subject: `High-value refund flagged: order ${data.orderId}`,
text: `A refund of $${data.amount} was requested for order ${data.orderId} and requires review.`,
});
}
}
async sendSummary(data, headers, api) {
await api.sendEmail({
subject: `Inventory sync complete: ${data.updatedCount} items updated`,
text: `Sync finished. Updated: ${data.updatedCount}. Skipped: ${data.skippedCount}. Errors: ${data.errorCount}.`,
html: `
<h2>Inventory sync complete</h2>
<table>
<tr><td>Updated</td><td>${data.updatedCount}</td></tr>
<tr><td>Skipped</td><td>${data.skippedCount}</td></tr>
<tr><td>Errors</td><td>${data.errorCount}</td></tr>
</table>
`,
});
}

Example: send a completion summary from run state

Section titled “Example: send a completion summary from run state”

If earlier steps collect totals or summary data in api.runStore, you can read that data in onWorkflowComplete(api) and send a single completion email after the entire run finishes.

export class Workflow {
async start(data, headers, api) {
for (const row of data.rows) {
await api.scheduleNextStep({
delay: 10,
action: 'processRow',
payload: { row },
});
}
}
async processRow(data, headers, api) {
if (data.row.status === 'updated') {
await api.runStore.increment('updatedCount');
} else if (data.row.status === 'skipped') {
await api.runStore.increment('skippedCount');
} else if (data.row.status === 'error') {
await api.runStore.increment('errorCount');
}
}
async onWorkflowComplete(api) {
const updatedCount = (await api.runStore.get('updatedCount')) ?? 0;
const skippedCount = (await api.runStore.get('skippedCount')) ?? 0;
const errorCount = (await api.runStore.get('errorCount')) ?? 0;
await api.sendEmail({
subject: 'Inventory sync complete',
text: `Updated: ${updatedCount}. Skipped: ${skippedCount}. Errors: ${errorCount}.`,
});
}
}
async notifySkippedRows(data, headers, api) {
if (!data.skippedCount) return;
await api.sendEmail({
subject: `Inventory sync skipped ${data.skippedCount} records`,
text: `The workflow skipped ${data.skippedCount} records and may need review.`,
});
}

api.sendEmail() returns { sent: false, reason } rather than throwing when the email cannot be sent due to a predictable condition. This keeps the workflow running. Check the result if you need to react or log:

async notifyTeam(data, headers, api) {
const result = await api.sendEmail({
subject: 'Order flagged for review',
text: `Order ${data.orderId} has been flagged.`,
});
if (!result.sent) {
console.log('Email skipped:', result.reason);
}
}

Conditions that return { sent: false } (workflow continues):

  • No notification email set. Configure one in Settings.
  • Free plan. Upgrade to a paid plan to use this feature.
  • Quota reached. The sending limit for your plan has been exhausted. It resets at the start of your next billing cycle.

Conditions that throw (workflow fails):

  • The platform email service is not configured.
  • The email delivery service returns an error.

Current email usage for your billing cycle is visible in Settings under the notification email field. The quota resets at the start of each billing cycle, not on the calendar month. Upgrading your plan starts a fresh quota window immediately.