Skip to content
Docs · API

Batch, rates and errors.

Beyond single send: bulk ingest with partial success, tenant metrics, the uniform error envelope, certified vs plain and online evidence verification.

  1. 01POST /v1/messages/batch

    Batch ingest (≤100)

    One JSON request with up to 100 messages. Each item has its own idempotency_key. The response lists per-index results and created / failed / replayed counters.

    max 100partial successempty_batchbatch_too_large
    POST /v1/messages/batch
    curl -s -X POST "$MAILACK_API_URL/v1/messages/batch" \
      -H "Authorization: Bearer $MAILACK_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "messages": [
          {
            "idempotency_key": "order-1",
            "from": "noreply@acme.mx",
            "to": "a@ejemplo.com",
            "subject": "Pedido 1",
            "text": "Gracias"
          },
          {
            "idempotency_key": "order-2",
            "from": "noreply@acme.mx",
            "to": "b@ejemplo.com",
            "subject": "Pedido 2",
            "text": "Gracias"
          }
        ]
      }' | jq .
  2. 02GET /v1/rates

    Deliverability rates

    Tenant aggregates over a day window (query days, default 14): ingested, sent, deferred, bounced, complained, sealed, rates and daily series. Same shape in the portal Rates screen.

    These are operational tenant metrics, not a public deliverability claim for the product.

    GET /v1/rates?days=14
    curl -s "$MAILACK_API_URL/v1/rates?days=14" \
      -H "Authorization: Bearer $MAILACK_API_KEY" | jq .
  3. 03auth

    API key scopes

    The Bearer key resolves the tenant. Common scopes:

    messages:sendsend, batch, domains, webhooks, templates
    evidence:readreads, lists, rates, proof-bundle
    suppressions:writesuppressions
    api_keys:writetenant key management
  4. 04errors

    Envelope, codes and HTTP status

    Uniform shape: {"error":{"code":"snake_case","message":"…"}}. The message is human-readable and usually tells you the fix (e.g. "add and verify it in the portal"). Each code always maps to the same HTTP status:

    400 · missing_idempotency_keymissing Idempotency-Key header on ingest; it is required and makes retries safe
    400 · invalid_bodymalformed JSON or no content: at least one of text or html is required
    400 · invalid_addressempty or malformed From/To address
    401 · unauthorizedmissing or invalid API key in Authorization: Bearer
    403 · insufficient_scopethe key is valid but lacks the scope the route requires
    403 · tenant_suspended / tenant_deactivatedthe account is suspended or deactivated; contact support
    404 · not_foundthe resource does not exist (or is not visible to your tenant: a cross-tenant read answers 404, not 403)
    404 · template_not_foundthe template_id used in the send does not exist in the tenant
    415 · unsupported_media_typeContent-Type other than application/json or message/rfc822
    422 · domain_not_verifiedthe From domain is not verified on your account; register it, publish the challenge TXT and verify it (Domains guide)
    422 · from_not_approvedthe sender is not an approved active sender of the verified domain
    422 · recipient_suppressedthe recipient is on the suppression list (global or tenant); the dispatcher will not queue it
    422 · template_render_errorfailed to render the template's {{variables}} with the supplied data
    422 · not_certifiedtried to seal a message sent with certified=false; there is no silent upgrade to certified
    422 · missing_proof_datathe message is not sealed into a Merkle batch yet (or it is plain and never will be); retry after sealing
    429 · quota_exceededmonthly quota exhausted; the message names the bag ("certified" or "plain"). Independent bags per UTC calendar month: exhausting one does not block the other; idempotent replay does not consume quota

    The complete, normative list of codes lives in the repository's OpenAPI contract (docs/openapi.yaml, Error schema). This table covers the errors you will hit while integrating sending.

  5. 05surfaces

    Machine, portal and admin

    Machine API (/v1/* with API key) is for SDKs and MCP. Portal (/v1/portal/*) uses the customer user session. Admin (/v1/admin/*) is the JAAK operator console.

    API keyportal sessionadmin session
  6. 06certified

    Certified vs plain

    Every message carries a certified flag. JSON sends accept the per-message override "certified": false; when omitted, the account default (default_certified) applies. Raw message/rfc822 bodies take no override: they always use the account default. A plain message is delivered the same way, but it never enters the Merkle tree: sealing it answers 422 not_certified and its proof-bundle 422 missing_proof_data. There is no silent upgrade to certified.

    certified=truecanonicalized, Merkle leaf, sealable with NOM-151; quota_messages_month bag
    certified=falsetransactional delivery with SMTP transcript and DSN, no seal; quota_plain_messages_month bag
    independent quotasexhausting the plain bag does not block the certified one, and vice versa
    billingprice_per_message_cents (certified) and price_per_plain_message_cents (plain), per account
    POST /v1/messages with certified=false
    curl -s -X POST "$MAILACK_API_URL/v1/messages" \
      -H "Authorization: Bearer $MAILACK_API_KEY" \
      -H "Idempotency-Key: $(uuidgen)" \
      -H "Content-Type: application/json" \
      -d '{
        "from": "noreply@acme.mx",
        "to": "cliente@ejemplo.com",
        "subject": "Aviso operativo",
        "html": "<p>Mensaje transaccional sin certificación.</p>",
        "certified": false
      }' | jq '{id, certified}'
  7. 07GET /v1/messages/{id}/evidence · POST /v1/verify

    Online evidence and verification

    Besides the downloadable proof-bundle —which anyone can check at mailack.com/verificar, in their browser with nothing uploaded—, the API exposes evidence online: /evidence returns the sealed message summary (canonical hash, batch_id, Merkle root, certificate_id, sealed_at) and /v1/verify recomputes the Merkle inclusion proof by message_id and answers valid: true|false. Every verification consult is recorded in the access log.

    evidenceonline verifyproof-bundle
    GET /v1/messages/{id}/evidence · POST /v1/verify
    # Resumen de evidencia del mensaje (hash, batch, raíz Merkle, constancia)
    curl -s "$MAILACK_API_URL/v1/messages/$MESSAGE_ID/evidence" \
      -H "Authorization: Bearer $MAILACK_API_KEY" | jq .
    
    # Verificación online de la prueba de inclusión Merkle
    curl -s -X POST "$MAILACK_API_URL/v1/verify" \
      -H "Authorization: Bearer $MAILACK_API_KEY" \
      -H "Content-Type: application/json" \
      -d "{\"message_id\": \"$MESSAGE_ID\"}" | jq .
    # → {"valid": true, "merkle_root": "…", "certificate_id": "…", "sealed_at": "…"}

Official SDKs

The same endpoints wrapped in Go, Rust, JS, Node, Python, Java and C#.