Hilos
`threads.list`, `listAll`, `iterate`, `get`, `update`, `trash`, `snooze`, `unsnooze` y `listAttachments`.
Lectura
const page = await openemail.threads.list({ folder: 'inbox', query: 'from:ada', labelIds: ['INBOX', 'IMPORTANT'], limit: 25,}) const next = page.nextCursor ? await openemail.threads.list({ folder: 'inbox', cursor: page.nextCursor }) : null const thread = await openemail.threads.get('thread_…')console.log(thread.messageCount, thread.hasUnread, thread.totalReplies)La API pagina los hilos con un pageToken. El cliente te lo entrega como nextCursor y lo recibe de vuelta como cursor, igual que cualquier otra lista, y listAll e iterate lo siguen por ti. Es opaco: devuelve lo que te dieron y nunca construyas uno.
Organización
await openemail.threads.update('thread_…', { read: true, addLabelIds: ['Done'], removeLabelIds: ['INBOX'],}) await openemail.threads.trash('thread_…')await openemail.threads.snooze('thread_…', new Date(Date.now() + 86_400_000))await openemail.threads.unsnooze('thread_…')El estado de lectura ES una etiqueta en todos los backends de aquí, así que viaja con las listas de etiquetas y el orden es determinista cuando fijas ambas cosas. Al menos uno de los tres campos debe estar presente.
Adjuntos de un mensaje
const files = await openemail.threads.listAttachments('thread_…', 'message_…') for (const file of files) { console.log(file.filename, file.contentType, file.size) if (file.content) await save(file.filename, Buffer.from(file.content, 'base64'))}content es base64, y una cadena vacía cuando no se han podido encontrar los bytes almacenados, así que comprueba su longitud antes de decodificar. El texto cifrado de un mensaje cifrado SÍ está en esta lista y se descarga como cualquier otro archivo; la parte de versión PGP/MIME y cualquier firma separada, no. Conservan sus ids en encryption.parts y nada más.
Un mensaje que llegó cifrado
Este SDK no cifra ni descifra: no puede abrir un mensaje que haya cifrado otra persona ni puede enviar uno cifrado. La solicitud de envío se rechaza si lleva un marcador de cifrado, porque un cliente sin clave no tiene por qué afirmar que la tiene. Las claves generadas en la aplicación de OpenEmail viven en el navegador que las creó y no llegan a nada de aquí, y cuando ese navegador abre un mensaje sellado el texto plano se queda en él: el mensaje almacenado que lee esta llamada sigue siendo texto cifrado. Lo que te da threads.get es el sobre, reconocido. Un mensaje que llegó envuelto en PGP o S/MIME lleva un objeto encryption, de modo que un decodedBody vacío deja de ser lo único que recibes, y encryption es el único campo de MessageResource con un tipo real, porque es aquel cuya ausencia no puedes sobrevivir adivinando.
import { isSealed, openemail } from '@openemail/sdk' const thread = await openemail.threads.get('thread_…') for (const message of thread.messages) { if (!message.encryption) continue if (!isSealed(message)) continue console.warn('cannot read this one:', message.encryption.format)}Ramifica con isSealed, nunca según la presencia del campo. Dos de los cinco formatos, pgp-signed y smime-signed, describen un cuerpo que llegó EN CLARO junto a una firma separada, así que condicionar por la presencia oculta correo que nadie necesitaba ocultar, y el usuario no puede verlo ni explicárselo. isSealed existe exactamente por eso: el servidor declara una sola vez el conjunto sellado, y una tercera copia escrita a partir de la unión es la copia que se desvía.
La ausencia no significa texto plano. encryption falta en todos los mensajes almacenados antes de que se publicara la detección, y en todo lo que llegó al buzón por una ruta donde el detector nunca se ejecutó. Registra que nadie miró, un hecho sobre nuestra cobertura y no sobre el correo, y nada lo rellena a posteriori.
En qué se diferencian del resto
- Cada entrada de
ThreadResource.messageses unMessageResource, unRecord<string, unknown>con exactamente un campo con nombre. Tipar el resto sería el cliente afirmando una normalización que nadie realiza, yencryptionsí lleva nombre porque un cliente que no puede ramificar según él lee un mensaje sellado como uno vacío. - Una solicitud que no puede atenderse fielmente es un 422
capability_unsupported, no una respuesta que parece correcta y está silenciosamente equivocada.
Parámetros: threads.list (ThreadListOptions)
folderstring- Qué carpeta listar. El servidor usa `inbox` por defecto, así que omitirlo restringe el listado en lugar de ampliarlo a todo. También se aplica a una búsqueda con `query`, salvo que la propia consulta nombre una carpeta con `in:` o con un `is:` de carpeta como `is:sent`.
querystring- La sintaxis de búsqueda del buzón. Todas las palabras sueltas deben aparecer, y cada una coincide de forma laxa: se ignoran mayúsculas, acentos y separadores, y una parte de una palabra más larga cuenta, así que tanto `min` como `ben jamin` encuentran "Benjamin". Una frase entre comillas coincide tal como está escrita, salvo por mayúsculas y acentos, de modo que `"ben jamin"` no encuentra "Ben-Jamin", y las palabras vacías se descartan cuando queda algo más que buscar. Restringe con operadores como `from:ada`, `label:Invoices`, `is:unread`, `has:pdf`, `before:2026/01/31` y `older_than:1y`, y combínalos con `OR`, paréntesis y un `-` inicial; un valor que la búsqueda no pueda usar se ignora en lugar de restringir. Las palabras y los operadores `from:`, `to:`, `cc:`, `subject:` y `body:` leen el remitente, los destinatarios y el asunto del mensaje más reciente, y los primeros 4.000 caracteres de su cuerpo con el marcado eliminado, mientras que `filename:` y `has:` leen todos los adjuntos de la conversación completa, y las etiquetas y las carpetas leen la conversación completa. Restringe el mismo índice que lee el listado sin filtrar. Los mensajes sellados no almacenan texto del cuerpo, así que solo pueden coincidir su remitente, sus destinatarios y su asunto.
labelIdsstring | string[]- Restringe el listado a los hilos que llevan estas etiquetas. El endpoint recibe una cadena separada por comas y el cliente une un array en una sola por ti; no hay límite de cuántas nombres.
limitnumber- Cuántos hilos devolver, de 1 a 100. Si se omite, el handler usa 25. El valor por defecto vive en el handler y no en el esquema, así que un valor ausente y un 25 explícito se comportan igual.
cursorstring- El `nextCursor` de la página anterior, devuelto literalmente. Es el `pageToken` de la API bajo el nombre que usa cualquier otra lista, y es opaco, así que nunca construyas ni edites uno.
Respuesta: Page<ThreadSummaryResource>
itemsThreadSummaryResource[]- Una entrada por hilo en esta página, extraída del sobre `data` de la API. Cada entrada es solo un marcador de objeto y un id. El listado no lleva asunto, fragmento, participantes ni etiquetas, así que cualquier otra cosa implica llamar a `threads.get` sobre los hilos que te interesen.
items[].idstring- El id del hilo, para pasárselo sin cambios a `threads.get`, `threads.update` y los demás. Es el mismo id tanto si la fila vino de un listado filtrado como de una búsqueda con `query`.
hasMoreboolean- Si hay otra página más, derivado de `nextCursor` allí donde la API no lo indica.
nextCursorstring | null- El `nextPageToken` de la API, para reenviarlo como `cursor` en la página siguiente, o null cuando no hay más páginas. Un token vacío se normaliza a null, así que una comprobación de valor falsy y una de null coinciden.