# Introducción (/) ReallyQuickEmails (RQE) es la API para enviar emails transaccionales y campañas masivas: una llamada HTTP y tu primer email está en camino. ## Tu primer email ```bash curl -X POST https://api.reallyquickemails.com/v1/send-email \ -H "Authorization: Bearer sk_proj_xxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "recipient_email": "usuario@ejemplo.com", "sender_email": "hola@tudominio.com", "subject": "Hola desde ReallyQuickEmails", "html_body": "
Mi primer email con RQE.
" }' ``` Respuesta exitosa (`200`): ```json { "message": "Email sent successfully", "email_id": "550e8400-e29b-41d4-a716-446655440000", "project_id": "123e4567-e89b-12d3-a456-426614174000" } ``` `email_id` identifica el envío: úsalo para consultar su estado en [Activity](/api-reference/activity) y correlacionar los eventos que llegan a tus [webhooks](/api-reference/webhooks). El `sender_email` debe pertenecer a un [dominio verificado](/api-reference/domains).Tenemos una oferta en {{producto}}.
", "recipients": [ { "email": "juan@ejemplo.com", "data": { "nombre": "Juan", "producto": "Plan Pro" } }, { "email": "maria@ejemplo.com", "data": { "nombre": "Maria", "producto": "Plan Business" } } ] }' ``` Soporta idempotencia (header `Idempotency-Key`), previsualización con `dry_run` y envío programado con `scheduled_at` (ISO 8601). Ver [API Pública v1](/api-reference/public-api) y [Programar envíos](/guides/scheduling). ### Automatizaciones Dispara flujos de emails (onboarding, recuperación de carritos, secuencias) desde tu backend con el [SDK de Node.js](/guides/sdk-nodejs): ```ts import { RQE } from '@reallyquickemails/sdk'; const rqe = new RQE({ apiKey: process.env.RQE_API_KEY }); // Enroll idempotente — pasa trigger_reference_id para que múltiples calls // con el mismo external_id NO creen runs duplicados, sino que actualicen el run existente await rqe.automations.enroll('automation-uuid', { email: 'user@example.com', data: { first_name: 'Adriano', plan: 'pro' }, trigger_reference_id: 'order-12345', }); ``` ### Respuestas entrantes (inbound) Los emails enviados vía API (`POST /v1/send-email`, `POST /send-email` y `POST /v1/send-batch`) incluyen automáticamente un `Reply-To` con token único: ``` Reply-To: "Tu Empresa"Tu código de descuento es: {{codigo}}
{{default producto "nuestro catálogo"}}
``` **Sintaxis de variables:** | Sintaxis | Descripción | |---|---| | `{{variable}}` | Sustitución básica | | `{{default variable "fallback"}}` | Valor por defecto si la variable no existe | | `{{#each items}}...{{/each}}` | Loop sobre arrays | **Helpers disponibles:** | Helper | Descripción | |---|---| | `{{formatCurrency precio "USD"}}` | Formatea un número como moneda. | | `{{formatDate fecha "long"}}` | Formatea una fecha (`short` por defecto). | | `{{multiply cantidad precio}}` | Multiplica dos números (2 decimales). | | `{{capitalize texto}}` / `{{uppercase texto}}` / `{{lowercase texto}}` | Transformación de texto. | **Variables integradas:** cada destinatario recibe automáticamente `{{name}}`, `{{email}}` y `{{unsubscribe_url}}`, además de los campos de datos del contacto. La búsqueda de claves de primer nivel también funciona en minúsculas (si tus datos de CSV/CRM traen claves como `NOMBRE`, el template puede usar `{{nombre}}`). Si el template no incluye `{{unsubscribe_url}}`, se agrega un footer de cancelación de suscripción automáticamente y los emails incluyen headers `List-Unsubscribe`. ### Paso 2: Crear la campana Desde **Campañas → Nueva Campaña**, el asistente te guía en 5 pasos: 1. **Destinatarios** — Selecciona manualmente, usa "Seleccionar todos" o filtra por segmento. 2. **Template y Remitente** — Elige el template creado y el perfil de remitente (dominio verificado). 3. **Variables** (opcional) — Configura valores por defecto, mapea campos del destinatario a variables del template, o sube un CSV con valores personalizados por email. 4. **Configuración de envío** — Activa modo por lotes, define tamaño de lote e intervalo, o distribuye el envío en N horas. 5. **Revisar y Enviar** — Confirma todo y lanza la campaña. ### Paso 3: Procesamiento automatico Una vez creada la campaña: 1. Se crean los registros de destinatarios (estado: `pending`) 2. La campaña pasa de `initializing` a `pending` 3. Los destinatarios se procesan de forma asíncrona en segundo plano: se preparan las variables de cada uno, se encolan para envío y pasan a estado `queued` 4. Cada email se renderiza con Handlebars, se envía a través de nuestra infraestructura de envío y pasa a estado `sent` 5. Los eventos de delivery/bounce/complaint actualizan el estado final y disparan tus webhooks Ver más en [Webhooks](./webhooks). ### Configuracion avanzada #### Modo por lotes Divide el envío en lotes con intervalos entre ellos con `batchMode`, `batchSize` y `batchIntervalHours`. Útil para warming de dominio o para no saturar a tus destinatarios. **Ejemplo:** 50,000 destinatarios con lotes de 10,000 cada 24 horas = 5 días de envío gradual. Ver más en [`POST /v1/campaigns`](#post-v1campaigns). #### Distribucion horaria Distribuye el envío uniformemente en un período de tiempo con el campo `sendOverHours`. **Ejemplo:** 3,000 emails distribuidos en 6 horas = ~500 emails por hora. #### Variables por destinatario (CSV) Personaliza cada email subiendo un CSV con columnas: ```csv email,nombre,codigo_descuento,producto juan@ejemplo.com,Juan,JUAN20,Plan Pro maria@ejemplo.com,Maria,MARIA15,Plan Business ``` Las variables del CSV tienen prioridad sobre los valores por defecto. --- ## Warming de dominio Para envíos grandes (>50,000 emails) desde un dominio nuevo o con poco historial, envía gradualmente: | Día | Volumen sugerido | |---|---| | Día 1 | ~50,000 | | Día 2 | ~75,000 | | Día 3+ | Resto | Usa el **modo por lotes** para automatizar esto. Enviar todo de golpe desde un dominio frío puede resultar en clasificación como spam por Gmail, Outlook y otros proveedores. Ver más en [Deliverability y autenticación](/concepts/deliverability). --- ## Estados de una campana | Estado | Descripción | |---|---| | `initializing` | Creando registros de destinatarios (transitorio, típicamente 2-30 segundos) | | `pending` | Lista para ser procesada | | `processing` | Enviando lotes activamente | | `paused` | Pausada manualmente por el usuario | | `rate_limited` | Pausada automáticamente por límite de tasa | | `completed` | Todos los emails fueron procesados | | `failed` | La campaña terminó con errores | | `cancelled` | Cancelada por el usuario | # Dominios (/api-reference/domains) Endpoints de ReallyQuickEmails para la gestión completa de dominios de envío: registro, verificación DNS, consulta de estado y configuración de perfil de remitente. **Base URL:** `https://api.reallyquickemails.com` --- ## Autenticacion Todos los endpoints de dominios requieren autenticación mediante Secret Key: | Header | Tipo | Requerido | Descripción | | --------------- | ------ | --------- | --------------------------------- | | `Authorization` | string | Sí | `Bearer sk_proj_...` | | `Content-Type` | string | Sí* | `application/json` (en POST/PUT). | --- ## POST /domains/register Registra un nuevo dominio de envío: crea la identidad de envío, genera los registros DNS necesarios y crea el perfil de remitente asociado. ### Request Body | Campo | Tipo | Requerido | Descripción | | -------------- | ------ | --------- | ------------------------------------------------------------------ | | `domain` | string | Sí | Dominio a registrar (ej. `mitienda.com`). | | `sender_name` | string | Sí | Nombre del remitente (ej. `Mi Tienda`). | | `sender_email` | string | Sí | Dirección de correo del remitente. Debe terminar en `@{domain}` (ej. `ventas@mitienda.com`). | | `external_key` | string | No | Clave externa para integraciones de terceros. | ### EjemploGracias por registrarte.
" }' ```Adjunto encontrarás el detalle.
", "attachments": [ { "filename": "reporte.pdf", "url": "https://storage.miapp.com/reportes/2026-04-28.pdf", "contentType": "application/pdf" } ] }' ```Tenemos una oferta especial en {producto}.
", "recipients": [ { "email": "juan@ejemplo.com", "data": { "nombre": "Juan", "producto": "Plan Pro" } }, { "email": "maria@ejemplo.com", "data": { "nombre": "Maria", "producto": "Plan Business" } } ] }' ```Tenemos una oferta especial en {producto}.
', recipients: [ { email: 'juan@ejemplo.com', data: { nombre: 'Juan', producto: 'Plan Pro' } }, { email: 'maria@ejemplo.com', data: { nombre: 'Maria', producto: 'Plan Business' } }, ], }); if (error) console.error(error); else console.log('Batch encolado:', data.batch_id); ```Las noticias de esta semana...
", "scheduled_at": "2026-03-25T10:00:00Z", "recipients": [ { "email": "sub1@ejemplo.com" }, { "email": "sub2@ejemplo.com" } ] }' ```Las noticias de esta semana...
', scheduled_at: '2026-03-25T10:00:00Z', recipients: [ { email: 'sub1@ejemplo.com' }, { email: 'sub2@ejemplo.com' }, ], }); ```Tu pedido #12345 está en camino.
", "template_id": null, "variables_used": ["nombre", "pedido"] } } ``` `html_preview` se trunca a 8000 caracteres. Las variables sin valor en `data` se dejan como `{{variable}}` en el preview. ## Headers | Header | Tipo | Requerido | Descripción | | --------------- | ------ | --------- | ------------------------------------------------ | | `Authorization` | string | Sí | `Bearer sk_proj_...` / `sk_live_...` / `sk_test_...`. | | `Content-Type` | string | Sí | Debe ser `application/json`. | | `Idempotency-Key` | string | No | Key arbitraria 1-256 chars. Mismo `(project_id, key)` dentro de 24h retorna respuesta cacheada. | | `x-source` | string | No | Identificador del origen de la solicitud (`api`, `platform`, `test`). | Ver más sobre `Idempotency-Key` en [API Keys](/guides/api-keys). ## Ejemplo de Solicitud ### Envio inmediatoTu pedido #{{pedido}} está en camino.
", "subject": "Tu pedido está en camino", "recipient": "cliente@ejemplo.com", "sender": "ventas@mitienda.com", "senderName": "Mi Tienda", "data": { "nombre": "Juan", "pedido": "12345" }, "email_type": "transactional" }' ```Tu pedido #{{pedido}} está en camino.
', subject: 'Tu pedido está en camino', recipient: 'cliente@ejemplo.com', sender: 'ventas@mitienda.com', senderName: 'Mi Tienda', data: { nombre: 'Juan', pedido: '12345', }, email_type: 'transactional', }); if (error) console.error(error); else console.log('Encolado:', data.email_id); ```Tu pedido fue confirmado.
" }' ``` → webhook a `webhook_url` → payload con `is_test: false` → actividad visible en las métricas de producciónTest desde staging.
" }' ``` → webhook a `webhook_url_dev` → payload con `is_test: true` → actividad marcada como test, separada de tus métricas liveHola
", "environment": "staging" }' ```Hola
', environment: 'staging', }), }); ```Hola
', 'environment': 'staging', }, ) ```Test desde staging
" }' ``` > **Importante:** todos los endpoints de la API aceptan keys live y test. RQE determina el modo del envío únicamente por el prefijo de la key — **no** existe un parámetro `mode` ni headers especiales para forzar un modo distinto. ### Errores de prefijo inválido Si pasas una key con un prefijo desconocido (ej. `sk_dev_`), recibes `401 Unauthorized`: ```json { "error": "Invalid API key prefix. Expected sk_proj_*, sk_live_* or sk_test_*." } ``` --- ## Idempotency Los endpoints `POST /send-email` ([API avanzada](/api-reference/send-email)) y `POST /v1/send-batch` soportan idempotencia opcional vía header `Idempotency-Key`: ``` Idempotency-Key: pedido-12345-confirmacion ``` - La key es un string arbitrario de 1 a 256 caracteres. - La respuesta del primer request se cachea por **24 horas**, scoped a `(project_id, idempotency_key)`. - Si llega otro request con la misma key dentro de la ventana, RQE devuelve la **respuesta cacheada** del primer request, con header `Idempotency-Replayed: true`. - Las respuestas `5xx` no se cachean — puedes reintentar con la misma key. - Útil para retries de red sin riesgo de doble envío. Funciona idéntico en live y test. > `POST /v1/send-email` y `POST /v1/send-template-email` **no** soportan `Idempotency-Key`. Si necesitas idempotencia en envíos individuales, usa la [API avanzada](/api-reference/send-email). --- ## Dry Run Para validar payload y variables sin enviar el email, usa `POST /send-email` ([API avanzada](/api-reference/send-email), campos `recipient`/`sender`/`html`) con `dry_run: true` en el body: ```bash curl -X POST https://api.reallyquickemails.com/send-email \ -H "Authorization: Bearer sk_test_tu_test_key" \ -H "Content-Type: application/json" \ -d '{ "recipient": "destinatario@ejemplo.com", "sender": "noreply@tudominio.com", "subject": "Hola {{nombre}}", "html": "Adjuntamos tu factura de octubre.
", "attachments": [ { "filename": "factura-octubre-2026.pdf", "url": "https://storage.ejemplo.com/facturas/factura-octubre-2026.pdf" } ] }' ``` Respuesta (el correo queda encolado; los adjuntos se procesan en segundo plano): ```json { "success": true, "queued": true, "jobId": "email-1780676307345-6da66195", "email_id": "d116a543-9b13-41b4-93cf-647539018275", "activityId": "d116a543-9b13-41b4-93cf-647539018275", "message": "Email queued for sending" } ``` ### Adjunto por base64 ```bash curl -X POST https://api.reallyquickemails.com/send-email \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk_proj_xxxxxxxxxxxx" \ -d '{ "recipient": "cliente@ejemplo.com", "sender": "ventas@mitienda.com", "senderName": "Mi Tienda", "subject": "Invitación a reunión", "html": "Te invitamos a una reunion de equipo.
", "attachments": [ { "filename": "reunion.ics", "content": "QkVHSU46VkNBTEVOREFSClZFUlNJT046Mi4wCkJFR0lOOlZFVkVOVApEVFNUQVJUOjIwMjUxMDE2VDE1MDAwMFoKRFRFTkQ6MjAyNTEwMTZUMTYwMDAwWgpTVU1NQVJZOlJldW5pb24gZGUgZXF1aXBvCkVORDpWRVZFTlQKRU5EOlZDQUxFTkRBUg==", "contentType": "text/calendar" } ] }' ``` ### Multiples adjuntos ```bash curl -X POST https://api.reallyquickemails.com/send-email \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk_proj_xxxxxxxxxxxx" \ -d '{ "recipient": "cliente@ejemplo.com", "sender": "ventas@mitienda.com", "senderName": "Mi Tienda", "subject": "Documentos del proyecto", "html": "Adjuntamos el contrato y la agenda de la reunion.
", "attachments": [ { "filename": "contrato.pdf", "url": "https://storage.ejemplo.com/docs/contrato.pdf" }, { "filename": "agenda.ics", "content": "QkVHSU46VkNBTEVOREFSClZFUlNJT046Mi4wCkVORDpWQ0FMRU5EQVI=", "contentType": "text/calendar" } ] }' ``` La respuesta tiene la misma estructura `queued` del primer ejemplo. El campo `attachments` no se valida en la respuesta HTTP — revisa el estado final con la [Activity API](/api-reference/activity). # Autenticación (/guides/authentication) Todos los endpoints de la API de ReallyQuickEmails usan el mismo método de autenticación: **Bearer Token** con tu Secret Key. --- ## Bearer Token Envía tu **Secret Key** (`sk_proj_...`) en el header `Authorization`: ``` Authorization: Bearer sk_proj_tu_secret_key ``` Este patrón funciona para **todos** los endpoints: - **Envío:** `POST /v1/send-email`, `POST /v1/send-template-email`, `POST /v1/send-batch`, `POST /send-email` - **Dominios:** `POST /domains/register`, `GET /domains/:domain/dns-records`, `POST /domains/:domain/verify`, `GET /domains/:domain/status`, `GET /domains?domain=`, `POST /domains/:domain/recreate`, `DELETE /domains/:domain`, `PUT /domains/:domain/sender`, `POST /domains/verify-email`, `GET /domains/verify-email/status` - **Activity:** `GET /v1/activity`, `GET /v1/activity/:id` - **Datos:** `POST /v1/leads`, `POST /v1/events` (y el resto de la [API de leads y eventos](/api-reference/leads)) ### Ejemplo ```bash curl -X POST https://api.reallyquickemails.com/v1/send-email \ -H "Authorization: Bearer sk_proj_tu_secret_key" \ -H "Content-Type: application/json" \ -d '{ "recipient_email": "cliente@ejemplo.com", "sender_email": "ventas@tudominio.com", "subject": "Confirmación de pedido", "html_body": "Este es mi primer email.
" }' ```Este es mi primer email.
', }), }); const data = await res.json(); console.log(data); // { message: 'Email sent successfully', email_id: '...', project_id: '...' } ```Este es mi primer email.
', }, ) print(res.json()) ```Te recordamos tu cita de manana a las 15:00.
", "scheduled_at": "2026-10-16T15:00:00+09:00" }' ``` Respuesta: ```json { "success": true, "scheduled": true, "scheduled_send_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "scheduled_for": "2026-10-16T06:00:00.000Z", "scheduled_for_local": "2026-10-16T15:00:00.000+09:00", "timezone": "UTC+9", "message": "Email scheduled for October 16, 2026 at 3:00 PM GMT+9" } ``` ### Programar con lenguaje natural y zona horaria ```bash curl -X POST https://api.reallyquickemails.com/send-email \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk_proj_xxxxxxxxxxxx" \ -d '{ "recipient": "cliente@ejemplo.com", "sender": "ventas@mitienda.com", "subject": "Oferta especial", "html": "Aprovecha nuestra oferta especial de esta semana.
", "scheduled_at": "tomorrow at 3pm", "timezone": "America/Santiago" }' ``` Respuesta (valores ilustrativos — dependen del momento de la solicitud): ```json { "success": true, "scheduled": true, "scheduled_send_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "scheduled_for": "2026-06-06T19:00:00.000Z", "scheduled_for_local": "2026-06-06T15:00:00.000-04:00", "timezone": "America/Santiago", "message": "Email scheduled for June 6, 2026 at 3:00 PM GMT-4" } ``` ### Programar con offset numerico El campo `timezone` solo acepta nombres IANA. Para programar con un offset UTC fijo, especifica el offset directamente en el timestamp ISO 8601: ```bash curl -X POST https://api.reallyquickemails.com/send-email \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk_proj_xxxxxxxxxxxx" \ -d '{ "recipient": "cliente@ejemplo.com", "sender": "ventas@mitienda.com", "subject": "Seguimiento", "html": "Hacemos seguimiento de tu solicitud.
", "scheduled_at": "2026-10-20T09:00:00-03:00" }' ``` Respuesta: ```json { "success": true, "scheduled": true, "scheduled_send_id": "c3d4e5f6-a7b8-9012-cdef-123456789012", "scheduled_for": "2026-10-20T12:00:00.000Z", "scheduled_for_local": "2026-10-20T09:00:00.000-03:00", "timezone": "UTC-3", "message": "Email scheduled for October 20, 2026 at 9:00 AM GMT-3" } ``` # SDK de Node.js (/guides/sdk-nodejs) SDK oficial de ReallyQuickEmails para Node.js / TypeScript. Cubre los 5 módulos de la API: `emails`, `leads`, `automations`, `events` y `senders`. - **Repo:** [github.com/DropoutCapital/reallyquickemails-sdk](https://github.com/DropoutCapital/reallyquickemails-sdk) - **npm:** [@reallyquickemails/sdk](https://www.npmjs.com/package/@reallyquickemails/sdk) - **License:** MIT - **Tamaño:** ~13 KB CJS / ~12 KB ESM, zero runtime deps (usa `fetch` nativo)Hola desde RQE
', }); if (error) { console.error('Send falló:', error.message); } else { console.log('Email encolado, id:', data.email_id); } ``` El sender debe estar verificado en tu proyecto. Ver [dominios](/api-reference/domains).Tu pedido fue recibido.
', attachments: [{ filename: 'factura.pdf', content: base64Pdf }], }); // Broadcast — un template a hasta 10,000 recipients await rqe.emails.sendBatch({ sender: 'noreply@brand.com', subject: 'Hola {{name}}', html: 'Hola {{name}}, tu plan {{plan}} se renueva pronto.
', recipients: [ { email: 'a@x.com', data: { name: 'Alice', plan: 'Pro' } }, { email: 'b@x.com', data: { name: 'Bob', plan: 'Free' } }, ], }); ``` ### `leads` — gestión de contactos ```ts // Upsert por email await rqe.leads.upsert({ email: 'user@example.com', data: { plan: 'pro', signup_date: '2026-04-30' }, segment_ids: ['seg-uuid-1'], }); // Bulk hasta 1000 await rqe.leads.upsertMany({ leads: [ { email: 'a@x.com', data: { plan: 'pro' } }, { email: 'b@x.com' }, ], }); // Tags por email await rqe.leads.addTags('user@example.com', ['vip', 'beta']); const { data } = await rqe.leads.getTags('user@example.com'); // Atributos custom (body raw, NO wrapped) await rqe.leads.setAttributes('user@example.com', { total_orders: 42, last_order_date: '2026-04-30', }); // Listar con paginación const page = await rqe.leads.list({ page: 1, per_page: 50, search: 'pro' }); // Get / update / delete por UUID const { data: { lead } } = await rqe.leads.get('lead-uuid'); await rqe.leads.update('lead-uuid', { data: { plan: 'enterprise' } }); await rqe.leads.delete('lead-uuid'); ``` ### `automations` — disparar flujos ```ts // Enroll idempotente — pasa trigger_reference_id para que múltiples calls // con el mismo external_id NO creen runs duplicados, sino que actualicen el run existente await rqe.automations.enroll('automation-uuid', { email: 'user@example.com', data: { first_name: 'Adriano', plan: 'pro' }, trigger_reference_id: 'order-12345', }); // Manual enroll (UI-style bulk) await rqe.automations.manualEnroll('automation-uuid', { emails: ['a@x.com', 'b@x.com'], }); // Cancel por recipient await rqe.automations.cancel('automation-uuid', { email: 'user@example.com', reason: 'payment_succeeded', }); // Cancel por external_id (cuando no tienes el email) await rqe.automations.cancelByExternalId('automation-uuid', { external_id: 'order-12345', }); ``` ### `events` — tracking custom ```ts // Single event await rqe.events.track({ email: 'user@example.com', event: 'offer_accepted', properties: { offer_id: 'xyz', amount_usd: 5000 }, }); // Bulk hasta 1000 await rqe.events.bulk([ { email: 'a@b.com', event: 'page_view', properties: { url: '/pricing' } }, { email: 'c@d.com', event: 'page_view', properties: { url: '/features' } }, ]); // Listar con filtros + paginación offset const { data } = await rqe.events.list({ email: 'user@example.com', event: 'offer_accepted', since: '2026-04-01T00:00:00Z', per_page: 50, }); ``` ### `senders` — listar / verificar remitentes (v0.1.1+) Permite descubrir programáticamente qué senders están verificados en el proyecto, verificar nuevos vía magic link y consultar el estado actual de un remitente....
', }); // Verificar un sender nuevo (se envía un magic link al email) await rqe.senders.verifyEmail({ email: 'support@brand.com' }); // Check status de un sender específico const { data: st } = await rqe.senders.status('support@brand.com'); // st.status: 'Success' | 'Pending' | 'Failed' | 'NotStarted' ``` Casos de uso típicos: - **Multi-tenant** — listar senders del proyecto activo y elegir según el cliente - **Failover** — si un sender falla DKIM/DMARC, fallback automático a otro verified - **Onboarding wizard** — UI muestra senders disponibles para que el cliente elija - **Health check** — poll periódico de status para detectar senders fallados ## Idempotency Para retries seguros (timeouts de red, reintentos de Lambda, etc), pasa `idempotencyKey` en operaciones de escritura. Hoy soportado en `emails.send` y `emails.sendBatch`. ```ts await rqe.emails.send( { sender: 'soporte@brand.com', recipient: 'user@example.com', subject: 'Pedido #123', html: 'Confirmación
', }, { idempotencyKey: 'order-123-confirmation' }, ); ``` El backend cachea por 24h la respuesta para el par `(project_id, idempotencyKey)`. Una segunda llamada con la misma key devuelve la respuesta cacheada **sin re-procesar** y agrega el header `Idempotency-Replayed: true`. ```ts const { data, headers } = await rqe.emails.send( { /* ... */ }, { idempotencyKey: 'unique-id' }, ); if (headers['idempotency-replayed'] === 'true') { console.log('Replay — backend ya había procesado este request'); } ```Tu cuenta en {{empresa}} ha sido creada exitosamente.
``` **En el request body (API pública v1):** ```json { "template_id": "550e8400-e29b-41d4-a716-446655440000", "variables": { "nombre": "Carlos", "empresa": "TechStore" } } ``` **Resultado renderizado:** ```htmlTu cuenta en TechStore ha sido creada exitosamente.
``` En `/v1/send-template-email` (campo `variables`), además: - `{nombreVariable}` con llave simple es equivalente a `{{nombreVariable}}`. - Puedes definir un fallback con `{variable || "valor por defecto"}` para cuando la variable no existe. - Los valores se escapan como HTML por defecto; usa `{!variable}` para insertar HTML sin escapar. - Las variables sin valor (y sin fallback) se renderizan como cadena vacía. En la API avanzada (`/send-email`, campo `data`) usa siempre la sintaxis de doble llave `{{variable}}`; la llave simple y los fallbacks `||` no están disponibles, y los valores se insertan sin escape HTML. --- ### Condicionales > Disponibles solo en la **API avanzada** (`POST /send-email`, campo `data`). `/v1/send-template-email` no soporta bloques `{{#if}}`. Usa `{{#if variable}}...{{/if}}` para mostrar contenido solo cuando una variable existe y es truthy. Combina con `{{else}}` para manejar el caso contrario. **En el template:** ```html {{#if premium}}Gracias por ser miembro Premium. Tienes envío gratuito en todos tus pedidos.
{{else}}Actualiza a Premium para obtener envío gratuito.
{{/if}} ``` **En el request body:** ```json { "data": { "premium": true } } ``` > **Nota:** Handlebars evalúa como falsy los valores `false`, `undefined`, `null`, `""`, `0` y arrays vacíos `[]`. --- ### Loops Usa `{{#each arreglo}}...{{/each}}` para iterar sobre una lista de elementos. Los loops están disponibles en ambas APIs, con diferencias de sintaxis dentro del bloque. **En la API avanzada** (`/send-email`, campo `data`), `{{this}}` referencia al elemento actual, puedes acceder a sus propiedades con `{{this.propiedad}}` y usar helpers: ```html| Producto | Cantidad | Precio |
|---|---|---|
| {{this.nombre}} | {{this.cantidad}} | {{formatCurrency this.precio}} |
Total: {{formatCurrency total}}
``` Con `"total": 61970` produce: `$61,970.00` Para usar otra moneda: ```htmlTotal: {{formatCurrency total "CLP"}}
``` ### `multiply` Multiplica dos valores numéricos. El resultado se redondea a 2 decimales. ```htmlSubtotal: {{formatCurrency (multiply cantidad precio)}}
``` ### `formatDate` Formatea una fecha ISO 8601 a un formato legible en español (`es-ES`). Acepta un segundo parámetro opcional para el formato. **Formato por defecto (`short`):** ```htmlFecha: {{formatDate fechaEntrega}}
``` Con `"fechaEntrega": "2025-03-15T00:00:00Z"` produce: `15/3/2025` **Formato largo (`long`):** ```htmlFecha: {{formatDate fechaEntrega "long"}}
``` Con `"fechaEntrega": "2025-03-15T00:00:00Z"` produce: `sábado, 15 de marzo de 2025` ### `default` Proporciona un valor por defecto cuando la variable es falsy (`undefined`, `null`, `""`, `0` o `false`). ```htmlHola, {{default nombre "Cliente"}}!
``` Si `nombre` no está definido en `data`, se renderiza como `Hola, Cliente!`. ### `json` Serializa un objeto a formato JSON con indentación. Útil para depuración o para incluir datos estructurados en el correo. ```html{{json datos}}
```
Con `"datos": {"clave": "valor"}` produce:
```json
{
"clave": "valor"
}
```
También están disponibles los helpers de texto `capitalize`, `uppercase` y `lowercase` para transformar strings.
---
## Identificar un template
Puedes referenciar un template de dos formas en la API pública (`/v1/send-template-email`):
| Campo | Tipo | Descripción |
|---|---|---|
| `template_id` | string (UUID) | UUID único del template |
| `template_internal_id` | number | ID interno auto-incrementado (por proyecto) |
```json
{
"template_id": "550e8400-e29b-41d4-a716-446655440000",
"recipient_email": "usuario@ejemplo.com",
"sender_email": "tienda@tudominio.com",
"variables": {
"nombre": "Ana",
"numeroPedido": "5021"
}
}
```
- Si el template no existe, la API retorna un error `404`.
- `template_internal_id` se resuelve dentro del proyecto asociado a la API key utilizada.
En la API avanzada (`/send-email`), el campo `templateId` acepta el UUID del template o su ID interno numérico, siempre dentro del proyecto asociado a la API key.
---
## El objeto `variables`
En `/v1/send-template-email`, el objeto `variables` es un JSON donde cada clave corresponde a una variable del template. Los valores se convierten a string al renderizar; las variables que el template usa pero que no existen en `variables` se renderizan como cadena vacía (o con su fallback `||` si está definido).
### Ejemplo completo
**Request:**
```bash
curl -X POST https://api.reallyquickemails.com/v1/send-template-email \
-H "Authorization: Bearer sk_proj_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"template_id": "550e8400-e29b-41d4-a716-446655440000",
"recipient_email": "juan.perez@ejemplo.com",
"sender_email": "pedidos@mitienda.com",
"variables": {
"nombre": "Juan",
"numeroPedido": "3847",
"fechaCompra": "20 de febrero de 2026",
"productos": [
{ "producto": "Audifonos Bluetooth", "cantidad": 1, "precio": "$34.990" },
{ "producto": "Cable USB-C", "cantidad": 3, "precio": "$5.990" }
],
"total": "$52.960"
}
}'
```
**Template correspondiente (sintaxis v1):**
```html
Pedido #{{numeroPedido}} - {{fechaCompra}}
| {{producto}} | x{{cantidad}} | {{precio}} |
Total: {{total}}
Atendido por: {vendedor || "nuestro equipo"}
``` Como este motor no incluye helpers de formateo, los precios y fechas se envían ya formateados como strings. Si necesitas condicionales, helpers o propiedades anidadas, usa la API avanzada (`POST /send-email`) con `templateId` + `data`. --- ## Acceso a propiedades anidadas En la API avanzada (`/send-email`, campo `data`), usa notación de punto para acceder a objetos anidados: ```htmlCiudad: {{direccion.ciudad}}
Región: {{direccion.region}}
``` ```json { "data": { "direccion": { "ciudad": "Santiago", "region": "Metropolitana" } } } ``` En `/v1/send-template-email` la notación de punto **no** está soportada: aplana las variables antes de enviarlas (por ejemplo, `direccion_ciudad` en lugar de `direccion.ciudad`). --- ## Buenas practicas - **Define valores por defecto para variables opcionales:** en la API avanzada usa el helper `default`; en v1 usa fallbacks `{variable || "valor"}`. Así evitas espacios vacíos en el correo. - **Valida tus variables antes de enviar.** Si un template espera variables que no existen en el objeto enviado, se renderizan como cadenas vacías sin producir error. - **Guarda el `template_id` (UUID) en tu configuración**, o usa `template_internal_id` si prefieres identificadores numéricos cortos por proyecto. - **Prueba tus templates** con datos de ejemplo antes de integrar. En la API avanzada puedes usar `dry_run: true` para renderizar el correo sin enviarlo — ver [Dry Run](/api-reference/send-email#dry-run).