API de envío de xstarmail

Envía correo transaccional desde tu backend con una API Key — sin SMTP, sin sesión de navegador. Igual que Resend o Postmark: creas una clave, opcionalmente verificas tu propio dominio, y haces un POST.

🔑

API Keys

Bearer token

🌐

Dominios propios

DKIM + SPF, o auto-config con Cloudflare

📎

Adjuntos

Base64, cualquier tipo de archivo

📜

Logs

Historial de envíos en la consola

Inicio rápido

  1. Ve a Consola de Desarrolladores → API Keys y crea una clave.
  2. Guarda la clave — se muestra una única vez.
  3. Haz un POST a /api/v1/emails con esa clave.
curl
curl -X POST https://xstarmail.es/api/v1/emails \
  -H "Authorization: Bearer xsk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from": "tu@xstarmail.es",
    "to": "destinatario@ejemplo.com",
    "subject": "Hola desde la API",
    "html": "<p>Este correo se envió vía API</p>"
  }'

Autenticación

Todas las llamadas a /api/v1/* requieren el header:

header
Authorization: Bearer xsk_...

Una API Key pertenece a una cuenta de xstarmail y hereda sus dominios verificados. Nunca la expongas en código de cliente (frontend/app móvil) — úsala solo desde tu backend. Si se filtra, revócala al instante desde la consola.

Planes y cuotas

Toda cuenta nueva empieza en el plan gratuito, igual que en Resend:

CampoTipoObligatorioDescripción
Correos por mes5,000Solo cuenta lo enviado vía /api/v1/emails (no el webmail). Se reinicia cada mes natural.
Dominios verificados3Puedes seguir enviando desde tu dirección @xstarmail.es sin límite adicional.

Consulta tu consumo actual en la parte superior de la Consola de Desarrolladores. Al superar una cuota, la API responde 402 Payment Required.

Dominios propios

Por defecto puedes enviar desde tu propia dirección @xstarmail.es. Para enviar desde cualquiera@tudominio.com, verifica el dominio en la pestaña Dominios de la consola. Hay dos formas:

Manual (cualquier proveedor)

  1. Añade el dominio — generamos una clave DKIM exclusiva.
  2. Copia los 2 registros TXT (DKIM y SPF) a tu proveedor DNS.
  3. Pulsa "Verificar" — comprobamos los registros en vivo.

☁️ Automática con Cloudflare

Si tu dominio usa Cloudflare como DNS, el botón "Configurar con Cloudflare" abre su dashboard con el permiso DNS → Edit ya seleccionado — no tienes que buscarlo. Creas el token allí, lo pegas de vuelta, y creamos los registros por ti automáticamente. No se guarda — se usa una sola vez.

Nota: esto habilita solo el envío. La recepción de correo entrante sigue limitada a xstarmail.es.

⚠ Si tu dominio ya tiene un registro SPF (v=spf1 ...), no publiques uno nuevo — un dominio solo puede tener uno. Añade include:xstarmail.es dentro de tu registro existente, antes del ~all/-all final. Dos registros SPF a la vez es un error según el RFC y rompe el SPF para todos, aunque uno sea el correcto.

Enviar un correo

POST
/api/v1/emails

Envía un correo. Autenticación: Authorization: Bearer <api key>

Cuerpo de la petición (JSON)

CampoTipoObligatorioDescripción
fromstringDirección remitente, o "Nombre <email>". Debe ser tu xstarmail.es o un dominio verificado.
tostring | string[]Destinatario(s). Máximo 50.
subjectstringNoAsunto del correo.
htmlstringhtml o textCuerpo en HTML.
textstringhtml o textCuerpo en texto plano.
ccstring | string[]NoCopia.
bccstring | string[]NoCopia oculta.
reply_tostringNoDirección de respuesta.
attachmentsAttachment[]NoVer sección Adjuntos.
headersobjectNoHeaders SMTP personalizados, ej. { "X-Entity-Ref-ID": "123" }.
tags{name,value}[]NoMetadata para buscar/filtrar en Logs.
scheduled_atstring (ISO 8601)NoPrograma el envío para más tarde. Solo con tu dirección @xstarmail.es (ver Envío programado).

Respuesta — 200 OK

json
{ "id": "cm3x9f2a10001abc" }

Envío por lotes

POST
/api/v1/emails/batch

Envía hasta 100 correos distintos en una sola llamada.

El cuerpo es un array JSON (no un objeto envolvente) con la misma forma que un envío individual. La respuesta mantiene el mismo orden que el array enviado — el índice i del request corresponde al índice i de data. Cada correo cuenta individualmente contra tu cuota mensual y tu límite de envíos.

curl
curl -X POST https://xstarmail.es/api/v1/emails/batch \
  -H "Authorization: Bearer xsk_..." \
  -H "Content-Type: application/json" \
  -d '[
    { "from": "tu@xstarmail.es", "to": "a@ejemplo.com", "subject": "Hola A", "html": "<p>Hola A</p>" },
    { "from": "tu@xstarmail.es", "to": "b@ejemplo.com", "subject": "Hola B", "html": "<p>Hola B</p>" }
  ]'

Respuesta — 200 OK

json
{
  "data": [
    { "id": "cm3x9f2a10001abc" },
    { "id": "cm3x9f2a10002abd" }
  ]
}

Adjuntos

Envía cualquier archivo codificado en base64 dentro de attachments:

CampoTipoObligatorioDescripción
filenamestringNombre del archivo tal como lo verá el destinatario.
contentstringContenido del archivo en base64.
content_typestringNoMIME type (ej. "application/pdf"). Por defecto application/octet-stream.
javascript
import fs from 'fs';

const pdf = fs.readFileSync('./factura.pdf').toString('base64');

await fetch('https://xstarmail.es/api/v1/emails', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer xsk_...',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    from: 'facturacion@tudominio.com',
    to: 'cliente@ejemplo.com',
    subject: 'Tu factura',
    html: '<p>Adjuntamos tu factura.</p>',
    attachments: [
      { filename: 'factura.pdf', content: pdf, content_type: 'application/pdf' },
    ],
  }),
});

Headers y tags

headers añade cabeceras SMTP propias al correo saliente (útil para threading, referencias de sistemas externos, etc.).tags es metadata que se guarda junto al envío para poder buscarlo luego en Logs — no se envía al destinatario.

javascript
await fetch('https://xstarmail.es/api/v1/emails', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer xsk_...',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    from: 'notificaciones@tudominio.com',
    to: 'usuario@ejemplo.com',
    subject: 'Tu pedido #4821',
    html: '<p>Tu pedido va en camino.</p>',
    headers: {
      'X-Entity-Ref-ID': 'pedido-4821',
    },
    tags: [
      { name: 'category', value: 'order-shipped' },
      { name: 'order_id', value: '4821' },
    ],
  }),
});

Envío programado

Añade scheduled_at (ISO 8601, en el futuro) para que el correo se envíe más tarde en vez de inmediatamente. Un proceso interno revisa cada 60 segundos los envíos pendientes.

Limitación actual: solo disponible enviando desde tu dirección @xstarmail.es (no desde dominios propios) y sin adjuntos.

curl
curl -X POST https://xstarmail.es/api/v1/emails \
  -H "Authorization: Bearer xsk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from": "tu@xstarmail.es",
    "to": "destinatario@ejemplo.com",
    "subject": "Recordatorio",
    "html": "<p>Esto se envía mañana</p>",
    "scheduled_at": "2026-07-27T09:00:00.000Z"
  }'

Respuesta — 200 OK

json
{ "id": "cm3x9f2a10001abc", "scheduled": true }

Idempotencia

Si reintentas una petición tras un timeout o error de red, puedes acabar enviando el mismo correo dos veces. Evítalo enviando un header Idempotency-Key único por operación (no por request):

header
Idempotency-Key: bienvenida-usuario-4821

Si repites la misma clave dentro de las 24 horas siguientes, te devolvemos la respuesta original sin reenviar el correo. La clave es única por API Key — dos claves distintas pueden reutilizar el mismo Idempotency-Key sin chocar entre sí.

Webhooks

Suscríbete desde la pestaña Webhooks de la consola para recibir un POST cada vez que un envío hecho con tu API Key termine en email.sent o email.failed. No hacemos tracking de aperturas ni clics, así que no existen eventos email.opened/email.clicked.

Payload

json
{
  "event": "email.sent",
  "created_at": "2026-07-26T15:46:28.297Z",
  "data": {
    "id": "cm3x9f2a10001abc",
    "from": "notificaciones@tudominio.com",
    "to": "usuario@ejemplo.com",
    "subject": "Bienvenido",
    "status": "sent",
    "error": null
  }
}

Verificar la firma

Cada request incluye X-Xstarmail-Event y X-Xstarmail-Signature — un HMAC-SHA256 del cuerpo firmado con el secreto que se te mostró al crear el webhook. Verifícalo antes de confiar en el payload:

node.js
import crypto from 'crypto';

function isValidSignature(payloadRaw, signatureHeader, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(payloadRaw)
    .digest('hex');
  return signatureHeader === expected;
}

// payloadRaw debe ser el body SIN parsear (string), no el JSON ya parseado
app.post('/webhooks/xstarmail', express.text({ type: '*/*' }), (req, res) => {
  const valid = isValidSignature(req.body, req.headers['x-xstarmail-signature'], process.env.XSTARMAIL_WEBHOOK_SECRET);
  if (!valid) return res.status(401).send('firma inválida');

  const { event, data } = JSON.parse(req.body);
  console.log(event, data);
  res.sendStatus(200);
});

Si tu endpoint no responde 2xx, reintentamos una vez a los 5 segundos y luego lo damos por perdido — no hay cola persistente de reintentos.

La URL debe ser pública — rechazamos localhost y direcciones privadas/locales (10.x, 192.168.x, 169.254.x, etc.) para que no puedas usar el webhook para acceder a servicios internos.

Límites

CampoTipoObligatorioDescripción
Destinatarios por envíoarrayMáximo 50 direcciones combinadas en "to".
Tamaño por adjunto10MBPor archivo, en base64 decodificado.
Tamaño total de adjuntos25MBSuma de todos los adjuntos de un mismo envío.
Correos por lote100Máximo por llamada a /api/v1/emails/batch.
Envíos por API Key100 / 10 minVentana deslizante; supera el límite y recibes 429.
Correos por mes (plan gratuito)5,000Ver sección Planes y cuotas. Responde 402 al superarlo.
Dominios verificados (plan gratuito)3Ver sección Planes y cuotas.
Vigencia de Idempotency-Key24 horasPasado ese tiempo, la misma clave se trata como una nueva petición.

Estado de un envío

GET
/api/v1/emails/:id

Consulta el estado de un envío realizado con tu clave.

Los estados posibles son queued (programado, aún no enviado), sent (entregado al servidor de correo del destinatario) y failed. No incluye aperturas ni clics — no hacemos tracking de eso.

json — 200 OK
{
  "id": "cm3x9f2a10001abc",
  "fromAddress": "notificaciones@tudominio.com",
  "toAddress": "usuario@ejemplo.com",
  "subject": "Bienvenido",
  "deliveryStatus": "sent",
  "deliveryError": null,
  "createdAt": "2026-07-26T15:46:28.297Z"
}

Listar envíos

GET
/api/v1/emails

Lista los correos enviados con esta API Key, más recientes primero.

CampoTipoObligatorioDescripción
limitnumberNoMáximo de resultados (por defecto 20, máximo 100).
starting_afterstringNoID de un envío anterior — devuelve los siguientes más antiguos que él (paginación por cursor).
curl
curl https://xstarmail.es/api/v1/emails?limit=20 \
  -H "Authorization: Bearer xsk_..."

Respuesta — 200 OK

json
{
  "data": [
    {
      "id": "cm3x9f2a10001abc",
      "fromAddress": "notificaciones@tudominio.com",
      "toAddress": "usuario@ejemplo.com",
      "subject": "Bienvenido",
      "deliveryStatus": "sent",
      "deliveryError": null,
      "scheduledAt": null,
      "tags": [{ "name": "category", "value": "order-shipped" }],
      "createdAt": "2026-07-26T15:46:28.297Z"
    }
  ],
  "has_more": false
}

Ejemplos de código

Enviar un correo con reply-to e idempotencia, en varios lenguajes:

curl -X POST https://xstarmail.es/api/v1/emails \
  -H "Authorization: Bearer xsk_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: bienvenida-usuario-4821" \
  -d '{
    "from": "notificaciones@tudominio.com",
    "to": ["usuario@ejemplo.com"],
    "subject": "Bienvenido",
    "html": "<strong>Gracias por registrarte</strong>",
    "reply_to": "soporte@tudominio.com"
  }'

Errores

CódigoCausa
401API key ausente, inválida o revocada.
402Cuota del plan gratuito superada (5,000 correos/mes o 3 dominios). Ver Planes y cuotas.
403El "from" no te pertenece, o el dominio no está verificado.
422Faltan campos obligatorios, se supera el límite de destinatarios, un adjunto/header/tag es inválido, o scheduled_at es inválido.
429Límite de envíos alcanzado (100 cada 10 min por clave).
502No se pudo entregar a los servidores del destinatario.
json — error
{ "error": "El dominio ejemplo.com todavía no está verificado" }