Batch, rates y errores.
Más allá del envío unitario: ingesta masiva con éxito parcial, métricas del tenant, el envelope de error uniforme, certificado vs plain y la verificación online de la evidencia.
- 01POST /v1/messages/batch
Ingesta batch (≤100)
Un solo request JSON con hasta 100 mensajes. Cada ítem lleva su propio idempotency_key. La respuesta lista resultados por índice y contadores created / failed / replayed.
max 100partial successempty_batchbatch_too_largePOST /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 . - 02GET /v1/rates
Rates de entregabilidad
Agregados del tenant en una ventana de días (query days, default 14): ingested, sent, deferred, bounced, complained, sealed, tasas y serie diaria. Misma forma en el portal (pantalla Rates).
Son métricas operativas del tenant, no un claim de entregabilidad pública del producto.
GET /v1/rates?days=14 curl -s "$MAILACK_API_URL/v1/rates?days=14" \ -H "Authorization: Bearer $MAILACK_API_KEY" | jq . - 03auth
Scopes de API key
La key Bearer resuelve el tenant. Scopes habituales:
messages:sendenvío, batch, dominios, webhooks, templatesevidence:readlectura, listados, rates, proof-bundlesuppressions:writesupresionesapi_keys:writegestión de keys del tenant - 04errores
Envelope, códigos y HTTP status
Formato uniforme: {"error":{"code":"snake_case","message":"…"}}. El message es legible y suele indicar la corrección (p. ej. "add and verify it in the portal"). Cada código va siempre con el mismo HTTP status:
400 · missing_idempotency_keyfalta el header Idempotency-Key en la ingesta; es obligatorio y hace seguro el reintento400 · invalid_bodyJSON malformado o sin contenido: se requiere al menos uno de text o html400 · invalid_addressdirección From/To vacía o con formato inválido401 · unauthorizedAPI key ausente o inválida en Authorization: Bearer403 · insufficient_scopela key es válida pero no tiene el scope que exige la ruta403 · tenant_suspended / tenant_deactivatedla cuenta está suspendida o desactivada; contacta a soporte404 · not_foundel recurso no existe (o no es visible para tu tenant: una lectura cruzada responde 404, no 403)404 · template_not_foundel template_id del envío no existe en el tenant415 · unsupported_media_typeContent-Type distinto de application/json o message/rfc822422 · domain_not_verifiedel dominio del From no está verificado en tu cuenta; regístralo, publica el TXT de challenge y verifícalo (guía de Dominios)422 · from_not_approvedel remitente no está aprobado como sender activo del dominio verificado422 · recipient_suppressedel destinatario está en la lista de supresión (global o del tenant); el dispatcher no lo encola422 · template_render_errorfallo al renderizar las {{variables}} de la plantilla con los datos enviados422 · not_certifiedse intentó sellar un mensaje enviado con certified=false; no hay upgrade silencioso a certificado422 · missing_proof_datael mensaje aún no está sellado en un batch Merkle (o es plain y nunca lo estará); reintenta tras el sellado429 · quota_exceededcuota mensual agotada; el message distingue la bolsa ("certified" o "plain"). Bolsas independientes por mes natural UTC: agotar una no bloquea la otra; el replay idempotente no consume cuotaEl listado completo y normativo de códigos vive en el contrato OpenAPI del repositorio (docs/openapi.yaml, schema Error). Esta tabla cubre los errores que verás al integrar el envío.
- 05superficies
Machine, portal y admin
Machine API (/v1/* con API key) es la de SDKs y MCP. Portal (/v1/portal/*) usa sesión del usuario del cliente. Admin (/v1/admin/*) es la consola de operadores JAAK.
API keyportal sessionadmin session - 06certified
Certificado vs plain
Cada mensaje lleva una bandera certified. El envío JSON acepta el override por mensaje "certified": false; si lo omites, aplica el default de la cuenta (default_certified). Los cuerpos message/rfc822 no admiten override: siempre usan el default. Un mensaje plain entrega igual, pero no entra al árbol Merkle: sellarlo responde 422 not_certified y su proof-bundle 422 missing_proof_data. No hay upgrade silencioso a certificado.
certified=truecanonicalizado, hoja Merkle, sellable con NOM-151; bolsa quota_messages_monthcertified=falseentrega transaccional con transcript SMTP y DSN, sin sello; bolsa quota_plain_messages_monthcuotas independientesagotar la bolsa plain no bloquea la certificada, y viceversafacturaciónprice_per_message_cents (certificado) y price_per_plain_message_cents (plain), por cuentaPOST /v1/messages con 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}' - 07GET /v1/messages/{id}/evidence · POST /v1/verify
Evidencia y verificación online
Además del proof-bundle descargable —que cualquiera puede comprobar en mailack.com/verificar, en su navegador y sin subir nada—, la API expone la evidencia en línea: /evidence devuelve el resumen del mensaje sellado (hash canónico, batch_id, raíz Merkle, certificate_id, sealed_at) y /v1/verify recomputa la prueba de inclusión Merkle por message_id y responde valid: true|false. Cada consulta de verificación queda en el access log.
evidenceverify onlineproof-bundleGET /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": "…"}