Pony doesn't schedule letters for you. Your own cron job or agent decides when to send and calls the API. This guide shows how to send a prepared letter from a scheduler so that crashes, timeouts and overlapping runs never mail it twice.
cron_send_example.py is a complete, runnable example. It uses only Python 3's standard library and runs on macOS and Linux.
What the script does#
The script sends one letter that you've already prepared. It doesn't write letters, add credits or change permissions.
Each run:
- Takes a lock on the work folder, so two runs can't overlap.
- Checks that
PONY_ENVIRONMENTmatches the server's catalog, before sending any credential. - Looks up whether this send already happened.
- Sends the request only if Pony has no record of it.
- Saves the result as
order-result.json.
Because it always looks before it sends, you can run it as often as you like. A lost response, or a crash right after sending, is recovered on the next run.
Before you begin#
Follow the quickstart through step 5, then build the send body from step 6 without sending it. Save two files in a private work folder:
| File | Contents |
|---|---|
send-request.json |
The send body from step 6. grant_id is optional. |
send.key |
The idempotency key for this send, such as invoice-1042:send |
mkdir -p ~/pony-work/invoice-1042 && chmod 700 ~/pony-work/invoice-1042
jq '{send_intent_id,quote_id,proof_id,expected_total_minor:.total_minor,currency}' quote-result.json \
> ~/pony-work/invoice-1042/send-request.json
echo 'invoice-1042:send' > ~/pony-work/invoice-1042/send.keyRun the script#
export PONY_API_BASE=https://www.mailbypony.com/api/v1
export PONY_ACCESS_TOKEN_FILE=~/.config/pony/api-key
export PONY_ENVIRONMENT=live
python3 cron_send_example.py ~/pony-work/invoice-1042| Variable | Meaning |
|---|---|
PONY_API_BASE |
The API base URL, ending in /api/v1 |
PONY_ACCESS_TOKEN_FILE |
A file containing only your API key, readable only by you (chmod 600) |
PONY_ENVIRONMENT |
live. The script stops if the server reports a different environment. |
The script reads the key file on every request, so you can rotate the key without restarting anything. It never follows redirects and always verifies TLS.
Exit codes#
| Code | Meaning | What your scheduler should do |
|---|---|---|
0 |
The letter was accepted, now or on an earlier run | Nothing. Check the order separately for mailing status. |
2 |
Something needs a person: a configuration problem or a rejected send | Alert someone. Don't retry automatically. |
75 |
Temporary: busy, rate-limited, still processing or no response | Run again later |
0 means Pony accepted the letter, not that it has been mailed. Use webhooks or GET /orders/{order_id} to follow it.
Schedule it#
Call the script from a small wrapper, and schedule the wrapper:
# Retries the same prepared letter every 5 minutes until it's accepted.
# It doesn't create a new letter each time.
*/5 * * * * /absolute/path/to/run-pony-letter.shIn production, add backoff with some random jitter, and alert on exit code 2.
Sending the next letter#
Give each letter its own work folder, with a new client_reference, quote and idempotency key. Don't reuse a folder by deleting its files: if a previous send's outcome is still unknown, a new key could mail the letter twice.
Files in the work folder#
Don't edit or delete these while a send might be in progress:
| File | Written by | Purpose |
|---|---|---|
send-request.json |
You | The exact request body |
send.key |
You | The idempotency key |
attempt.json |
The script | Recorded before sending, so a crash can be recovered |
order-result.json |
The script | The accepted order |
worker.lock |
The script | Prevents overlapping runs |
If order-result.json is missing, that doesn't mean the send failed. Run the script again; it will look up the original request.
Keeping your key safe#
- Store the key in a secret manager or a file only you can read.
- Never put the key in a cron line, a URL or a source file.
- An assistant's OAuth connection (such as ChatGPT's) can't be used from cron. Create an API key.