Mail by PonyDocs

Get started

Test mode

View as Markdown

Build and test your integration against the live service without spending money or mailing anything. A pony_test_ key runs every request in a separate test environment with free test credit and simulated order progress.

Authentication, idempotency, errors and scopes work exactly as they do with a live key.

Get a test key#

Create a test key the same way as a live key, while signed in to the website, with "test": true:

JSON
{"label": "Billing integration (test)", "scopes": ["letters:read", "letters:write", "orders:read", "orders:send", "orders:cancel", "wallet:read", "grants:read"], "expires_at": "$expires_at", "test": true}

POST /api/account/api-keys returns the secret once. Test secrets start with pony_test_ and live secrets with pony_live_. GET /api/account/api-keys lists both, each with its environment, and you revoke them the same way.

What a test key does#

A request made with a pony_test_ key runs entirely in the isolated test environment:

  • GET /identity and GET /wallet report "environment": "test".
  • Test records are separate: wallet, credit history, quotes, send intents, sending permissions, orders, drafts, proofs, templates and batches. Live keys, OAuth connections and the website never see them, and test keys never see live records.
  • Saved address references from the website are live records, so test letters use inline recipient and sender addresses.
  • No permission setup is needed. A test key sends through an open test permission with no limits or address restrictions. Live keys still need a sending permission.
  • Final quotes use the configured price with tax_minor: 0 and tax_provenance.type: "test_mode". No tax or payment provider is called.
  • Orders are marked test_mode: true and credit_environment: "test". Nothing is printed or mailed, and test orders never reach the print queue, notifications or analytics.
  • POST /wallet/topups is refused, because test keys can't create real funding links. Use test credit instead.

You don't need real credits to use a test key, so you can finish your integration before you fund your account.

Add test credit#

POST /test/credits adds up to $1,000 (100000 cents) of test credit per call. Call it again for more. It needs a test key with wallet:read and an Idempotency-Key header. Live keys and OAuth tokens get 403.

JSON
{"amount_minor": 50000, "currency": "USD"}

The response contains the ledger transaction (type test_credit) and the test wallet.

Send a test letter#

Use exactly the live flow from the quickstart: create a draft (or render a template), render a proof, get a final quote and send. grant_id can be left out.

HTTP
POST /api/v1/drafts              -> draft_id, version
POST /api/v1/drafts/{id}/proofs  -> proof_id
POST /api/v1/send-quotes         -> send_intent_id, quote_id, total_minor
POST /api/v1/orders              -> order_id, status "queued", environment "test"

Simulate order progress#

POST /test/orders/{order_id}/advance moves a test order forward so you can exercise your status handling and webhooks. It needs a test key with orders:read. With no body it advances one step. With a status, it advances through every step up to that status:

queued -> preparing -> printed -> mailed -> returned
JSON
{"status": "mailed"}

The response has the same shape as GET /orders/{order_id}, including history and tracker. Each simulated step is recorded with the source test_mode_simulation.

Orders only move forward. returned requires mailed first, and cancelled orders can't advance. You can cancel a queued test order with POST /orders/{order_id}/cancel, which returns its test credit.

Going live#

Create a live key on Account → Credits → API keys, fund your balance and swap the key. Nothing else in your code changes. Test records stay in the test environment and aren't copied over.