> ## Documentation Index
> Fetch the complete documentation index at: https://ahasend.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> AhaSend stores all message data in the EU. See /facts.
> The API base URL is https://api.ahasend.com. Send the API key in the Authorization: Bearer header. See /api-reference/authentication.
> Use send.ahasend.com for SMTP. Ports 25, 587 and 2525 use STARTTLS. Port 465 is not supported. See /smtp.
> For send-only integrations, create an API Key v2 under Credentials → Add and scope it to messages:send:{your-domain}. See /send-api/credentials and /api-reference/scopes.

# ahasend messages: Send and Track Email

> Send email from your terminal with the AhaSend CLI: text, HTML and AMP content, stored templates, attachments, scheduling, sandbox mode and batch sends.

AhaSend CLI `messages` commands let you send, list, inspect and cancel email messages; this page covers their arguments and flags.

Before running the examples, [log in with a full API key](https://ahasend.com/docs/cli/authentication.md), use your verified sender domain, and replace `YOUR_RESOURCE_ID` with a template ID or message ID from your test account, as each example needs. Create any named content, recipient, attachment, or JSON files first. Shell examples use Bash; examples that read JSON with `jq` need jq installed. Sends use sandbox mode.

## Overview

```text theme={null}
ahasend messages [command] [flags]
```

Available subcommands:

* `send` - Send an email message
* `list` - List messages
* `get` - Get one message
* `cancel` - Cancel a scheduled message

## Send Messages

```text theme={null}
ahasend messages send [flags]
```

A send needs `--from`, `--subject`, a recipient (`--to` or `--recipients`) and text or HTML content. A send with `--template-id` needs only a recipient, because the template supplies the rest; see [Stored Templates](#stored-templates). When a value is missing, the command can ask for it; see [prompts](https://ahasend.com/docs/cli/configuration.md#prompts). The command checks every flag and file before it sends anything, so invalid input sends nothing.

### Basic Usage

```bash theme={null}
# Text email
ahasend messages send --sandbox \
  --from sender@example.com \
  --to recipient@example.com \
  --subject "Hello" \
  --text "Hello World"

# HTML email
ahasend messages send --sandbox \
  --from sender@example.com \
  --to recipient@example.com \
  --subject "Welcome" \
  --html "<h1>Welcome!</h1><p>Thank you for signing up.</p>"

# Multipart email with HTML and text
ahasend messages send --sandbox \
  --from sender@example.com \
  --to recipient@example.com \
  --subject "Welcome" \
  --html "<h1>Welcome!</h1>" \
  --text "Welcome!"
```

### Sender and Reply-To

`--from-name` sets the sender's display name and needs `--from`. `--reply-to` sets the address that replies go to. You cannot also set `Reply-To` with `--header`.

```bash theme={null}
ahasend messages send --sandbox \
  --from support@example.com \
  --from-name "Example Support" \
  --reply-to help@example.com \
  --to recipient@example.com \
  --subject "Your ticket" \
  --text "We received your request."
```

### Recipients

Use `--to` for a simple list of recipients. You can use it multiple times, and each value is split on commas. Use `--recipients` for a CSV or JSON file that gives each recipient a name and substitutions. You cannot combine `--to` and `--recipients`.

```bash theme={null}
# Several recipients
ahasend messages send --sandbox \
  --from sender@example.com \
  --to user1@example.com \
  --to user2@example.com \
  --subject "Team Update" \
  --text "Important announcement"

# Recipients file with per-recipient substitutions
ahasend messages send --sandbox \
  --from sender@example.com \
  --recipients users.csv \
  --subject "Hello {{first_name}}" \
  --html-file newsletter.html
```

The [message content guide](https://ahasend.com/docs/cli/templates.md#recipient-substitutions) describes the recipients file formats. A send to more than 100 recipients is split into several requests; see [batch processing](https://ahasend.com/docs/cli/batch-processing.md).

### Content

Give each part of the content inline or from a file, not both:

| Part | Inline | From a file |
| - | - | - |
| Plain text | `--text` | `--text-file` |
| HTML | `--html` | `--html-file` |
| AMP HTML | `--amp` | `--amp-file` |

Text or HTML content is required. AMP content is an extra part that goes with one of them. Several parts together make a multipart email.

```bash theme={null}
ahasend messages send --sandbox \
  --from sender@example.com \
  --to recipient@example.com \
  --subject "Welcome {{first_name}}" \
  --html-file welcome.html \
  --text-file welcome.txt \
  --global-substitutions data.json
```

The subject and the content can hold placeholders such as `{{first_name}}`, which the API fills from `--global-substitutions` and the recipients file. See the [message content guide](https://ahasend.com/docs/cli/templates.md).

### Stored Templates

`--template-id` sends a transactional template stored in your AhaSend account. Manage stored templates with the [`ahasend templates` commands](https://ahasend.com/docs/cli/commands/templates).

```bash theme={null}
ahasend messages send --sandbox \
  --template-id YOUR_RESOURCE_ID \
  --recipients users.csv \
  --global-substitutions defaults.json
```

* The template supplies the content, so you cannot use `--text`, `--html`, `--amp`, `--text-file`, `--html-file` or `--amp-file` with it.
* `--from` and `--subject` are optional. Without them, the template's sender and subject apply.
* The values of `--global-substitutions` are copied into each recipient's substitutions. A recipient's own value for the same key wins.

### Attachments

`--attach` attaches a regular file, and you can use it multiple times. Each file can be at most 10,485,760 bytes (10 MiB), and the CLI detects its MIME type. Every request carries every attachment. Together, base64-encoded, the attachments can take at most 29 MB of a request; a larger set fails before anything is sent.

```bash theme={null}
ahasend messages send --sandbox \
  --from sender@example.com \
  --to recipient@example.com \
  --subject "Invoice" \
  --text "Please find your invoice attached." \
  --attach invoice.pdf
```

### Scheduling

`--schedule` takes an RFC 3339 time in UTC, in the form `YYYY-MM-DDTHH:MM:SSZ`, or a relative time from now, such as `30m` or `2h`. The API takes a future time within 7 days. A time in the past or later than that fails before anything is sent.

```bash theme={null}
ahasend messages send --sandbox \
  --from sender@example.com \
  --to recipient@example.com \
  --subject "Reminder" \
  --text "Meeting tomorrow at 2 PM" \
  --schedule 2h
```

### Headers, Tags and Tracking

* `--header` adds a custom header in `Name: value` format. You can use it multiple times, with each name once, in any case. Put a value that holds a comma in double quotes, such as `--header '"Name: a, b"'`. The command refuses `From`, `Sender`, `To`, `Cc`, `Bcc`, `Subject`, `Message-ID` and the headers AhaSend sets, such as `AhaSend-Sandbox`; use the matching flag instead, such as `--from`, `--sandbox` or `--tag`.
* `--tag` adds a message tag. You can use it multiple times.
* `--track-opens` and `--track-clicks` turn tracking on; `--track-opens=false` and `--track-clicks=false` turn it off. Without the flags, your account's tracking setting applies.

```bash theme={null}
ahasend messages send --sandbox \
  --from sender@example.com \
  --to recipient@example.com \
  --subject "Important Update" \
  --html-file update.html \
  --header "X-Campaign-ID: summer" \
  --tag onboarding \
  --track-clicks=false
```

### Sandbox Mode

`--sandbox` sends in sandbox mode, which delivers nothing. `--sandbox-result` picks the simulated outcome: `deliver` (the default), `bounce`, `defer`, `fail` or `suppress`. It needs `--sandbox`. See [sandbox mode](https://ahasend.com/docs/send-api/sandbox.md).

```bash theme={null}
ahasend messages send --sandbox --sandbox-result bounce \
  --from sender@example.com \
  --to test@example.com \
  --subject "Test" \
  --text "Testing"
```

### Idempotency

`--idempotency-key` makes a retry safe: the API does not act twice on the same request with the same key. Without the flag, the command generates a key. The API keeps a send's result for its key for 24 hours. Reuse a key only to retry the identical request; see [idempotency](https://ahasend.com/docs/api-reference/idempotency.md).

```bash theme={null}
ahasend messages send --sandbox \
  --from sender@example.com \
  --to user@example.com \
  --subject "Important" \
  --text "Message" \
  --idempotency-key "order-1234-confirmation"
```

### Output and Exit Codes

A send of up to 100 recipients makes one request. Plain output starts with a heading, `Message sent successfully`, `Message sent to N of M recipients` or `Message not sent to any recipient`, then lists each message:

```text theme={null}
Message sent to 1 of 2 recipients

Message 1:
  Recipient: u@example.com
  ID: <0d371db4-00d0-5eec-851c-e1db1e2b190d@example.com>
  Status: queued

Message 2:
  Recipient: rej@example.com
  Status: error
  Error: failed to send message: rej@example.com: The message could not be queued because of an internal error. Please try again later or contact support.
```

With `--output json`, the command prints the API's response body as [JSON output](https://ahasend.com/docs/cli/configuration.md#json-output) describes. Each message's `id` is its Message-ID, which `messages get` and `messages cancel` take. Notices, such as where the command saved failed recipients, go to stderr.

The API can accept a request but refuse some recipients in it, when it could not queue their message. Those recipients count as failed. The command saves failed recipients to a file that you can send again; see [failed recipients](https://ahasend.com/docs/cli/batch-processing.md#failed-recipients).

The API does not refuse a suppressed address. It queues the message, does not deliver it, and gives the message the status `suppressed`, which `messages list --status suppressed` finds. AhaSend also sends a [`message.suppressed` event](https://ahasend.com/docs/api-reference/webhooks/message-suppressed) to your webhooks.

A send exits with the [shared exit codes](https://ahasend.com/docs/cli/configuration.md#exit-codes). [Exit codes for sends](https://ahasend.com/docs/cli/batch-processing.md#exit-codes) gives the codes that report its result.

## List Messages

```text theme={null}
ahasend messages list [flags]
```

```bash theme={null}
# List recent messages
ahasend messages list

# Messages from the last 24 hours that bounced or failed
ahasend messages list --from-time 24h --status bounced --status failed

# Messages to every address at one domain
ahasend messages list --recipient @example.com

# Messages with a tag
ahasend messages list --tag onboarding --limit 50
```

* `--sender` matches the sender address exactly. `--recipient` and `--subject` match every message whose recipient or subject contains the value, case-sensitively, so `--recipient user@example.com` also matches `olduser@example.com`.
* `--status` can be used multiple times. Valid statuses are `received`, `delivered`, `opened`, `clicked`, `deferred`, `bounced`, `failed`, `suppressed`, `sandbox delivered`, `sandbox deferred`, `sandbox failed`, `sandbox bounced` and `sandbox suppressed`. `delivered` also matches opened and clicked messages, and `opened` also matches clicked ones.
* `--tag` takes at most 20 tags, none with a comma.
* `--from-time` and `--to-time` take an RFC 3339 time or a relative time that counts back from now, such as `1h`, `24h` or `7d`.
* `--limit`, `--after` and `--before` page through the results as [lists and pagination](https://ahasend.com/docs/cli/configuration.md#lists-and-pagination) describes. With `--output json`, read the next cursor from `pagination.next_cursor`.
* `--show-details` adds more fields for each message, such as its direction, attempts and bounce class.

## Get a Message

```text theme={null}
ahasend messages get <message-id> [flags]
```

The message ID is the message's UUID or its Message-ID, such as `<550e8400-e29b-41d4-a716-446655440000@example.com>`. Quote a Message-ID in the shell. The output shows the message's status, its delivery attempts, the template it was sent from, and its raw content while your [retention settings](https://ahasend.com/docs/retention.md) keep it.

```bash theme={null}
ahasend messages get "YOUR_RESOURCE_ID"

# Save the message content to a file
ahasend messages get "YOUR_RESOURCE_ID" --output json | jq -r .content > message.txt
```

## Cancel Messages

```text theme={null}
ahasend messages cancel <message-id>... [flags]
```

Cancel stops a message that still waits in the delivery queue, such as one scheduled for later. It takes one or more UUIDs or Message-IDs. The API answers success for any message it finds, including one already delivered, which the cancel leaves as it is; check a message's status with `messages get`.

The command asks for confirmation first; see [prompts](https://ahasend.com/docs/cli/configuration.md#prompts).

```bash theme={null}
# Schedule a sandbox message, then cancel it by its Message-ID
TEST_ID=$(ahasend messages send --sandbox \
  --from sender@example.com \
  --to test@example.com \
  --subject "Cancellation test" \
  --text "Test" \
  --schedule 1h \
  --output json | jq -er '.data[0].id')

# Wait for the API to record the message
sleep 10
ahasend messages cancel "$TEST_ID" --force
```

A message the API does not find fails with exit code 5. The API records a message from its delivery events, so a message you just sent can take a few seconds to appear. Until it does, `messages get` and `messages cancel` cannot find it and exit with code 5; try again shortly.

With several IDs, the command cancels each in turn and prints one result per ID; with `--output json`, it prints a JSON array with the API's response or error body for each ID, or the CLI's [error object](https://ahasend.com/docs/cli/configuration.md#json-output) for an ID whose request got no answer. Any failure then exits with code 1. Ctrl+C stops before the next cancel and exits with code 130.

## Advanced Usage

* [Batch Processing Guide](https://ahasend.com/docs/cli/batch-processing.md) - Send to many recipients from a CSV or JSON file
* [Message Content Guide](https://ahasend.com/docs/cli/templates.md) - Content files, variables, conditions and loops
* [Stored Templates](https://ahasend.com/docs/cli/commands/templates.md) - Manage the transactional templates that `--template-id` sends
* [Examples](https://ahasend.com/docs/cli/examples.md) - Real-world use cases

## Command Flags

These flags come from the CLI's `--help`. All commands also accept the [global flags](https://ahasend.com/docs/cli/configuration.md#global-flags).

### `messages cancel`

| Flag | Description and default |
| - | - |
| `-f`, `--force` | Skip confirmation prompt |

### `messages get`

This command has no command-specific flags. See [global flags](https://ahasend.com/docs/cli/configuration.md#global-flags).

### `messages list`

| Flag | Description and default |
| - | - |
| `--after string` | Return the page after this cursor |
| `--before string` | Return the page before this cursor |
| `--from-time string` | Filter messages created after this time (RFC3339 or relative like '24h', '7d') |
| `--limit int32` | Maximum number of items to return (0-100; 0 uses the API default) |
| `--message-id string` | Filter by message ID header |
| `--recipient string` | Filter by recipient: addresses that contain the value, such as @example.com |
| `--sender string` | Filter by sender email address (must be from your domain) |
| `--show-details` | Show detailed message information |
| `--status strings` | Filter by message status: `received`, `delivered`, `opened`, `clicked`, `deferred`, `bounced`, `failed`, `suppressed`, `sandbox delivered`, `sandbox deferred`, `sandbox failed`, `sandbox bounced` or `sandbox suppressed` (can be used multiple times) |
| `--subject string` | Filter by subject text (matches subjects that contain it) |
| `--tag strings` | Filter by tag, at most 20, none with a comma (can be used multiple times) |
| `--to-time string` | Filter messages created before this time (RFC3339 or relative) |

### `messages send`

| Flag | Description and default |
| - | - |
| `--amp string` | AMP HTML content |
| `--amp-file string` | File with the AMP HTML content |
| `--attach strings` | File to attach, at most 10,485,760 bytes (10 MiB) (can be used multiple times) |
| `--from string` | Sender email address (required unless `--template-id` is set) |
| `--from-name string` | Sender display name (needs `--from`) |
| `--global-substitutions string` | JSON file with global template variables |
| `--header strings` | Custom header in 'Name: value' format, each name once in any case, in double quotes when the value holds a comma: `'"Name: a, b"'`; not `From`, `Sender`, `To`, `Cc`, `Bcc`, `Subject`, `Message-ID` or a header AhaSend sets, such as `AhaSend-Sandbox` (can be used multiple times) |
| `--html string` | HTML content |
| `--html-file string` | File with the HTML content |
| `--idempotency-key string` | Idempotency key for safe retries (generated when omitted) |
| `--max-concurrency int` | Maximum concurrent sends for batch operations (1-10) (default 1) |
| `--max-retries int` | Times to send a failed request again (0 or more) (default 3) |
| `--non-interactive` | Never prompt; fail when a required flag is missing |
| `--progress` | Show progress bar for batch operations (disabled in debug mode) |
| `--recipients string` | Recipients file (JSON or CSV format) with per-recipient substitutions |
| `--reply-to string` | Reply-to email address |
| `--sandbox` | Send in sandbox mode, which delivers nothing |
| `--sandbox-result string` | Sandbox result simulation: deliver, bounce, defer, fail or suppress (needs `--sandbox`) (default "deliver") |
| `--schedule string` | Schedule delivery for a time in RFC3339 format or relative like '30m', '2h' (the API takes a future time within 7 days) |
| `--show-metrics` | Show performance metrics on stderr after the send |
| `--subject string` | Email subject (required unless `--template-id` is set) |
| `--tag strings` | Message tag (can be used multiple times) |
| `--template-id string` | Send this transactional template; it supplies the content |
| `--text string` | Plain text content |
| `--text-file string` | File with the plain text content |
| `--to strings` | Recipient email address (can be used multiple times) |
| `--track-clicks` | Track clicks (`--track-clicks=false` turns it off); the account's setting applies when omitted |
| `--track-opens` | Track opens (`--track-opens=false` turns it off); the account's setting applies when omitted |

## Next Steps

* [Learn about domains](https://ahasend.com/docs/cli/commands/domains.md)
* [Configure webhooks](https://ahasend.com/docs/cli/commands/webhooks.md)
* [View statistics](https://ahasend.com/docs/cli/commands/stats.md)

## Related Guides

* [Install the CLI](https://ahasend.com/docs/cli/installation.md), [CLI authentication](https://ahasend.com/docs/cli/authentication.md), [Sending guide](https://ahasend.com/docs/send-api/send-email.md).
* [SMTP settings](https://ahasend.com/docs/smtp.md), [Node.js SDK](https://ahasend.com/docs/guides/nodejs-sdk.md), [Go SDK](https://ahasend.com/docs/guides/go-sdk.md).
* [CLI configuration and output](https://ahasend.com/docs/cli/configuration.md), [API authentication](https://ahasend.com/docs/api-reference/authentication.md), [Permission scopes](https://ahasend.com/docs/api-reference/scopes.md).
* [Domain setup](https://ahasend.com/docs/domains.md), [Sandbox behavior](https://ahasend.com/docs/send-api/sandbox.md), [Tracking setup](https://ahasend.com/docs/tracking/open-tracking.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.