Mail by PonyDocs

Guides

Send from a scheduler

View as Markdown

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:

  1. Takes a lock on the work folder, so two runs can't overlap.
  2. Checks that PONY_ENVIRONMENT matches the server's catalog, before sending any credential.
  3. Looks up whether this send already happened.
  4. Sends the request only if Pony has no record of it.
  5. 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
Shell
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.key

Run the script#

Shell
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:

cron
# 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.sh

In 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.