# Workflow Notification Emails

Send internal workflow notifications to your configured notification address.

`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.

## Requirements

- 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.

| Plan | Quota |
| --- | --- |
| Free | Not available |
| Starter | 500 emails / billing cycle |
| Growth | 2,000 emails / billing cycle |
| Business | 5,000 emails / billing cycle |
| Enterprise | 10,000 emails / billing cycle |

## Signature

```js
await api.sendEmail({ subject, text, html });
```

| Option | Type | Required | Description |
| --- | --- | --- | --- |
| `subject` | `string` | yes | Email subject line |
| `text` | `string` | no | Plain-text body |
| `html` | `string` | no | HTML 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 }>`.

| Result | When |
| --- | --- |
| `{ 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.

## Common use cases

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

```js
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.`,
    });
  }
}
```

## Example: send a completion summary

```js
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

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.

```js
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}.`,
    });
  }
}
```

## Example: notify when records were skipped

```js
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.`,
  });
}
```

## Checking the result

`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:

```js
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.

## Usage and quota

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.

> Note: `api.sendEmail()` always delivers to the single notification address in Settings. It cannot be used to email customers or any external address.