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
- Ve a Consola de Desarrolladores → API Keys y crea una clave.
- Guarda la clave — se muestra una única vez.
- Haz un
POSTa/api/v1/emailscon esa clave.
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:
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:
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| Correos por mes | 5,000 | — | Solo cuenta lo enviado vía /api/v1/emails (no el webmail). Se reinicia cada mes natural. |
| Dominios verificados | 3 | — | Puedes 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)
- Añade el dominio — generamos una clave DKIM exclusiva.
- Copia los 2 registros TXT (DKIM y SPF) a tu proveedor DNS.
- 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
/api/v1/emailsEnvía un correo. Autenticación: Authorization: Bearer <api key>
Cuerpo de la petición (JSON)
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| from | string | Sí | Dirección remitente, o "Nombre <email>". Debe ser tu xstarmail.es o un dominio verificado. |
| to | string | string[] | Sí | Destinatario(s). Máximo 50. |
| subject | string | No | Asunto del correo. |
| html | string | html o text | Cuerpo en HTML. |
| text | string | html o text | Cuerpo en texto plano. |
| cc | string | string[] | No | Copia. |
| bcc | string | string[] | No | Copia oculta. |
| reply_to | string | No | Dirección de respuesta. |
| attachments | Attachment[] | No | Ver sección Adjuntos. |
| headers | object | No | Headers SMTP personalizados, ej. { "X-Entity-Ref-ID": "123" }. |
| tags | {name,value}[] | No | Metadata para buscar/filtrar en Logs. |
| scheduled_at | string (ISO 8601) | No | Programa el envío para más tarde. Solo con tu dirección @xstarmail.es (ver Envío programado). |
Respuesta — 200 OK
{ "id": "cm3x9f2a10001abc" }Envío por lotes
/api/v1/emails/batchEnví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 -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
{
"data": [
{ "id": "cm3x9f2a10001abc" },
{ "id": "cm3x9f2a10002abd" }
]
}Adjuntos
Envía cualquier archivo codificado en base64 dentro de attachments:
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| filename | string | Sí | Nombre del archivo tal como lo verá el destinatario. |
| content | string | Sí | Contenido del archivo en base64. |
| content_type | string | No | MIME type (ej. "application/pdf"). Por defecto application/octet-stream. |
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' },
],
}),
});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 -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
{ "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):
Idempotency-Key: bienvenida-usuario-4821Si 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
{
"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:
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
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| Destinatarios por envío | array | — | Máximo 50 direcciones combinadas en "to". |
| Tamaño por adjunto | 10MB | — | Por archivo, en base64 decodificado. |
| Tamaño total de adjuntos | 25MB | — | Suma de todos los adjuntos de un mismo envío. |
| Correos por lote | 100 | — | Máximo por llamada a /api/v1/emails/batch. |
| Envíos por API Key | 100 / 10 min | — | Ventana deslizante; supera el límite y recibes 429. |
| Correos por mes (plan gratuito) | 5,000 | — | Ver sección Planes y cuotas. Responde 402 al superarlo. |
| Dominios verificados (plan gratuito) | 3 | — | Ver sección Planes y cuotas. |
| Vigencia de Idempotency-Key | 24 horas | — | Pasado ese tiempo, la misma clave se trata como una nueva petición. |
Estado de un envío
/api/v1/emails/:idConsulta 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.
{
"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
/api/v1/emailsLista los correos enviados con esta API Key, más recientes primero.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| limit | number | No | Máximo de resultados (por defecto 20, máximo 100). |
| starting_after | string | No | ID de un envío anterior — devuelve los siguientes más antiguos que él (paginación por cursor). |
curl https://xstarmail.es/api/v1/emails?limit=20 \
-H "Authorization: Bearer xsk_..."Respuesta — 200 OK
{
"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ódigo | Causa |
|---|---|
| 401 | API key ausente, inválida o revocada. |
| 402 | Cuota del plan gratuito superada (5,000 correos/mes o 3 dominios). Ver Planes y cuotas. |
| 403 | El "from" no te pertenece, o el dominio no está verificado. |
| 422 | Faltan campos obligatorios, se supera el límite de destinatarios, un adjunto/header/tag es inválido, o scheduled_at es inválido. |
| 429 | Límite de envíos alcanzado (100 cada 10 min por clave). |
| 502 | No se pudo entregar a los servidores del destinatario. |
{ "error": "El dominio ejemplo.com todavía no está verificado" }