QMailing
ProductoPreciosAPI y MCP
Iniciar sesión

Documentación

API pública y servidor MCP

Construye agentes, CLIs e integraciones de back-office sobre qmailing. PLUS y superiores desbloquean la autenticación con token Bearer en cada endpoint público, además de un servidor MCP listo para usar con Claude Desktop, Cursor o cualquier otro cliente compatible.

Empezar Gestionar tokens

En esta página

  • 1. Primeros pasos
  • 2. Autenticación
  • 3. Scopes
  • 4. Buzones
  • 5. Dominios y DNS
  • 6. Correo
  • 7. Webhooks
  • 8. Errores
  • 9. Límites de tasa
  • 10. Servidor MCP

Primeros pasos

  1. 1. Pasa a PLUS — la API pública está disponible en PLUS, PRO, PRO_PLUS o PRO_MAX. Las llamadas desde FREE reciben 402 Payment Required independientemente del token.
  2. 2. Emite un token — ve a Configuración → Desarrolladores, haz clic en Nuevo token, elige los scopes que necesites y copia el valor qm_live_… cuando se muestre. Los tokens aparecen una sola vez.
  3. 3. Realiza tu primera llamada:
    curl https://qmailing.com/api/v1/pub/mailboxes \
      -H "Authorization: Bearer qm_live_your_token_here"

Autenticación

Toda llamada a la API pública lleva una cabecera Authorization: Bearer qm_live_<…>. El formato del token tiene un espacio de nombres (qm_live_ seguido de 32 caracteres aleatorios), así las fugas en logs y repositorios saltan a la vista.

Los tokens se almacenan en forma de hash (SHA-256). El texto en claro se muestra exactamente una vez al emitirlo y nunca más — si lo pierdes, genera uno nuevo. Cada solicitud activa una comparación de hash en tiempo constante; last_used_at se actualiza al tener éxito para que la UI de desarrolladores pueda señalar credenciales obsoletas.

Las bajadas de plan desactivan los tokens existentes de inmediato. La API vuelve a comprobar apiAccess en cada solicitud firmada — no es necesario revocar tokens uno a uno al cambiar de plan.

Scopes

Cada token lleva una o más cadenas de scope. Los endpoints declaran los scopes que necesitan; los scopes faltantes devuelven 403 InsufficientScope. El comodín (*) lo concede todo — actívalo solo si realmente lo necesitas.

ScopePermite
mailboxes:readListar e inspeccionar buzones
mailboxes:writeCrear, modificar y eliminar buzones
domains:readListar dominios y registros DNS
domains:writeAñadir, verificar y eliminar dominios personalizados (solo FE en v1)
email:readListar e inspeccionar mensajes de los buzones
email:sendEnviar correo saliente a través de la API
webhooks:readListar endpoints de webhook
webhooks:writeRegistrar, listar y revocar endpoints de webhook
*Comodín — lo concede todo. Úsalo con moderación.

Buzones

Listar

GET /api/v1/pub/mailboxes
# scope: mailboxes:read

Obtener uno

GET /api/v1/pub/mailboxes/{id}
# scope: mailboxes:read

Crear

POST /api/v1/pub/mailboxes
Content-Type: application/json
# scope: mailboxes:write

{
  "localPart": "support",
  "domain": "yourbrand.com",
  "displayName": "Support team",
  "forwardTo": "ops@yourbrand.com"
}

El campo domain es opcional — omítelo y el buzón se creará en qmailing.com. En un dominio personalizado, el dominio debe estar tanto claimed COMO fullyVerified; un dominio aún no listo devuelve 400 en lugar de crear silenciosamente un buzón sin enrutamiento.

La eliminación de buzones no se expone deliberadamente en v1. Registra un consentimiento de los Términos del Servicio y consume un slot del cupo vitalicio de la cuenta — ambos pertenecen a una persona pulsando un botón tras leer el modal, no a un agente sin supervisión. Usa el frontend.

Dominios y DNS

Listar dominios personalizados

GET /api/v1/pub/domains
# scope: domains:read

Lista de registros DNS

GET /api/v1/pub/domains/{id}/dns-records
# scope: domains:read

Devuelve la lista completa de 8 registros (TXT de challenge, MX, SPF, tres CNAME DKIM, DMARC, opcionalmente _amazonses) con un status por fila de PENDING / FOUND / MISMATCH / NOT_REQUIRED. El agente puede usarla para indicar al usuario exactamente qué publicar en su proveedor de DNS.

Correo

Lee el contenido de los buzones y envía correo saliente. La paginación por carpetas coincide con la API interna del frontend — los agentes que migran de cookie-JWT a Bearer no verán ningún cambio de forma.

Listar por carpeta

GET /api/v1/pub/email?folder=INBOX&offset=0&limit=25
# scope: email:read

# Optional query params:
#   mailboxId   filter to one mailbox (omit for all-mailboxes view)
#   folder      INBOX | SENT | DRAFTS | TRASH | STARRED | SPAM
#   offset      0-based index, default 0
#   limit       1..100, default 25

folder por defecto es INBOX; valores permitidos: INBOX, SENT, DRAFTS, TRASH, STARRED, SPAM. mailboxId es opcional — al omitirlo se listan todos los buzones del llamante. Paginación con offset + limit; limit está restringido al rango 1–100 (por defecto 25).

Obtener uno

GET /api/v1/pub/email/{id}
# scope: email:read

Devuelve el EmailDetailDto completo: cuerpo HTML / texto, lista de etiquetas, metadatos de adjuntos. Los adjuntos NO se incluyen inline — descarga cada uno con GET /{emailId}/attachments/{index} más abajo, que transmite los bytes en bruto con el nombre original y el content-type.

Descargar adjunto

GET /api/v1/pub/email/{emailId}/attachments/{index}
# scope: email:read
# response: streamed bytes, Content-Type from the attachment

Transmite los bytes en bruto del adjunto (índice basado en cero dentro de la lista de adjuntos del correo). Mismo formato que el endpoint con sesión: Content-Type pass-through, Content-Disposition según RFC 6266 con el nombre original (UTF-8 percent-encoded para Unicode). Los agentes MCP que llaman vía @qmailing/mcp-server usan la herramienta qmailing_get_attachment, que devuelve los bytes codificados en base64 inline (límite 5 MiB).

Enviar

Endpoint de envío. Cuerpo multipart/form-data con una parte command en JSON (destinatarios / asunto / cuerpo) y cero o más partes-archivo attachments:

POST /api/v1/pub/email/send
Content-Type: multipart/form-data
# scope: email:send

--boundary
Content-Disposition: form-data; name="command"
Content-Type: application/json

{
  "mailboxId": "11111111-2222-3333-4444-555555555555",
  "to":      ["alice@example.com"],
  "cc":      [],
  "bcc":     [],
  "subject": "Order #1428 confirmed",
  "bodyText": "Plain-text body",
  "bodyHtml": "<p>Rich body</p>",
  "replyToId": null
}
--boundary
Content-Disposition: form-data; name="attachments"; filename="receipt.pdf"
Content-Type: application/pdf

<binary bytes>
--boundary--

Forma del JSON command:

{
  "mailboxId":   "uuid (required) — must be SES-verified",
  "to":          "string[] (required, max 50)",
  "cc":          "string[] (optional, max 50)",
  "bcc":         "string[] (optional, max 50)",
  "subject":     "string (max 998 chars)",
  "bodyText":    "string — plain-text body",
  "bodyHtml":    "string — HTML body",
  "replyToId":   "uuid | null — set for in-thread replies"
}

Cada llamada cuenta contra el límite diario de envío del plan, la cuota de envío SES del buzón y la cuota de almacenamiento del usuario para la copia resultante en SENT. Los destinatarios se validan como direcciones RFC 5322; los inválidos devuelven 400 con la dirección ofensora. El buzón referenciado en mailboxId debe estar verificado en SES — si no, 409 MailboxNotVerified con una pista hacia la página de dominios.

Los agentes que prefieran JSON sobre multipart pueden usar la herramienta MCP incluida — codifica los adjuntos inline en base64 y luego los reempaca en multipart/form-data al salir, así la forma en cable permanece idéntica.
El filename en la parte command es opcional — Node fetch sella automáticamente filename="blob" en cada Blob, los clientes de navegador y Node pueden pasar command.json, y los clientes curl --form pueden omitirlo del todo. Las tres formas se parsean igual; solo importa Content-Type: application/json en los bytes de la parte.

Scopes: email:read para listar y obtener uno, email:send para enviar.

Webhooks

Registra un endpoint HTTPS y QMailing hará POST de un envelope JSON firmado en él cada vez que se dispare uno de los eventos suscritos. La entrega está viva — el worker sondea una cola per-event cada 10s, firma cada solicitud con HMAC-SHA256 y reintenta los fallos con backoff exponencial (1m / 5m / 15m / 1h / 6h) antes de aparcar la fila en el DLQ para revisión manual.

Registrar un endpoint

POST /api/v1/pub/webhooks
Content-Type: application/json
# scope: webhooks:write

{
  "url":    "https://your-server.example.com/qmailing-events",
  "label":  "Production listener",
  "events": ["email.received", "email.bounced", "domain.verified"]
}

# Response (201):
{
  "endpoint": {
    "id":         "uuid",
    "url":        "https://your-server.example.com/qmailing-events",
    "label":      "Production listener",
    "events":     ["email.received", "email.bounced", "domain.verified"],
    "secretPrefix": "whk_AbCd",
    "createdAt":  "2026-05-04T20:30:00Z",
    "active":     true
  },
  "plaintext":  "whk_AbCd…<32 random chars>"   // shown ONCE
}

url debe ser un endpoint HTTPS (HTTP solo se permite para localhost en dev). El host se comprueba contra una allow-list anti-SSRF — direcciones privadas / loopback / cloud-metadata se rechazan ya en el registro. La respuesta lleva plaintext exactamente una vez; guárdalo en el lado del servidor y úsalo para verificar firmas HMAC en las entregas entrantes.

Catálogo de eventos

EventoSe dispara cuando
email.receivedUn correo entrante llega a uno de tus buzones.
email.sentUn correo saliente fue aceptado por SES.
email.bouncedUn correo saliente rebotó (hard o soft).
domain.verifiedUn dominio personalizado completó la verificación DNS de extremo a extremo.

Listar endpoints

GET /api/v1/pub/webhooks
# scopes: webhooks:read OR webhooks:write

Devuelve endpoints activos y revocados, los más nuevos primero. El acceso de lectura funciona con webhooks:read o webhooks:write — un token inspector evita así el scope de escritura más amplio.

Forma de la entrega

Cada entrega es un único POST a tu endpoint con Content-Type: application/json. El cuerpo es el envelope del evento con los campos event (nombre del evento), occurred_at (timestamp ISO) y un bloque data cuya forma depende del evento (ver tabla arriba). Tres cabeceras llevan metadatos de routing + verificación:

POST https://your-server.example.com/qmailing-events
Content-Type: application/json
User-Agent: qmailing-webhook/1 (+https://qmailing.com)
X-Qmailing-Event: email.received
X-Qmailing-Delivery: 5f3b2a91-7c4d-4d52-9c3e-aa1bcd8a4f12
X-Qmailing-Signature: t=1685120800,v1=abc123def…

{
  "event": "email.received",
  "occurred_at": "2026-05-23T19:46:40Z",
  "data": {
    "email_id":   "uuid",
    "mailbox_id": "uuid",
    "from":       "alice@example.com",
    "subject":    "Hello",
    "preview_text": "Just checking in…"
  }
}

Esquema de firma

# Receiver side (Node example)
const crypto = require("node:crypto");

function verify(headers, body, secret) {
  const sig = headers["x-qmailing-signature"];           // "t=1685120800,v1=abc123…"
  const m = /t=(\d+),v1=([0-9a-f]+)/.exec(sig);
  if (!m) return false;
  const ts = Number(m[1]);
  if (Math.abs(Date.now() / 1000 - ts) > 300) return false;  // 5-min replay window
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${ts}.${body}`)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(m[2]));
}

Rechaza la solicitud si el timestamp tiene más de 5 minutos (ventana anti-replay) o si el HMAC recalculado no coincide con el v1 hex — nuestro firmador del BE imita exactamente las convenciones de Stripe / GitHub, por lo que cualquiera de las librerías estándar de verificación funcionará tal cual.

Política de reintentos

Respuestas no-2xx (o errores de transporte — DNS, TLS, conexión rechazada, timeout de 10s) incrementan attempt_count y empujan next_attempt_at al siguiente slot en 1m / 5m / 15m / 1h / 6h. Tras el quinto fallo la entrega pasa a FAILED (DLQ) — visible en /settings/developers, donde el desarrollador puede reintentarla manualmente. Idempotencia: cada reintento lleva el mismo UUID en X-Qmailing-Delivery — los receptores deduplican por esa cabecera.

Listar entregas recientes

GET /api/v1/pub/webhooks/{endpointId}/deliveries?limit=100
# scopes: webhooks:read OR webhooks:write

Devuelve las limit entregas más recientes de un endpoint (default 100, máx 100), las más nuevas primero. Cada fila incluye estado, contador de intentos, último código HTTP, último mensaje de error y el JSON original — alimenta la vista histórica del dashboard de desarrollador.

Enviar entrega de prueba

POST /api/v1/pub/webhooks/{endpointId}/test
# scope: webhooks:write
# Response 202: WebhookDeliveryDto (enqueued, picked up next worker tick)

Dispara un evento sintético webhook.test al endpoint — mismo camino de dispatch que los eventos reales, útil para verificar firma + TLS antes de suscribirse a tráfico en vivo.

Reintentar entrega

POST /api/v1/pub/webhooks/deliveries/{deliveryId}/retry
# scope: webhooks:write
# Response 200: WebhookDeliveryDto (status=PENDING, attempt=MAX-1)

Re-encola manualmente una fila de entrega. No se puede reintentar una ya entregada (409). El reintento da exactamente un intento adicional (contador a MAX-1), por lo que un endpoint persistentemente roto no puede bucle indefinido con clics repetidos.

Eliminar entrega del historial

DELETE /api/v1/pub/webhooks/deliveries/{deliveryId}
# scope: webhooks:write
# Response: 204 No Content

Hard-delete de una fila de entrega de la tabla de historial. El log de auditoría de cumplimiento captura el propio evento de borrado, así que la trazabilidad no se pierde aunque desaparezca la fila.

Revocar endpoint

DELETE /api/v1/pub/webhooks/{id}
# scope: webhooks:write
# Response: 204 No Content

Idempotente — revocar un endpoint ya revocado tiene éxito de forma silenciosa. La fila se conserva (timestamp revoked-at fijado) para que los logs de auditoría mantengan sus referencias.

Límite: 10 endpoints activos por cuenta, 32 eventos por endpoint. Ambos se aplican al registrarse — 409 WebhookEndpointLimitExceeded / 400 InvalidWebhookEvent.

Errores

Cada respuesta no-2xx es un ProblemDetail RFC-7807 con un campo code estable sobre el que tu agente puede ramificar:

HTTPcodeCuándo
400InvalidApiTokenScopeCadena de scope desconocida al crear.
400InvalidEmailAddressUno de los destinatarios no superó la validación de sintaxis RFC 5322.
400InvalidWebhookEventLa lista de eventos registrada contiene un nombre desconocido.
401—Cabecera Bearer ausente, malformada, o token revocado / caducado.
402PlanFeatureRequiredEl llamante está en un plan que no incluye acceso a la API — actualiza.
403InsufficientScopeEl token no contiene ninguno de los scopes requeridos.
404ApiTokenNotFoundEl objetivo de revocación no existe o pertenece a otra cuenta.
409MailboxNotVerifiedEl dominio del buzón aún no está verificado del todo — completa el DNS en /settings/domains.
409ApiTokenLimitExceededLa cuenta tiene el máximo (20) de tokens activos; revoca uno antes.
409WebhookEndpointLimitExceededLa cuenta ya tiene 10 endpoints de webhook activos; revoca uno primero.
429RateLimitExceededEl token superó las 300 solicitudes por minuto. Retry-After en segundos.

Límites de tasa

Cada token tiene su propio cubo de 300 solicitudes por minuto. Los entornos multi-agente (CLI + cron + plugin de IDE) que usan tokens distintos para la misma cuenta funcionan de forma independiente — nunca compiten por un cubo compartido. 429 RateLimitExceeded incluye una cabecera Retry-After en segundos.

Servidor MCP

El paquete @qmailing/mcp-server se ejecuta localmente y expone la API pública como herramientas del Model Context Protocol. Conéctalo a Claude Desktop, Cursor, Continue o cualquier cliente compatible con MCP.

El formato del cable es estable — pero pueden aparecer nuevas herramientas en cualquier versión 0.x. Fija una versión concreta (@qmailing/mcp-server@0.2.0) si no quieres actualizaciones automáticas.
{
  "mcpServers": {
    "qmailing": {
      "command": "npx",
      "args": ["-y", "@qmailing/mcp-server"],
      "env": {
        "QMAILING_API_TOKEN": "qm_live_your_token_here"
      }
    }
  }
}

Herramientas incluidas: qmailing_list_mailboxes, qmailing_get_mailbox, qmailing_create_mailbox, qmailing_list_domains, qmailing_get_dns_records, qmailing_list_emails, qmailing_get_email, qmailing_get_attachment, qmailing_send_email, qmailing_register_webhook, qmailing_list_webhooks, qmailing_delete_webhook. El catálogo crece a medida que llegan nuevos endpoints.

¿Has encontrado un bug o tienes alguna pregunta? Tokens, scopes, límites de plan y contadores de límite de tasa son todos visibles desde tu página de desarrolladores.

QMailing

Crea buzones, no cuentas.

Producto

  • Todo lo que necesitas
  • Precios
  • Comparativa
  • Casos de uso
  • API y MCP

Legal

  • Política de privacidad
  • Términos de servicio
  • Reembolsos
  • Eliminación de datos

Soporte

  • Contactar con soporte
© 2026 QMailing. Todos los derechos reservados.