Concepts

Idempotency

Two tools keep writes safe: the Idempotency-Key header, which makes any write safe to retry, and dry_run, which reports what a write would do without committing it.

The Idempotency-Key header

A network timeout on a write leaves you not knowing whether it landed — without idempotency you must choose between losing the write and doing it twice. The header removes the dilemma: send a fresh unique value (a UUID is ideal) per logical operation, and reuse that same value on every retry of it.

curl -X POST "https://api.mailneo.co/api/v2/subscribers" \
  -H "x-api-key: $MAILNEO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9f6c2e58-52a3-4a0f-9c1d-1b7f37e3c001" \
  -d '{ "email": "ada@example.com" }'

# The same command again — timeout, crash, retry loop — replays the
# original response with Idempotent-Replayed: true, and writes nothing.

How a replay is decided

  • The key is fingerprinted together with the method, path, parameters and body. Same key and same request within 24 hours replays the original response, marked with Idempotent-Replayed: true.
  • Same key with a different request is 409 idempotency_key_reused — that mismatch is a bug in the caller, not something to guess at.
  • Same key while the first attempt is still running is 409 request_in_flight, with Retry-After telling you when to try again. A stalled attempt is reclaimed after 90 seconds.
  • Keys are scoped to your team and may be up to 255 characters.

Where the key is required

On most writes the header is optional — strongly recommended, but your risk to take. Where a repeat is not merely wasteful but harmful, the spec marks it required and a request without it is refused with 400 idempotency_key_required:

POST/contacts/bulka re-run bulk upsert would double-apply every row.
POST/campaigns/{campaignId}/send-previewa retried preview must not mint a second confirmation token.
POST/campaigns/{campaignId}/senda retried confirm must replay, never arm twice.
POST/newsletters/{newsletterId}/send-previewa retried preview must not mint a second confirmation token.
POST/newsletters/{newsletterId}/senda retried confirm must replay, never arm twice.
POST/webhooks/{webhookId}/deliveries/{deliveryId}/replaya retried replay that already ran must not deliver twice.

The header has no effect on GET and DELETE, which are already safe to repeat.

Dry runs with dry_run

Every write endpoint accepts ?dry_run=true: the request is validated and resolved exactly as a real one, but nothing is committed. The response carries data: null and a meta.impact block describing what would have happened.

curl -X DELETE "https://api.mailneo.co/api/v2/lists/$LIST_ID?dry_run=true" \
  -H "x-api-key: $MAILNEO_API_KEY"
{
  "data": null,
  "meta": {
    "request_id": "req_...",
    "impact": {
      "object": "impact",
      "would_create": 0,
      "would_update": 0,
      "would_delete": 1,
      "affected_count": 1,
      "affected_ids": ["..."],
      "quota": null
    }
  }
}
  • would_create / would_update / would_delete — row counts per kind of change; affected_count and affected_ids identify the records involved.
  • quota — when the write would consume plan quota (contact slots, email credits), the kind, the amount, and where the team stands against the limit. null when no quota is involved.
  • dry_run must be exactly true or false, and it requires the resource's :readscope in addition to the endpoint's own — a dry run reveals state, so it needs permission to read it.
The send endpoints are the one exception: send does not accept dry_run, because send-preview is the dry run — and unlike a dry run its answer is enforced, through the confirmation token. See sending.