Envía la key como credencial bearer
Usa una key sandbox durante la integración y otra de producción al lanzar. La key completa se muestra una sola vez.
curl "$MAILDECK_API_URL/v1/mailboxes" \
-H "Authorization: Bearer $MAILDECK_API_KEY"
Concede los scopes mínimos
La solicitud solo funciona si la key concede todos los scopes del endpoint. Los scopes quedan fijos al emitirla; para cambiar privilegios emite una key sustituta. Revocación y expiración surten efecto sin reiniciar servicios.
- mailboxes:read — lista buzones y consulta salud de dominios
- messages:read — lista/consulta mensajes y concilia GET /v1/events
- messages:write — cambia leído, destacado o carpeta con PATCH /v1/messages/{id}
- messages:send — encola correo saliente con POST /v1/messages
- attachments:write — carga un adjunto para un envío posterior
- attachments:read — descarga un adjunto solo tras un escaneo limpio
- webhooks:read — lista endpoints e inspecciona intentos de entrega
- webhooks:write — crea endpoints y hace replay de entregas fallidas
Pipeline de autenticación
Omnero Mail parsea el Bearer token, rechaza prefijos inválidos, verifica el hash Argon2id almacenado, comprueba estado y expiración de app/key, evalúa todos los scopes y deriva tenant y aplicación de la credencial. Cada operación vuelve a comprobar ownership; un tenant ID aportado por el caller nunca cambia el límite de autorización.
Rota y revoca sin downtime
Emite una segunda key para la misma aplicación, despliégala en los consumidores, confirma su actividad de último uso y revoca la anterior. Nunca registres Authorization en logs, pongas keys en URLs, reutilices cookies del dashboard ni copies keys de producción a tooling sandbox.
Maneja los límites explícitamente
Las solicitudes pasan primero un límite por IP (120 por minuto), luego el límite por key/minuto del plan activo y el límite diario compartido por tenant. Una respuesta 429 incluye el código estable rate_limited y Retry-After. Aplica backoff con jitter y no rotes keys para eludir la protección.
const response = await fetch(url, { headers });
if (response.status === 429) {
const retryAfter = Number(response.headers.get('retry-after') ?? '1');
await new Promise((resolve) =>
setTimeout(resolve, retryAfter * 1000 + Math.random() * 250),
);
}
Fallos estables
Los errores usan { code, message, requestId, details }. Interpreta 401 como credencial ausente, inválida, expirada o revocada; 403 como insufficient_scope; 404 como recurso inexistente o inaccesible; 409 como conflicto/idempotencia pendiente; 413 como adjunto excesivo; y 429 como rate limit. Incluye requestId al escalar un fallo.