# Email

Apps can send email: an approval request, a "your document was signed" note, a daily digest. fuast handles delivery. You do not need an email provider or API key.

```ts
await ctx.email.send({
  to: "ana@acme.com",
  subject: "Approval needed: New monitor",
  text: "Bo asked for a new monitor ($349).\n\nOpen the app to decide: https://approvals.fuast.com/"
});
```

`ctx.email.send(message)`:

| Field | Type | Notes |
| --- | --- | --- |
| `to` | `string` | One address. Loop for several recipients. |
| `subject` | `string` | Required. |
| `text` | `string` | Plain-text body. |
| `html` | `string` | HTML body. Send `text` as well for clients that prefer it. |

At least one of `text` and `html` is required. The promise resolves to `{ id }` once fuast has accepted the message for delivery, and rejects if the message is invalid or the app is over its daily email limit.

Mail comes from fuast's sending address with the app's name as the sender name. Replies go to the app owner.

## Where it works

`ctx.email.send` works in mutations, actions, endpoints, and schedules. It does not work in queries, which must not have side effects, or in `onGuestUpgrade`.

It does not work in an [unclaimed preview](https://fuast.com/docs/reference.md#deploy-without-an-account) either. An app deployed without an account cannot send mail until a person claims it: the call fails with `Claim this app to use email: ctx.email.send is off in an unclaimed preview.` (code `unclaimed`). After the claim the app is [restricted](https://fuast.com/docs/security.md#restricted-apps) and email stays off (code `restricted`) until its owner trusts it with the workspace.

## Email and transactions

An email is sent when you call `send`. A database rollback does not unsend it. If a mutation sends an email and then throws, the email still goes out and the rows do not change. Send email as the last step, after every check that can fail:

```ts
decide: mutation(async (ctx, id: string, approve: boolean) => {
  const me = ctx.auth.requireSignedIn();
  const request = await ctx.db.requests.get(id);
  if (!request || request.status !== "pending") throw new Error("This request was already decided.");
  await ctx.db.requests.update(id, { status: approve ? "approved" : "rejected", deciderId: me.userId });
  if (request.submitterEmail) {
    await ctx.email.send({
      to: request.submitterEmail,
      subject: `${request.title} was ${approve ? "approved" : "rejected"}`,
      text: `${me.displayName} decided your request.`
    });
  }
})
```

## Who to email

Use addresses you got from a trusted place:

- `ctx.auth.email` for the caller, when `emailVerified` is true.
- `ctx.workspace.members()` for coworkers.
- A stored email from a row that a signed-in user wrote about themselves.

Do not email arbitrary addresses typed into a form by a guest on a public app. That turns your app into a spam relay, and it uses your daily limit.

## Limits

Each app can send 100 emails per UTC day by default. Over the limit, `send` throws an error with code `fuast_quota_exceeded` and `bucket: "email"`. Batch notifications into a digest (see [schedules](https://fuast.com/docs/schedules.md)) instead of one email per event.

## Local development

`fuast dev` does not deliver email. Each message is written to the app log with its recipient, subject, and body, so you can check it:

```sh
fuast logs --port 3000
```
