npm package: @hemia/workflow-node-email
Description: Nodo de email para Hemia Workflow: envía correos electrónicos basados en eventos.
Author: Hemia Technologies
Latest version: 0.0.2

# @hemia/workflow-node-email

Nodo de envío de correos para Hemia Workflow. Permite enviar emails usando un servicio inyectado, con soporte de plantillas, datos dinámicos, adjuntos y cabeceras. Aprovecha la interpolación del engine para `${config.*}` y `{{ ... }}` (JEXL).

## Instalación

```bash
npm install @hemia/workflow-node-email
```

## Registro en el engine

```ts
import { NodeRegistry } from '@hemia/workflow-engine';
import { emailNode } from '@hemia/workflow-node-email';

const registry = new NodeRegistry();
registry.register('email', emailNode);
```

## Requisitos de ejecución

Este nodo espera un EmailService inyectado en el contexto:
- variables.emailService, o
- variables.services.email

Interfaz mínima esperada:
```ts
interface EmailService {
  send(options: {
    to: string[]; subject: string; html?: string; text?: string;
    template?: string; data?: Record<string, any>;
    from?: string; cc?: string[]; bcc?: string[]; replyTo?: string;
    headers?: Record<string, string>;
    attachments?: Array<{ filename: string; content: string; contentType?: string; encoding?: string }>;
  }): Promise<{ id: string; provider?: any }>;
}
```

## Uso en un workflow

Ejemplo básico con plantilla y datos dinámicos:
```json
{
  "id": "vacation-request-flow",
  "name": "Solicitud de Vacaciones",
  "config": {
    "email": { "from": "noreply@example.com" },
    "timeouts": { "http": "30s" }
  },
  "steps": [
    {
      "id": "notifyManager",
      "type": "email",
      "params": {
        "to": "{{ context.userId | getManagerEmail }}",
        "from": "${config.email.from}",
        "subject": "Solicitud de vacaciones pendiente",
        "template": "vacation-request-approval",
        "data": {
          "employee": "{{context.userFullName}}",
          "startDate": "{{input.startDate}}",
          "endDate": "{{input.endDate}}",
          "reason": "{{input.reason}}",
          "approvalUrl": "{{ request.data.id | generateApprovalUrl }}"
        },
        "saveAs": "notifyEmail"
      },
      "next": [{ "id": "waitApproval" }],
      "error": { "next": "handleError" }
    }
  ]
}
```

Ejemplo con CC/BCC y adjuntos:
```json
{
  "id": "emailWithAttachments",
  "type": "email",
  "params": {
    "to": ["manager@example.com"],
    "cc": ["hr@example.com"],
    "bcc": ["audit@example.com"],
    "from": "${config.email.from}",
    "subject": "Detalle de solicitud",
    "html": "<p>Hola {{context.userFullName}}, adjuntamos el detalle.</p>",
    "attachments": [
      { "filename": "resumen.pdf", "content": "{{output_generatePdf.base64}}", "contentType": "application/pdf", "encoding": "base64" }
    ],
    "saveAs": "emailSummary"
  },
  "error": { "next": "handleError" }
}
```

Ejemplo usando texto plano (sin plantilla):
```json
{
  "id": "plainTextEmail",
  "type": "email",
  "params": {
    "to": "{{input.email}}",
    "subject": "Recibimos tu solicitud",
    "text": "Hola {{input.employeeName}}, tu solicitud de vacaciones fue registrada."
  }
}
```

## Parámetros

- to: string | string[] (requerido). Destinatarios. Acepta coma separada en string.
- from?: string. Remitente; si no se envía, puedes resolverlo desde ${config.email.from}.
- subject: string (recomendado). Asunto del email.
- template?: string. Identificador de plantilla a resolver por tu EmailService.
- data?: object. Datos para la plantilla (interpolables con JEXL).
- html?: string. Cuerpo HTML alternativo si no usas template.
- text?: string. Cuerpo texto plano.
- cc?: string | string[]. Copia.
- bcc?: string | string[]. Copia oculta.
- replyTo?: string. Responder a.
- headers?: object. Cabeceras adicionales.
- attachments?: Array<{ filename, content, contentType?, encoding? }>.
- saveAs?: string. Si se define y el envío es exitoso, el nodo guarda la salida en variables[saveAs].

Notas:
- Interpolación soportada:
  - ${config.*} desde workflow.config (p. ej., ${config.email.from})
  - {{ ... }} con JEXL sobre variables/context (p. ej., {{context.userId}})
- Manejo de errores: puedes encadenar con "error.next" hacia un handler o usar workflow.errorHandlers.

## Salida (output)

En caso de éxito:
```json
{
  "results": {
    "id": "email_123",
    "to": ["manager@example.com"]
  },
  "provider": { "messageId": "sg-abc123" }
}
```

- Si definiste "saveAs": "notifyEmail", encontrarás esa salida en variables.notifyEmail.
- El engine también guardará output del paso en variables.output_<stepId>.

En caso de error:
```json
{
  "success": false,
  "error": { "code": "EMAIL_FAILED", "message": "Descripción", "details": { } }
}
```

## Integración del EmailService

Ejemplo de inyección en el contexto:
```ts
// Al cargar el contexto de una ejecución
const loadContext = async () => ({
  variables: {
    config: { email: { from: "noreply@example.com" } },
    services: { email: myEmailService }, // o variables.emailService = myEmailService
    context: { userId: "E-1001", userFullName: "Ana Pérez" }
  }
});
```

## Buenas prácticas

- Centraliza el remitente en config.email.from y evita hardcodear emails.
- Usa transforms JEXL para datos derivados (p. ej., getManagerEmail, generateApprovalUrl).
- Maneja errores con error.next y un handler HTTP global para observabilidad.
- Agrega unit tests del nodo y mocks del EmailService.

## Scripts

- npm run build: compila el paquete.
- npm run test: ejecuta pruebas.
- npm run lint: lint del código.