Ir a la documentación
API

Conversaciones

Leer y organizar el correo.

GETapi.openemail.uk/threads

Ejecuta cualquiera de las 7 llamadas de esta página contra tu espacio de trabajo, con tu propia clave.

Listado

GET /threads?folder=inbox. Pasar query busca en el mismo índice local. Las palabras sueltas deben aparecer todas, y cada una coincide de forma laxa, ignorando mayúsculas, acentos y separadores, así que min encuentra "Benjamin". Una frase entrecomillada coincide tal cual está escrita salvo por mayúsculas y acentos, así que "ben jamin" no encuentra "Ben-Jamin". Las palabras de relleno como the o emails se descartan de una lista de palabras sueltas cuando queda algo más que buscar. Operadores como from:, to:, subject:, label:, is:unread, has:pdf, after:2026/01/31 y newer_than:7d la acotan, y OR, los paréntesis y un - inicial los combinan. Los destinatarios se guardan como una sola lista sin roles y nunca contienen un Bcc, así que cc: lee el mismo campo que to: y bcc: no coincide con nada propio. from:me es el correo que enviaste, y to:me es el correo que lleva una de tus propias direcciones, alias incluidos, entre sus destinatarios o como dirección a la que se entregó.

Las palabras y los operadores from:, to:, cc:, subject: y body: leen el mensaje más nuevo de cada conversación: su remitente, sus destinatarios, su asunto y los primeros 4.000 caracteres de su cuerpo. filename: y has: leen todos los adjuntos de la conversación entera, y label:, in: y is: leen la conversación entera. folder sigue aplicándose salvo que la consulta nombre una carpeta con in:, o con un is: que sea una carpeta como is:sent, y in:anywhere busca en todas las carpetas, tanto por sí solo como junto a otros términos. Un listado de borradores es la excepción y se queda en borradores diga lo que diga la consulta.

Un valor que la búsqueda no puede usar se ignora en lugar de acotar, así que una errata en un valor amplía el resultado en vez de vaciarlo: category:, larger:, smaller:, size:, messagesize:, list:, rfc822msgid:, received:, sent:, las palabras de categoría como is:promotions, una palabra de has: que no nombre ningún tipo de adjunto, un importance: distinto de high o low, una fecha ilegible y una duración cuya unidad no sea h, d, w, m o y. Un nombre de operador que no conoce, project: por ejemplo, se busca como texto plano. Las fechas leen la actividad más reciente de la conversación, en UTC, con after: incluyendo el día que nombra y before: excluyéndolo; escribe una como YYYY/MM/DD, YYYY-MM-DD, YYYYMMDD, un año a secas, o segundos o milisegundos de epoch.

nextPageToken es opaco. Devuelve exactamente lo que te dieron; nunca construyas ni edites uno. Su forma no forma parte del contrato.

Recuperación

GET /threads/{id} devuelve todos los mensajes de la conversación, no solo el más reciente, junto con sus etiquetas y si algo en ella está sin leer.

Mensajes que llegaron cifrados

Esta API ni cifra ni descifra. No puede abrir un mensaje que otra persona cifró, y no puede enviar uno cifrado. Una petición que lleva un marcador de cifrado se rechaza con un 422, porque las únicas superficies que pueden ponerlo son las que tienen las claves, y ningún cliente de la API tiene una clave. Lo que sí hace es RECONOCER un sobre sellado a la entrada, a partir del Content-Type de nivel superior y de nada más, y después decirlo en el mensaje.

OpenEmail sí guarda claves ahora, y vale la pena ser exactos sobre cuál mitad y dónde. El dueño de un buzón genera una identidad OpenPGP en su navegador y publica la clave PÚBLICA en un directorio que otros remitentes de OpenEmail autenticados pueden resolver. La mitad privada se crea en ese navegador, nunca se envía aquí y nunca es recuperable, así que nada en esta API puede descifrar nada, y ninguna petición de soporte, requerimiento judicial o copia de seguridad nuestra produce una clave que pudiera hacerlo. La aplicación web ya puede ABRIR un mensaje PGP/MIME o PGP en línea cuando la clave está en el navegador de quien lee, pero ese descifrado ocurre en la pestaña y su texto claro nunca se escribe de vuelta: el mensaje almacenado sigue siendo texto cifrado, y ninguna respuesta de esta API lleva jamás el texto abierto. La aplicación ya puede sellar un mensaje nuevo en el navegador y enviarlo: el compositor cifra hacia las claves publicadas de los destinatarios y el correo sale como PGP/MIME. Esta API sigue sin poder sellar nada, así que el campo de abajo describe tanto el correo que cifró otra persona como el correo sellado en una pestaña de OpenEmail.

Eso merece un campo por lo que había antes como alternativa. Un mensaje sellado no guarda cuerpo legible, así que decodedBody vuelve como "", los mismos bytes que un mensaje que realmente no tenía contenido. encryption es lo que te permite distinguir los dos casos antes de actuar sobre uno, y es una afirmación sobre el sobre y no una verificación: ver que un mensaje está sellado no es lo mismo que haberlo abierto.

Respuesta
{    "object": "thread",    "id": "thread_2f9b…",    "messages": [      {        "id": "msg_7c41…",        "subject": "Q3 numbers",        "decodedBody": "",        "encryption": {          "format": "pgp-mime",          "detectedAt": "2026-08-30T09:14:22.117Z",          "rawRetained": false,          "parts": [            { "index": 0, "attachmentId": "msg_7c41…-0", "role": "version" },            { "index": 1, "attachmentId": "msg_7c41…-1", "role": "ciphertext" }          ]        }      }    ]  }

encryption

format'pgp-mime' | 'pgp-signed' | 'pgp-inline' | 'smime-encrypted' | 'smime-signed'
Qué sobre llegó. Se lee del `Content-Type` de nivel superior (su parámetro `protocol` para PGP, su `smime-type` para S/MIME) o, para `pgp-inline`, de un cuerpo que empieza con la cabecera de armadura PGP. Una parte `pkcs7-mime` que no lleve ningún `smime-type` se lee como `smime-encrypted`, que es lo que RFC 8551 establece por defecto.
detectedAtstring
ISO 8601, cuándo se ejecutó el detector, que es cuándo se ingirió el mensaje aquí. No dice nada sobre cuándo se cifró el mensaje, ni por quién.
rawRetainedboolean
Si se conservaron los bytes RFC822 originales, de modo que el mensaje pudiera devolverse entero. False en todos los mensajes a día de hoy, ya que aquí todavía nada retiene correo en bruto. Está en la respuesta ya para que el día en que eso cambie no sea también el día en que haya que migrar otra vez todos los mensajes almacenados.
partsobject[]
Las partes del sobre que usa este formato. Presente siempre que lo esté `encryption`, y vacío cuando no hay ninguna que nombrar: `pgp-inline` no tiene ninguna parte separada, ya que su armadura ES el cuerpo y llega en `decodedBody`.
parts[].indexnumber
Qué parte MIME del mensaje original era esta, contada sobre las partes tal como llegaron y no sobre `attachments`. Las dos listas difieren, que es la razón entera de que esto se registre.
parts[].attachmentIdstring
El id que lleva esta parte en `attachments`, cuando aparece allí: el id del mensaje con el índice de la parte añadido. La parte `ciphertext` se lista y se descarga como cualquier otro archivo; `version` y `signature` se quedan fuera de la lista, así que sus ids correlacionan las dos vistas y nada más. El endpoint de adjuntos no las devuelve.
parts[].role'version' | 'ciphertext' | 'signature'
`version` es la parte de control de PGP/MIME, `ciphertext` es el mensaje, `signature` es una firma separada. Solo `ciphertext` merece la pena descargarse; las otras dos son mobiliario del protocolo que antes se renderizaba como adjuntos basura y ya no lo hace.
formatQué llegóCuerpo
pgp-mimeUn sobre PGP/MIME: multipart/encrypted con protocol=application/pgp-encrypted.Sellado
pgp-inlineArmadura en el propio cuerpo. Solo se lee del texto del cuerpo, así que una respuesta que simplemente cita un bloque con armadura no se confunde con uno.Sellado
smime-encryptedUna parte S/MIME pkcs7-mime con smime-type=enveloped-data, o una sin ningún smime-type.Sellado
pgp-signedUna firma PGP separada junto al mensaje: multipart/signed con protocol=application/pgp-signature.Legible
smime-signedUna firma S/MIME separada: un protocolo pkcs7-signature, o smime-type=signed-data.Legible

Firmado no es sellado, y ramificar según la presencia de encryption en lugar de según format lo entiende exactamente al revés. Una firma es una afirmación sobre quién escribió el mensaje, no una envoltura alrededor de él: el cuerpo de un mensaje firmado está en claro y se lee como cualquier otro. Trata pgp-mime, pgp-inline y smime-encrypted como ilegibles, y los dos formatos firmados como correo corriente.

Qué cambia en un mensaje sellado

Solo los tres formatos sellados cambian algo, y el cambio ocurre en la ingesta y no en esta respuesta. Todo lo que habría leído el cuerpo se aparta, en lugar de leer texto cifrado e informar de un resultado que no podría haber obtenido:

  • La búsqueda sobre el cuerpo. El mensaje se indexa con un fragmento de cuerpo vacío, así que sigue encontrándose por remitente, asunto, dirección y etiqueta, y no por nada de su interior.
  • La pasada sobre el cuerpo del evaluador de phishing. El veredicto sigue llegando y dice lo que no pudo hacer: risk.signals lleva body-encrypted y risk.aiChecked es false.
  • La comprobación de autoría por IA, que se abstiene en lugar de adivinar: aiWritten.level es unknown y aiWritten.skipped es encrypted.
  • Las condiciones sobre el cuerpo en las reglas. Las condiciones de sobre y de cabecera se ejecutan exactamente igual que antes; una regla que preguntaba por el cuerpo se registra como no evaluada en lugar de contarse como no coincidente, porque "no coincidió" y "no se pudo leer" son respuestas distintas.
  • La importación de invitaciones de calendario. La invitación está dentro del texto cifrado, y construir un evento a partir del sobre pondría una entrada equivocada en un calendario real.
  • Los resúmenes de conversación y los embeddings, para la conversación entera. Basta con una respuesta sellada. Un resumen es la lectura que un modelo hace del texto claro guardada como metadato en claro, que es el único punto de esta canalización donde un cuerpo se filtraría a un almacén que nadie considera un cuerpo.

Todo lo que no necesita el cuerpo queda intacto:

  • DMARC, DKIM y SPF. Eso se lee de Authentication-Results, que el texto cifrado no oculta, así que un mensaje cifrado sigue obteniendo un veredicto de autenticación real en lugar de ninguno.
  • El agrupado en conversaciones, el archivado de spam y la lista de bloqueo: todo trabajo de sobre y de cabecera.
  • Los adjuntos. La parte con el texto cifrado se queda en attachments, llamada encrypted-message.asc cuando llega sin nombre, y se descarga por el endpoint de abajo. Es exactamente lo que el lector de la aplicación web descarga y descifra en el navegador; para un cliente de la API, que no tiene clave, esa descarga sigue siendo la única forma de leer el correo. Ábrelo en un cliente que sí tenga una.
  • Un mensaje firmado no pierde nada de esto. Todas las comprobaciones anteriores siguen ejecutándose sobre él, y no se retiene nada, que es por lo que la lista de sellados tiene tres formatos y no cinco.

La ausencia de encryption no es una afirmación de texto claro. Significa que nadie miró: el mensaje es anterior a la detección, o llegó al buzón por una ruta que no ejecuta el detector. Nada lo rellena con efecto retroactivo, así que un campo que dice "no lo comprobamos" no debe leerse nunca como "lo comprobamos y no encontramos nada".

Marcar y etiquetar

PATCH /threads/{id} acepta read, addLabelIds y removeLabelIds. El estado de lectura es una etiqueta en todos los backends que este producto soporta, así que fijar read y mover etiquetas en una sola llamada mantiene el orden determinista.

PATCH
{ "read": true, "addLabelIds": ["USER_INVOICES"] }

TRASH y SNOOZED se rechazan aquí con label_not_directly_settable. Ninguno de los dos estados lo lleva su etiqueta sola (enviar a la papelera también limpia las etiquetas de carpeta, y un aplazamiento necesita una hora de despertar guardada junto a él), así que fijarlos a mano deja la conversación en un estado que la aplicación nunca produce y del que no puede recuperarse. Usa los endpoints de abajo.

Papelera y aplazamiento

EndpointHace
POST /threads/{id}/trashLa mueve a la Papelera, limpiando INBOX, SPAM, SNOOZED y ARCHIVE a la vez.
POST /threads/{id}/snoozeCuerpo { "wakeAt": "…" }. La oculta y programa su regreso.
POST /threads/{id}/unsnoozeLa trae de vuelta ahora y cancela el regreso programado.

Aplazar escribe dos cosas: la etiqueta que oculta la conversación y la entrada que la trae de vuelta. Hacer una sin la otra es exactamente por lo que estos son endpoints y no ediciones de etiquetas.

Adjuntos

GET /threads/{id}/messages/{messageId}/attachments devuelve cada adjunto con filename, contentType, size y content en base64. content es una cadena vacía cuando no se pudieron encontrar los bytes almacenados, así que comprueba su longitud antes de decodificar.

Un sobre cifrado no está aquí entero. El texto cifrado sí (es el mensaje, y descargarlo es la única forma en que un cliente de la API lee este correo), pero la parte de versión de PGP/MIME y cualquier firma separada se quedan fuera de la lista, porque se renderizaban como adjuntos basura y no hay nada que quien llama pueda hacer con ellas. Ambas conservan sus ids en encryption.parts, que correlaciona las dos vistas; este endpoint no las devuelve.