ahasend messages send and check the results.
Before running the examples, log in with a full API key and use your verified sender domain. Create the recipient, content and JSON files that the examples name first. Shell examples use Bash. Sends use sandbox mode.
How a Batch Is Sent
A batch is amessages send with a recipients file. The command reads and checks the whole file first, so a bad address sends nothing. It then sends the recipients in requests of up to 100 each:
- A send of up to 100 recipients makes one request, with your
--idempotency-keyas given. - A larger send makes one request per 100 recipients. Each request gets the key followed by
-and its number, from 0, such asmy-key-0andmy-key-1. Without--idempotency-key, the command generates the key. - Every request carries the same content, attachments and options.
Recipient File Formats
The file’s extension,.csv or .json, sets its format:
- CSV Format
- JSON Format
recipients.csv
Basic Batch Send
Performance Options
Concurrency Control
--max-concurrency sets how many requests are in flight at once, from 1 (the default) to 10. Each request holds up to 100 recipients, so the flag matters only for a send of more than 100 recipients.
Progress Bar
--progress draws a progress bar on stderr. It shows only when stderr is an interactive terminal and TERM is not dumb, and never with --debug. Notices and --verbose logs printed during the send appear above the bar.
Metrics
--show-metrics prints performance metrics on stderr after the send, in every output format, so JSON output on stdout stays the API’s responses alone:
Failed and Unknown outcome appear only when they are not zero. Performance is the number of recipients with a result, sent or not, divided by the duration. These metrics come from the send in Results and Output, where retries of one request after HTTP 429 add several seconds.
Results and Output
When the send makes several requests, table and plain output start with a heading,Message sent successfully, Message sent to N of M recipients or Message not sent to any recipient, then count the recipients that succeeded and failed, and add Unknown: N when the outcome of some is unknown. One result follows per request. A request that the API accepted with some recipients refused has the outcome partial and lists each refused recipient as email: reason:
--output table; the second request still got HTTP 429 after its retries. Plain output lists the same results as Result 1:, Result 2: and so on. CSV output has no heading or counts, only one row per request with the columns item,outcome,error. When any recipient failed, table, plain and CSV output add a line such as 101 of 250 recipients failed to stderr.
With --output json, the command prints a JSON array with one element per request, in order: the API’s response body for a request that succeeded, its error body for one the API refused, and the CLI’s error object for one that got no answer, such as after a network error or a timeout. A send of one request prints the API’s response body alone; see messages send output.
Error Handling
Automatic Retries
--max-retries sets how many times a failed request is sent again, 0 or more. Each request has its own count: with the default of 3, each request goes out at most 4 times. Only these failures are retried:
- HTTP 429
- HTTP 5xx
- A network error, except a failed TLS certificate check
- HTTP 409, when the API is still processing an earlier request with the same idempotency key
Retry-After the API gives, or else one second more per attempt, up to 30 seconds. A wait of more than 5 seconds is announced on stderr. A request that the API asks to retry after more than 5 minutes is not retried. A retry keeps the request’s idempotency key, so the API never sends a request twice.
Failed Recipients
When any recipient fails or is not sent, the command saves those recipients to a new file in~/.ahasend/, named failed-YYYYMMDD-HHMMSS-<random>.json. It prints the path on stderr in every output format, before the results:
_error (the error), _retryable (whether the error was of a kind the command retries, such as HTTP 429 or a network error; it is false for each recipient the API refused) and, when the API answered with an HTTP error status, _error_code. --recipients reads these keys, so you can pass the file back as it is. When _retryable is false, read _error and fix its cause before you send again.
--idempotency-key: the API refuses a key with a different request.
A send with no failed recipient writes no file. When the API refuses a request with attachments as too large (HTTP 413), the command sends no more requests, because they carry the same attachments; their recipients go to the file as not sent.
Unknown Outcome
When the last try of a request leaves it possible that the API took it, the command cannot tell whether that request’s recipients were sent. This happens when:- the API is still processing an earlier request with the same idempotency key (HTTP 409)
- a gateway answered HTTP 502 or 504
- the request was written in full but no answer came
- the API accepted the request with an answer the CLI cannot read
~/.ahasend/unknown-YYYYMMDD-HHMMSS-<random>.json, which adds _idempotency_key to each one, and names the idempotency key on stderr:
- Check the recipients with
ahasend messages list, such as with--recipientand--from-time. - Or run the same command again, with the same recipients file and the
--idempotency-keythat stderr names, within 24 hours. The API answers each request it already took with its stored result instead of sending it again.
--schedule, such as 2h, stands for a different time on each run. After such a send, stderr also gives the time it stood for; pass that time as --schedule when you run the command again.
The CLI never deletes either file. They hold the address, name and substitutions of each recipient, which can be personal data, so delete a file once you no longer need it.
Interrupting a Send
Ctrl+C, or SIGTERM, stops the send: no new request or retry starts, and the requests in flight finish. The command prints the results of the requests it made and exits with code 130. The recipients of each request that never started go to the failed recipients file with the errornot sent: the send was interrupted. A request that was waiting to retry keeps the error of its last try: its recipients count as failed, or as unknown when that error leaves the outcome unknown. Press Ctrl+C again to stop at once.
Exit Codes
Everymessages send, of one request or several, exits with the shared exit codes, and reports its result with these:
Invalid input, such as a bad address in the recipients file, sends nothing and exits with code 4. A send of one request that fails exits with the code of its error, such as 5 for HTTP 404 or 7 when the API could not be reached. A send of several requests exits with code 1 when any request fails, whatever its HTTP status.
Template Personalization
Combine values for every recipient with values per recipient. A recipient’s own value wins over the global value of the same key.global-data.json
--template-id sends a stored transactional template to a recipients file in the same way; see the ahasend templates commands.
Best Practices
1. Test with Small Batches First
2. Use Your Own Idempotency Key in Scripts
With your own key, a script that runs the same send twice does not send it twice: for 24 hours, the API answers a repeated request with its stored result. This holds only when the request is identical. When the content, the recipients file or another option changes, or a relative--schedule such as 2h stands for a different time, the API refuses the key with HTTP 422, and a send of one request exits with code 4.
3. Monitor Performance
Troubleshooting
Slow sends: Raise--max-concurrency, up to 10. It helps only for sends of more than 100 recipients.
High failure rate: Read the errors in the results and the _error of each recipient in the failed recipients file, and verify your sender domains. A suppressed address does not fail; messages send output says where it shows.
Unknown outcomes: Check the recipients with ahasend messages list before you send them again, as Unknown Outcome describes.

