Plantillas, reglas y webhooks
Cada comando de `templates`, `rules` y `webhooks`: cuerpos guardados que envías por slug, reglas que archivan el correo entrante y eventos firmados para tu propio servidor.
Tres espacios de nombres
Estos tres espacios de nombres permiten que un buzón funcione sin que nadie lo vigile. templates guarda cuerpos que envías muchas veces, rules archiva el correo a medida que llega y webhooks le cuenta a tu propio servidor lo que ha pasado. Cada comando es un método del SDK con su nombre en kebab-case, así que webhooks.rotateSecret es openemail webhooks rotate-secret, y lee argumentos y opciones como cualquier otro comando de recurso.
| Espacio de nombres | También | Las lecturas necesitan | Los cambios necesitan |
|---|---|---|---|
| templates | template | templates:read | templates:write, y además emails:send para send |
| rules | rule | rules:read, incluido test | rules:write |
| webhooks | webhook | webhooks:read | webhooks:write, incluidos test y replay-delivery |
Esta página lista cada comando y lo que conviene saber antes de usarlo en un script. Para cada argumento y opción, con su tipo, los scopes que necesita, su endpoint y lo que devuelve, ejecuta openemail <namespace> <verb> --help. Añade --json para obtener la misma página como JSON.
openemail templates --helpopenemail rules create --helpopenemail webhooks replay-delivery --help --jsonPlantillas
Cuerpos guardados una vez y enviados muchas, con versiones, previsualizaciones y props tipadas. Cada comando que acepta <id-or-slug> admite el id tpl_ o el slug. El slug nunca cambia cuando se renombra la plantilla, así que fija el slug en los scripts.
| Comando | Qué hace |
|---|---|
| openemail templates list | Listar plantillas, primero las actualizadas más recientemente. --status deja las de borrador, activas o archivadas, --search busca en nombres, slugs y asuntos, y --sort elige el orden |
| openemail templates get <id-or-slug> | Leer una plantilla con su versión head completa, cuerpo incluido |
| openemail templates create --name <value> | Crear una plantilla y su primera versión. Queda como borrador salvo que pases --publish, y --starter la inicia a partir de un diseño inicial |
| openemail templates update <id-or-slug> | Editar el nombre, el slug, la descripción o el estado, o el cuerpo del borrador. Los envíos mantienen la versión publicada hasta que publiques |
| openemail templates duplicate <id-or-slug> | Copiar la versión head en una plantilla nueva, que empieza como borrador |
| openemail templates replace-content <id-or-slug> | Sustituir el cuerpo por el de un diseño inicial (--starter) o el de otra plantilla (--from-template-id). Pide que confirmes |
| openemail templates delete <id-or-slug> | Eliminar una plantilla y todas sus versiones. Pide que confirmes |
| openemail templates list-versions <id-or-slug> | Listar las versiones, de la más reciente a la más antigua, sin sus cuerpos |
| openemail templates get-version <id-or-slug> <version> | Leer una versión con su cuerpo, sin tocar el borrador |
| openemail templates publish <id-or-slug> | Publicar el borrador para que los envíos lo usen. Publicar una versión head que ya está publicada no cambia nada |
| openemail templates restore-version <id-or-slug> <version> | Recuperar el cuerpo de una versión anterior como borrador. Pide que confirmes |
| openemail templates delete-version <id-or-slug> <version> | Eliminar una versión. Se rechazan la versión publicada, la versión head y la única versión. Pide que confirmes |
| openemail templates list-starters | Listar los diseños iniciales integrados |
| openemail templates get-starter <slug> | Leer un diseño inicial completo, con su árbol de bloques y una previsualización renderizada |
| openemail templates list-fonts | Listar las fuentes web que puede cargar una plantilla |
| openemail templates render | Renderizar un cuerpo que no está guardado en ninguna parte, a partir de --html o --document |
| openemail templates preview <id-or-slug> | Renderizar una plantilla guardada con --props y --slots, borradores incluidos, sin enviarla |
| openemail templates get-analytics <id-or-slug> | Envíos, aperturas y clics en un periodo, por día, por origen y por versión |
| openemail templates list-sends <id-or-slug> | Los mensajes individuales que envió la plantilla, del más reciente al más antiguo, una página cada vez |
| openemail templates send <id-or-slug> --from <value> --to <a,b> | Enviar un correo renderizado a partir de la versión publicada, o de la que fija --template-version |
Una plantilla tiene una versión head, que es un borrador mientras tenga ediciones sin publicar, y una versión publicada, que es la que usa un envío sin --template-version. create sin --publish, una edición del cuerpo con update, replace-content y restore-version escriben todos en el borrador, así que los destinatarios no ven nada nuevo hasta publish.
- Una plantilla archivada se niega a enviar con
template_archived.publishla vuelve a activar. - Un espacio de trabajo tiene como máximo 200 plantillas, archivadas incluidas, así que eliminar es la única forma de hacer sitio.
deletese rechaza contemplate_in_usemientras un envío masivo programado o en cola siga nombrando la plantilla.
Reglas
Condiciones y acciones evaluadas sobre el correo entrante, en el orden que muestra rules list. Una regla solo actúa sobre el correo que llega mientras está activada. Ningún comando aplica una regla al correo que ya está en el buzón, y rules test es la forma de ver lo que atraparía. Los ids de regla empiezan por rul_.
| Comando | Qué hace |
|---|---|
| openemail rules list | Listar las reglas en el orden en que se ejecutan. --enabled o --no-enabled deja un solo tipo |
| openemail rules get <id> | Leer una regla, con matchCount y lastMatchedAt |
| openemail rules create --name <value> --conditions <json|@file|-> --actions <json|@file|-> | Crear una regla al final del orden. Queda activada salvo que pases --no-enabled |
| openemail rules update <id> | Cambiar una regla. --conditions y --actions sustituyen la lista entera, y --position mueve solo esta regla |
| openemail rules delete <id> | Eliminar una regla. Lo que ya hizo se queda en list-runs. Pide que confirmes |
| openemail rules reorder <rule-ids...> | Fijar el orden de todas las reglas a la vez, nombrando cada regla exactamente una vez |
| openemail rules test <id> | Probar una regla en simulación contra el correo que ya está en el buzón. No cambia nada y funciona con una regla desactivada |
| openemail rules list-runs | Lo que las reglas hicieron realmente con el correo entrante, de lo más reciente a lo más antiguo. --rule-id y --thread-id lo filtran |
--conditions es una lista de objetos { field, op, value }, unidos por --match all o --match any, donde value es siempre una cadena y negate: true invierte una condición. --actions es una lista de objetos { type, value }, aplicados en orden. Una regla admite de 1 a 20 condiciones y de 1 a 10 acciones, y un buzón tiene como máximo 100 reglas.
- Campos de condición:
from,from_domain,envelope_from,to,cc,bcc,recipient,reply_to,delivered_to,subject,body,header,list_id,attachment_name,attachment_type,has_attachment,attachment_size,message_size,spam,houryweekday. - Operadores:
matches,contains,equals,starts_with,ends_with,gtylt.gtyltsolo funcionan con los campos numéricos, yhas_attachmentyspamsolo aceptanequalscontrueofalse. - Tipos de acción:
label,remove_label,archive,mark_read,star,spam,trash,forward,reply,block_senderyreject.labelyremove_labelaceptan un id de etiqueta comoUSER_RECEIPTS,forwardacepta una dirección yreplyacepta un id o un slug de plantilla. from_domaintambién coincide con los subdominios, yhouryweekdayse leen en UTC, con0para el domingo.- Una regla con una acción
rejecttambién debe comprobarenvelope_from, o se rechaza conreject_needs_envelope.
Webhooks
Endpoints de tu propio servidor que reciben eventos firmados del buzón, con sus secretos de firma, su registro de entregas y un registro de auditoría de cada cambio. Los ids de endpoint empiezan por whe_ y los ids de entrega por whd_.
| Comando | Qué hace |
|---|---|
| openemail webhooks list | Listar los endpoints del espacio de trabajo, del más reciente al más antiguo, con su estado |
| openemail webhooks get <id> | Leer un endpoint. El secreto de firma nunca forma parte de una lectura |
| openemail webhooks create --url <value> | Registrar un endpoint HTTPS. Muestra el secreto de firma, la única vez que ves ese secreto |
| openemail webhooks update <id> | Cambiar la URL, los eventos, las listas de permitidos o si está activado. Cada lista sustituye a la guardada |
| openemail webhooks delete <id> | Eliminar un endpoint y su registro de entregas. Pide que confirmes |
| openemail webhooks rotate-secret <id> | Emitir un secreto de firma nuevo. El anterior deja de funcionar al momento. Pide que confirmes |
| openemail webhooks test <id> | Enviar un evento sintético firmado email.sent e informar de cómo fue la entrega |
| openemail webhooks list-deliveries <id> | Los intentos de entrega de un endpoint, del más reciente al más antiguo. --status, --since y --until los filtran |
| openemail webhooks get-delivery <id> <delivery-id> | Un intento completo: el cuerpo enviado, la respuesta de tu servidor, cada intento del evento y si se aceptaría un reenvío |
| openemail webhooks replay-delivery <id> <delivery-id> | Volver a enviar ahora al endpoint un evento guardado |
| openemail webhooks list-workspace-deliveries | Los intentos de entrega de todos los endpoints, o de los que nombra --endpoint-ids |
| openemail webhooks list-activity <id> | El registro de auditoría de un endpoint: quién lo creó, cambió, probó, reenvió o quitó |
| openemail webhooks list-workspace-activity | El registro de auditoría de todos los endpoints, incluidos los quitados |
Si omites --event-types, un endpoint recibe el conjunto predeterminado, los eventos email.* salvo email.replied. email.replied, los eventos domain.* y los eventos suppression.* solo le llegan cuando los nombras. --address-allowlist y --domain-allowlist limitan un endpoint a algunas direcciones o dominios, igual que limitan una clave de API.
- Un espacio de trabajo tiene 10 endpoints salvo que el soporte haya subido su límite.
- Un endpoint que falla 100 entregas seguidas lo desactiva el servidor, y
webhooks update <id> --enabledlo recupera. - Con un inicio de sesión con el navegador, solo el propietario del espacio de trabajo puede leer una entrega con
get-delivery. Cualquier otra persona recibeowner_onlyy el código de salida4.
Comprobar una plantilla y luego publicarla
templates preview renderiza exactamente lo que produciría un envío con los mismos valores, borradores incluidos, y solo necesita templates:read, así que incluso una clave de solo lectura puede ejecutarlo. Informa de una prop obligatoria que falta como aviso donde send la rechazaría, así que haz fallar la compilación ante cualquier aviso. publish es seguro en cada despliegue, porque publicar una versión head que ya está publicada no cambia nada.
draft=$(openemail templates get order-shipped --json | jq .latestVersion)openemail templates preview order-shipped --template-version "$draft" \ --props '{"orderId":"AC-4192","customer":"Ada"}' --json | jq -e '.warnings == []'openemail templates publish order-shippedEnviar desde una plantilla
Fija la versión, para que una reescritura publicada mañana no cambie lo que envía este código, y pasa una clave de idempotencia tomada de lo que causó el envío, para que un reintento tras perder la respuesta reproduzca el primer mensaje en lugar de enviar un segundo. --dry-run muestra el método, la URL, las cabeceras con tu credencial ocultada y el cuerpo, no envía nada y sale con el código 0. Vuelve a ejecutarlo sin --dry-run para enviar.
openemail templates send order-shipped \ --from 'Acme <[email protected]>' \ --to [email protected] \ --template-version 5 \ --props '{"orderId":"AC-4192","customer":"Ada"}' \ --idempotency-key order-shipped:AC-4192 \ --dry-runProbar una regla antes de que se ejecute
Crea la regla desactivada, pruébala en simulación contra el correo reciente y actívala cuando atrape lo que querías. Con un inicio de sesión con el navegador, rules create y rules update piden un código de verificación, que un script no puede escribir, así que ejecuta primero openemail verify. Durante los 60 minutos siguientes ese perfil los ejecuta sin preguntar.
[ { "field": "from_domain", "op": "equals", "value": "stripe.com" }, { "field": "has_attachment", "op": "equals", "value": "true" }][ { "type": "label", "value": "USER_RECEIPTS" }, { "type": "archive" }]openemail verifyrule=$(openemail rules create --name 'Stripe receipts' \ --conditions @conditions.json --actions @actions.json --no-enabled --json | jq -r .id)openemail rules test "$rule" --days 30 --limit 100openemail rules update "$rule" --enabledLee los avisos de rules test antes que sus coincidencias. field_unevaluable significa que una condición lee algo que el correo guardado ya no lleva, así que la prueba no pudo juzgarla, y forward_unverified significa que un destino de reenvío no está alojado aquí. wouldApply lista lo que declara la regla: un reenvío a una dirección que no ha confirmado sigue fallando cuando llega correo real.
Poner una regla la primera y ver por qué se movió un mensaje
rules reorder acepta cada regla del buzón exactamente una vez. Una regla omitida o nombrada dos veces se rechaza y no se mueve nada. rules list devuelve los ids en el orden en que se ejecutan, así que pon la que quieras primero delante del resto.
first=rul_4f1c9a2b7d3e8f6a0b5c1d2eopenemail rules reorder "$first" $(openemail rules list --all --ndjson \ | jq -r --arg first "$first" 'select(.id != $first) | .id')openemail rules list-runs --thread-id CAHk7pQ2x9LmZ4 --json | jq '.items[] | {ruleName, actions, failures}'list-runs es el registro de lo que pasó realmente. Cada fila es una regla que coincidió con un mensaje, con las acciones que se aplicaron y, en failures, las que el buzón rechazó, como una respuesta a un remitente al que ya se respondió ese día. Cada fila conserva el nombre que tenía la regla en ese momento, así que --rule-id funciona con una regla que ya has eliminado.
Registrar un webhook y demostrar que funciona
webhooks create muestra el secreto de firma una vez, y ningún comando posterior lo vuelve a mostrar. Con --json va en el JSON de stdout, mientras que el recordatorio de guardarlo va a stderr, así que la salida se sigue pudiendo analizar. webhooks test envía un evento sintético firmado email.sent sea cual sea la suscripción del endpoint, y no se envía ningún correo.
openemail verifyopenemail webhooks create --url https://hooks.acme.com/openemail \ --event-types email.received,email.bounced,email.complained \ --description 'Support desk sync' --json > endpoint.jsonjq -r .secret endpoint.jsonopenemail webhooks test "$(jq -r .id endpoint.json)" --json | jq .deliveryrm endpoint.jsonGuarda el secreto en tu almacén de secretos antes de eliminar el archivo. test sale con el código 0 incluso cuando tu servidor falla, así que lee delivery.status: delivered para una respuesta 2xx y failed para cualquier otra, incluida una redirección, ya que las redirecciones nunca se siguen. Un responseCode de null significa que no llegó ninguna respuesta.
Buscar entregas fallidas y volver a enviar una
Tras una caída en tu lado, lista lo que falló en todos los endpoints, comprueba que se aceptaría un reenvío y vuelve a enviar el evento. Un reenvío lleva el mismo id de evento, así que un receptor que descarta los ids que ya ha procesado lo trata como el evento que ya conoce.
openemail webhooks list-workspace-deliveries --status failed --since 2026-09-26T00:00:00Z --all --ndjson \ | jq -r '[.endpointId, .id, .eventType, (.responseCode // "no answer")] | @tsv'openemail webhooks get-delivery whe_3f9c2a7b1e4d8f60a5c7b92d whd_8c1e4a7f2b9d3e6a0c5f1b28 --json | jq .replayRefusalopenemail webhooks replay-delivery whe_3f9c2a7b1e4d8f60a5c7b92d whd_8c1e4a7f2b9d3e6a0c5f1b28--sincey--untilaceptan un instante ISO 8601.- Una fila fallida cuyo
nextAttemptAttiene una hora aún tiene pendiente un reintento automático. replayRefusalesnullcuando un reenvío saldría, y si no, nombra por qué se rechazaría, comowebhook_disabledmientras el endpoint está desactivado.- Los reenvíos van de uno en uno. Ningún comando vuelve a enviar todas las entregas fallidas.
Códigos de verificación
Con un inicio de sesión con el navegador, cuatro de estos comandos piden un código de verificación antes de cambiar nada, como hace la app web: rules create, rules update, webhooks create y webhooks update. A una clave de API nunca se le pide. Todos los demás comandos de esta página se ejecutan sin código, incluidas las eliminaciones y webhooks rotate-secret.
- En un terminal, la CLI te envía por correo un código de seis dígitos, o te pide uno de tu app de autenticación cuando el inicio de sesión en dos pasos está activado, y luego ejecuta el comando una vez.
- Sin supervisión, con
--jsono--no-input, en CI o sin terminal, nadie puede escribir el código, así que el comando se detiene con el código de salida4y no cambia nada. Ejecuta primeroopenemail verify, y el perfil no necesita código durante 60 minutos. --yesconfirma una eliminación, pero nunca se salta un código.
Confirmaciones y simulaciones
Siete comandos de esta página quitan o sobrescriben algo, así que primero piden que confirmes: templates delete, templates delete-version, templates replace-content, templates restore-version, rules delete, webhooks delete y webhooks rotate-secret. Sin supervisión, cada uno se detiene con el código de salida 2 salvo que pases --yes.
$ openemail webhooks delete whe_3f9c2a7b1e4d8f60a5c7b92d --no-input✗ Refusing to run unattended. Pass --yes to confirm.$ openemail webhooks delete whe_3f9c2a7b1e4d8f60a5c7b92d --yes--dry-run muestra la primera solicitud que cambiaría algo y sale con el código 0, sin enviarla ni pedirte que confirmes. Con --json muestra un único documento { dryRun, request }. rules test, templates render y templates preview no cambian nada, pero son solicitudes POST, así que una simulación las muestra en lugar de ejecutarlas.
Paginación
templates list,templates list-versions,rules list,rules list-runsy cada comandowebhooks list…leen una página cada vez, de 25 filas salvo que--limitpida hasta 100. Un terminal muestra el--cursorque hay que pasar para la página siguiente.--alllee todas las páginas,--max <n>se detiene tras esa cantidad de filas, y--ndjsonmuestra un objeto JSON por línea. Con--json, una lista muestra un único documento{ items, hasMore, nextCursor }, también con--all.- Devuelve un cursor con los mismos filtros y el mismo orden con los que vino. Cualquier otra cosa se rechaza como
invalid_cursor, con el código de salida7. templates list-sendspagina por número, con--pagey--page-size, informa deltotaly no tiene--all. Los números de página se desplazan mientras sale correo, así que acota el periodo con--dayso--minutesen lugar de paginar muy lejos.templates list-startersytemplates list-fontsdevuelven el catálogo entero de una vez, yrules reorderdevuelve cada regla como una lista simple en su nuevo orden.- Un buzón tiene como máximo 100 reglas, así que
rules list --limit 100siempre devuelve todas las reglas en una sola página.
Opciones que merecen una segunda mirada
--template-versiones el campoversiondel cuerpo, renombrado porque--versionmuestra la versión de la CLI. El argumento<version>deget-version,restore-versionydelete-versiones un número de versión, no un idtplv_.--conditions,--actions,--document,--slots,--propsy las demás opciones JSON aceptan JSON en línea, desde un archivo con@patho desde stdin con-.--dataacepta el cuerpo entero de la misma forma, y cualquier opción que pases además reemplaza su clave.--htmlacepta el marcado en sí, no un archivo, así que--html @page.htmlenvía el texto@page.html. Pasa--html "$(cat page.html)", o ponhtmlen el archivo que das a--data.rules update --conditionsy--actionssustituyen la lista entera, y lo mismo hacenwebhooks update --event-types,--address-allowlisty--domain-allowlist. Lee el valor actual, cámbialo y envíalo entero.- Un
--event-typesvacío es un error de uso. Para devolver un endpoint al conjunto predeterminado, envía--data '{"eventTypes":[]}', y para detener sus entregas, pasa--no-enabled. --expected-versionentemplates update,replace-contentyrestore-versionacepta la versión head que leíste. Cuando otra persona ha movido la versión head entretanto, el comando se detiene con el código de salida6yversion_conflict, y no escribe nada.rules update <id> --no-enableddesactiva una regla y conserva su lugar en el orden, que es la forma de pausar una regla sin eliminarla.