---
title: "Send transactional email from your Ample app"
description: "Enable a verified app sender, preview HTML and text, and send transactional email from your server with Ample."
canonical_url: "https://ample.computer/guides/send-app-email"
last_updated: "2026-07-30"
author: "Ample Computer"
---

# Send transactional email from your Ample app

Enable a verified app sender, preview HTML and text, and send transactional email from your server with Ample.

## The short version

An active paid Ample subscription includes 100 recipient attempts per UTC calendar month, shared across its apps. Send from your app’s own ample.run domain, render templates in your code, and inspect delivery through the CLI or MCP. CLI commands require version 0.1.39 or later.

## Who this is for

For builders adding welcome messages, receipts, account notifications or other requested transactional mail to a server-backed Ample app. Manage your plan at https://ample.computer/billing.

## How to do it

1. **Choose your public app name first.** Sign in at ample.computer, open View my apps, and use Edit link to claim an available readable address such as bookclub.ample.run. Do this before enabling email: the sender domain is the current public app hostname, not the project name passed to deploy. A sender such as notifications@bookclub.ample.run stays on that domain if you later rename the app.

2. **Enable your app sender.** Start with a deployed public server-backed app and an active paid subscription for live sending. Choose hello, notifications, receipts or another sender prefix. For bookclub.ample.run, notifications becomes notifications@bookclub.ample.run. Ample configures DNS and verifies the domain automatically. These are sending addresses, not inboxes; set reply-to to an existing inbox. Enabling sends no email.

   ```sh
   ample email enable --app <deployment-id> --sender hello --display-name "My App" --reply-to owner@example.com
   ample email status --app <deployment-id>
   ```

3. **Design the body in your app.** Ask your agent to create reusable HTML and plain-text render functions matching your app. Escape user data, use email-friendly tables and inline styles, and include a readable text alternative. Templates remain in your repository, not a hosted template store.

4. **Preview before sending.** Preview captures a test without sending or using the live allowance. Its private link expires after 15 minutes; anyone with the link can view it, so never share previews containing login codes. The browser preview blocks scripts and external resources and cannot guarantee every mail client’s rendering. Free accounts can preview.

   ```sh
   ample email preview --app <deployment-id> --to owner@example.com --subject Welcome --text "Welcome!" --html "<h1>Welcome!</h1>" --idempotency-key welcome-preview-1
   ```

5. **Deploy to bind server-side credentials.** Enabling does not restart your app. Deploy your integration (use --force when the source is unchanged) to inject AMPLE_EMAIL_URL, AMPLE_EMAIL_TOKEN and AMPLE_EMAIL_FROM at runtime. Never put the token into browser code or NEXT_PUBLIC_/VITE_ variables. Status shows verification and whether the live release has the current credential.

   ```sh
   ample deploy --force
   ample email status --app <deployment-id>
   ```

6. **Send from your server.** POST JSON to AMPLE_EMAIL_URL with Authorization: Bearer AMPLE_EMAIL_TOKEN. Supply to (an address array), subject, text, optional html and reply_to, and idempotency_key. from defaults to the configured address. Set test:true to capture rather than send. Send live email only when requested by the recipient or app workflow.

   ```js
   const response = await fetch(process.env.AMPLE_EMAIL_URL, {
     method: 'POST',
     headers: { Authorization: `Bearer ${process.env.AMPLE_EMAIL_TOKEN}`, 'Content-Type': 'application/json' },
     body: JSON.stringify({ to: ['recipient@example.com'], subject: 'Welcome', text: 'Welcome!', html: '<h1>Welcome!</h1>', idempotency_key: 'welcome-user-123' })
   });
   const outcome = await response.json();
   if (!response.ok) throw new Error(outcome.error?.message ?? 'Email request failed');
   if (outcome.status === 'failed') throw new Error(`Email ${outcome.id} was rejected; inspect its record.`);
   if (outcome.status === 'unknown') {
     // Save this ID for inspection. Do not retry with a new idempotency key.
     console.warn('Email outcome uncertain', outcome.id);
   }
   // Persist outcome.id and outcome.status; accepted does not prove inbox delivery.
   ```

7. **Inspect outcomes and retry safely.** Use one stable idempotency key per logical message. Repeating the same payload/key returns the original result; changing the payload conflicts. An accepted result is not proof of inbox placement. For unknown outcomes, inspect the message instead of retrying with a new key. Hard bounces, complaints and unsubscribes suppress the recipient within your account.

   ```sh
   ample email list --app <deployment-id>
   ample email get <message-id>
   ```

8. **Control access and allowance.** Every recipient counts separately toward the account-wide 100/month limit. Overage is blocked, not charged automatically. Clearly rejected requests are refunded; uncertain or partially accepted submissions remain charged. Disable immediately revokes the app credential. Rotation also revokes the old token; redeploy afterward to inject the replacement. Remote MCP exposes the same workflow as ample_email_enable, ample_email_status, ample_email_preview, ample_email_send, ample_email_list and ample_email_get.

   ```sh
   ample email disable --app <deployment-id>
   # Or rotate, then redeploy:
   ample email rotate-token --app <deployment-id>
   ample deploy --force
   ```

## What this supports

- Paid subscriptions for live sending; free test capture
- Verified app-specific ample.run domains
- Server-rendered HTML and plain text; CLI 0.1.39+ and remote MCP

## Know the limits

- 100 recipient attempts per account per UTC month; no automatic paid overages
- Static-only sites need a server backend to protect credentials
- No receiving mailboxes, SMTP credentials, custom sender domains, campaigns or hosted templates
- Inbox placement depends on the receiving mail service; verified authentication and delivered status do not guarantee the inbox

## Common failure modes

- **Paid plan required** Activate a paid subscription at https://ample.computer/billing. Preview remains available.
- **Domain verification pending** Inspect ample email status and wait for DNS verification before sending.
- **Unknown delivery outcome** Read the existing message. Do not retry with a new idempotency key.
- **Credential revoked or not bound** Deploy --force after enable, rotation or sender changes, then inspect status.
- **Email still uses the old app domain** The sender domain is fixed when email is first enabled. Renaming the app afterward, disabling/re-enabling email, or rotating the token does not migrate it. Choose the public name first; contact support for an existing-domain migration.

## Related guides

- [How to deploy a vibe-coded app](https://ample.computer/guides/deploy-vibe-coded-app)
- [How to deploy an app with one command](https://ample.computer/guides/deploy-app-one-command)

## Machine-readable resources

- [llms.txt](https://ample.computer/llms.txt) | [AGENTS.md](https://ample.computer/AGENTS.md) | [sitemap.md](https://ample.computer/sitemap.md) | [OpenAPI](https://ample.computer/openapi.json)
