Wiring your app to a mail API is different from wiring it to most APIs, because a bug you’d normally catch in a log turns into paper in a mailbox. A retry double-sends a run of letters, a malformed record prints wrong and gets delivered, and none of it can be pulled back once it’s in the mail stream. So the integration is worth building defensively from the start, and Direct Mail Manager gives you the pieces: a sandbox that prints nothing, an idempotency key that makes retries safe, and error responses you can act on in code.
Test in a sandbox before it costs postage
Direct Mail Manager runs a sandbox at sandbox.directmailmanager.com/api, alongside production at api.directmailmanager.com/api. It behaves like production except nothing prints, nothing mails, and nothing is charged, so you can run the whole integration against it first. Keys are tied to one environment, and that’s where the most common early error comes from: a production key pointed at the sandbox URL returns 401, and so does a sandbox key pointed at production. A 401 reads like a broken key, but it’s usually the base URL, so check that before anything else.
Make retries safe with an idempotency key
A request that times out has usually already arrived, so when your job runner retries it, the second request is a second mailing. The fix is an Idempotency-Key header, which Direct Mail Manager accepts on every write: POST, PUT, PATCH, and DELETE. Send the same key twice and the second request returns the first one’s result instead of creating a second mailing.
The response tells you which happened. Every idempotent response carries an Idempotency-Status header, set to Original when the request did the work or Repeated when it matched an earlier key. Both mean success, so logging that header is how you tell a real send from a replayed one, and how you notice your runner is retrying more than you thought.
The key has to come from something stable in your own system, an order ID or a job ID with a batch number, so it survives a process restart and stays the same across retries of the same send. A fresh UUID generated at call time is the common mistake, because it’s different on every retry, so every retry mails.
Handle the errors the API returns
When a request fails rather than duplicates, the status code tells you what to do about it.
- 401 and 403 are an authorization problem with the key or the account, and the sandbox-versus-production mismatch above is the first thing to rule out.
- 413 is a payload too large.
- 422 means the body or query parameters didn’t pass validation, which in a running integration usually means the data changed shape upstream, so this is the one to surface to a person.
- 429 means too many requests in a given window, and the published ceilings are 60 API calls a minute on Starter and 300 on Pro, so a bulk job is worth pacing.
- 500 is a server error, and it’s the one case where retrying the same request under the same idempotency key is the right move.
Every error response carries a readable message, the status code, and a link into the documentation for that specific issue, so the error is something your code can branch on and something a person can read straight from a log.
Cancel a mistake before it prints
Some mistakes get through anyway, a send that shouldn’t have gone at all rather than one that duplicated. While a piece is accepted and not yet produced, you can pull it with POST /postcards/{psc_id}/cancel or POST /letters/{ltr_id}/cancel. Past production it’s in the mail stream and gone, which is the whole reason to catch it in the sandbox first.
The test that proves it
A safe integration isn’t a single successful send. It’s sending the same request twice under one idempotency key in the sandbox and confirming the second response comes back Repeated with no second record, then forcing an error and confirming your code reads the status. Once both hold, production is the same code pointed at a different URL and key.