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/configPATCH /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"}| Campo | Descripcion |
|---|---|
host | Servidor SMTP (smtp.gmail.com, smtp.office365.com, smtp.tudominio.com) |
port | Puerto: 587 (STARTTLS), 465 (SMTPS), 25 (sin cifrar) |
secure | true = usa SSL/TLS directamente (puerto 465) |
user | Usuario o direccion de email remitente |
password | Password o app password (recomendado: app password, no password real) |
from_name | Nombre que aparece como remitente |
from_email | Email que aparece como remitente |
reply_to | Email de respuesta (si diferente del remitente) |
tls | starttls (recomendado), tls (SSL), none |
Probar conexion
POST /api/email/config/testContent-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
| Estado | Descripcion |
|---|---|
queued | En espera. El siguiente proceso de cola lo recogerá. |
sending | Enviando actualmente. |
sent | Enviado correctamente. |
failed | Fallo tras agotar todos los reintentos. |
retrying | Reintentando 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/sendAuthorization: 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/previewAuthorization: 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-bulkAuthorization: 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}| Campo | Descripcion |
|---|---|
document_ids | Lista de IDs de documentos a enviar |
to_field | Campo del documento/partner usado como destinatario |
subject | Asunto con variables ({{series}}, {{number}}, {{partner_name}}) |
body_html | Cuerpo 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/:idIncluye 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/templatesPOST /api/email/templatesPATCH /api/email/templates/:idDELETE /api/email/templates/:idModelo
{ "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
| Variable | Descripcion |
|---|---|
{{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
| Metodo | Ruta | Descripcion |
|---|---|---|
| GET | /api/email/config | Obtener config SMTP |
| PATCH | /api/email/config | Guardar config SMTP |
| POST | /api/email/config/test | Enviar email de prueba |
| POST | /api/email/send | Enviar un documento por email |
| POST | /api/email/preview | Preview antes de enviar |
| POST | /api/email/send-bulk | Envio masivo |
| GET | /api/email/history | Historial de emails enviados |
| GET | /api/email/history/:id | Detalle de un email |
| GET | /api/email/templates | Listar plantillas |
| POST | /api/email/templates | Crear plantilla |
| PATCH | /api/email/templates/:id | Actualizar plantilla |
| DELETE | /api/email/templates/:id | Eliminar plantilla |