Saltearse al contenido

Email

Configuracion SMTP por empresa

Cada empresa (tenant) define su propio servidor SMTP para enviar emails desde Keirost. Esto permite usar el dominio de la empresa como remitente.

Configurar SMTP

GET /api/email/config
PATCH /api/email/config
{
"enabled": true,
"host": "smtp.gmail.com",
"port": 587,
"secure": false,
"user": "noreply@tudominio.com",
"password": "app_password_aqui",
"from_name": "Keirost - Tu Empresa",
"from_email": "noreply@tudominio.com",
"reply_to": "soporte@tudominio.com",
"tls": "starttls"
}
CampoDescripcion
hostServidor SMTP (smtp.gmail.com, smtp.office365.com, smtp.tudominio.com)
portPuerto: 587 (STARTTLS), 465 (SMTPS), 25 (sin cifrar)
securetrue = usa SSL/TLS directamente (puerto 465)
userUsuario o direccion de email remitente
passwordPassword o app password (recomendado: app password, no password real)
from_nameNombre que aparece como remitente
from_emailEmail que aparece como remitente
reply_toEmail de respuesta (si diferente del remitente)
tlsstarttls (recomendado), tls (SSL), none

Probar conexion

POST /api/email/config/test
Content-Type: application/json
{
"to": "test@tudominio.com"
}

Envia un email de prueba a la direccion indicada. Devuelve success o el error de conexion.


Cola de envio de emails

Los emails no se envian de forma sincrona. Se encolan en segundo plano para no bloquear la interfaz y poder reintentarlos en caso de fallo.

Modelo de cola

interface EmailQueueItem {
id: string;
to: string[];
cc?: string[];
bcc?: string[];
subject: string;
body_html: string;
body_text?: string;
attachments?: { filename: string; path: string; size: number }[];
status: 'queued' | 'sending' | 'sent' | 'failed' | 'retrying';
attempts: number;
max_attempts: number;
last_error?: string;
sent_at?: string;
created_at: string;
created_by: string;
}

Estados de la cola

EstadoDescripcion
queuedEn espera. El siguiente proceso de cola lo recogerá.
sendingEnviando actualmente.
sentEnviado correctamente.
failedFallo tras agotar todos los reintentos.
retryingReintentando tras un fallo temporal.

Reintentos automaticos

  • Maximo de intentos: 5
  • Intervalo de reintento: exponencial (1min, 5min, 15min, 1h, 4h)
  • Solo se reintenta en fallos temporales (timeout, conexion rechazada, servidor ocupado)
  • Fallos definitivos (autenticacion incorrecta, email invalido) no se reintentan

Envio de documentos por email

Enviar un documento

POST /api/email/send
Authorization: Bearer <token>
Content-Type: application/json
{
"document_type": "sales_invoice",
"document_id": "inv_2026_0012",
"to": ["cliente@acme.com"],
"cc": ["contabilidad@acme.com"],
"subject": "Factura F-2026-0012",
"body_html": "<p>Estimado cliente, adjuntamos la factura {{document_number}}.</p>",
"attach_pdf": true
}

Respuesta inmediata (el email se encola):

{
"queue_id": "eq_001",
"status": "queued",
"message": "Email encolado. Recibirás una notificacion cuando se complete."
}

Preview del email

Antes de enviar, puedes ver exactamente como quedara:

POST /api/email/preview
Authorization: Bearer <token>
Content-Type: application/json
{
"document_type": "sales_invoice",
"document_id": "inv_2026_0012",
"to": ["cliente@acme.com"],
"subject": "Factura F-2026-0012",
"body_html": "<p>Estimado cliente, adjuntamos la factura {{document_number}}.</p>"
}

Devuelve:

{
"subject": "Factura F-2026-0012",
"from": "noreply@tudominio.com",
"to": ["cliente@acme.com"],
"body_html_rendered": "<p>Estimado cliente, adjuntamos la factura F-2026-0012.</p>",
"attachments": [
{
"filename": "F-2026-0012.pdf",
"size": 45678
}
],
"preview_url": "/api/email/preview/:queue_id"
}

Adjuntos automaticos

Por defecto, cuando attach_pdf: true:

  • Se genera el PDF del documento con la plantilla activa
  • Se adjunta con el nombre {{series}}-{{number}}.pdf

Tambien puedes adjuntar archivos adicionales (contratos, presupuestos, etc.):

{
"attachments": [
{ "path": "/storage/contracts/CTR-2026-001.pdf" },
{ "path": "/storage/presupuestos/PRE-2026-001.pdf" }
]
}

Envio masivo

Seleccionar multiples documentos

POST /api/email/send-bulk
Authorization: Bearer <token>
Content-Type: application/json
{
"document_type": "sales_invoice",
"document_ids": ["inv_2026_001", "inv_2026_002", "inv_2026_003"],
"to_field": "partner.email",
"subject": "Factura {{series}}-{{number}}",
"body_html": "<p>Estimado {{partner_name}}, adjuntamos su factura {{document_number}} del {{document_date}}.</p>",
"attach_pdf": true
}
CampoDescripcion
document_idsLista de IDs de documentos a enviar
to_fieldCampo del documento/partner usado como destinatario
subjectAsunto con variables ({{series}}, {{number}}, {{partner_name}})
body_htmlCuerpo HTML con variables

Respuesta:

{
"queued": 3,
"failed": 0,
"queue_ids": ["eq_001", "eq_002", "eq_003"],
"message": "3 emails encolados para envio"
}

Historial de emails enviados

Listar historial

GET /api/email/history?from=2026-01-01&to=2026-05-08&status=sent
{
"data": [
{
"id": "eq_001",
"to": ["cliente@acme.com"],
"subject": "Factura F-2026-0012",
"document_type": "sales_invoice",
"document_id": "inv_2026_0012",
"status": "sent",
"attempts": 1,
"sent_at": "2026-05-08T10:05:00Z",
"created_by": "admin"
}
],
"pagination": {
"total": 156,
"page": 1,
"per_page": 20
}
}

Ver detalle de un email

GET /api/email/history/:id

Incluye el contenido HTML completo, adjuntos y eventos de reintento.


Notificaciones in-app de email

Cuando un email termina de enviarse (o falla despues de todos los reintentos), se genera una notificacion in-app dirigida al usuario que lo envio.

GET /api/notifications?type=email_status
{
"data": [
{
"id": "notif_001",
"type": "email_status",
"title": "Email enviado",
"body": "Factura F-2026-0012 enviada a cliente@acme.com",
"read": false,
"created_at": "2026-05-08T10:05:00Z"
}
]
}

El icono de campana en la interfaz muestra un indicador pulsante cuando hay nuevas notificaciones de email.


Plantillas de email

Keirost permite crear plantillas de email reutilizables por tipo de documento.

Endpoints

GET /api/email/templates
POST /api/email/templates
PATCH /api/email/templates/:id
DELETE /api/email/templates/:id

Modelo

{
"id": "et_factura_cliente",
"name": "Factura a cliente",
"document_type": "sales_invoice",
"subject": "Factura {{series}}-{{number}}",
"body_html": "<h2>Estimado {{partner_name}},</h2><p>Adjuntamos su factura <strong>{{document_number}}</strong> por importe de <strong>{{total}} EUR</strong>.</p>",
"attach_pdf": true,
"active": true
}

Variables disponibles

VariableDescripcion
{{document_number}}Numero completo del documento
{{document_date}}Fecha en formato legible
{{partner_name}}Nombre del cliente/proveedor
{{partner_vat}}CIF/NIF del partner
{{subtotal}}Importe sin impuestos
{{tax_amount}}Importe de impuestos
{{total}}Total con impuestos
{{due_date}}Fecha de vencimiento
{{company_name}}Nombre de la empresa
{{company_address}}Direccion de la empresa

Resumen de endpoints

MetodoRutaDescripcion
GET/api/email/configObtener config SMTP
PATCH/api/email/configGuardar config SMTP
POST/api/email/config/testEnviar email de prueba
POST/api/email/sendEnviar un documento por email
POST/api/email/previewPreview antes de enviar
POST/api/email/send-bulkEnvio masivo
GET/api/email/historyHistorial de emails enviados
GET/api/email/history/:idDetalle de un email
GET/api/email/templatesListar plantillas
POST/api/email/templatesCrear plantilla
PATCH/api/email/templates/:idActualizar plantilla
DELETE/api/email/templates/:idEliminar plantilla