# Send from a scheduler

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`](/docs/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](/docs/concepts.md) 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](/docs/quickstart.md) 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` |

```bash
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

```bash
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](/docs/webhooks.md) or [`GET /orders/{order_id}`](/docs/api.md) 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](/docs/authentication.md).
