Dev.to WebDev 🛠 Dev 👁 0 📖 3 min read

LectorMSG: lee archivos .msg de Outlook en tu navegador — 100% local, sin servidores y sin IA

Te llega un archivo .msg (el formato de correo de Outlook) y no tienes Outlook. Las opciones rápidas que encuentra uno son visores online donde subes tu correo —con sus adjuntos, remitentes y cabeceras— a un servidor des

Te llega un archivo .msg (el formato de correo de Outlook) y no tienes Outlook.
Las opciones rápidas que encuentra uno son visores online donde subes tu
correo —con sus adjuntos, remitentes y cabeceras— a un servidor desconocido.
Para un documento que puede ser confidencial, eso no era aceptable.

La buena noticia: el parsing ya estaba resuelto por la comunidad open source.
Solo faltaba una interfaz mínima que funcione 100% en tu navegador. Así nació
LectorMSG — y su
demo en GitHub Pages.

Qué hace

Un solo archivo HTML autocontenido (~380 KB, todo inline):

  1. Arrastras el .msg a la ventana (o click para elegirlo con el explorador).
  2. Ves asunto, remitente, destinatarios, fecha, ID del mensaje, cuerpo y las cabeceras originales.
  3. Click en un adjunto → vista previa aquí mismo: PDF embebido, imágenes y texto/XML, con botones para descargar o abrir en pestaña.
  4. Historial local: los últimos 10 archivos abiertos quedan en IndexedDB de tu navegador para reabrirlos con un click (borrables uno a uno o todos).

Funciona con doble clic desde file://, sin internet. Copiado a un USB, sigue
funcionando igual.

Cómo funciona por dentro (lo mínimos imprescindible)

Un .msg es un documento compuesto OLE2/CFB (firma d0 cf 11 e0 a1 b1 1a e1)
que guarda el mensaje y sus adjuntos como streams internos, con propiedades MAPI
y, a menudo, el cuerpo en RTF comprimido. Nada que reinventar:

Librería Licencia Rol
@kenjiuno/msgreader Apache-2.0 Parseo CFB + MAPI + adjuntos
@kenjiuno/decompressrtf BSD-2-Clause Descomprimir el cuerpo RTF
buffer / string_decoder MIT Polyfills de Node para el bundle de navegador

Mi código propio es una capa fina: drag & drop, render, vista previa modal,
historial en IndexedDB y descargas (notices completos
aquí
).

Privacidad por diseño

  • Todo el parseo ocurre en memoria del navegador; ninguna petición de red.
  • El cuerpo se muestra como texto plano (textContent): un correo no puede ejecutar scripts en el visor.
  • El PDF se previsualiza con el visor del navegador vía blob: local; las URLs se revocan al cerrar.
  • El historial vive solo en tu IndexedDB y «Borrar todo» lo elimina al instante.

Tres bugs que me dejó el camino (por si te sirven)

  1. display: flex pisa el atributo hidden. El modal de vista previa tenía #modal { display: flex }, que en CSS gana a la regla del user-agent [hidden] { display: none } — el modal salía abierto desde que cargaba la página, tapando todo con su fondo. Arreglo de una línea y para siempre:
   [hidden] { display: none !important; }

Y un test de regresión que verifica que la regla existe en el HTML compilado.

  1. accept=".msg" en el input de archivos filtra más de lo que promete en
    algunos entornos (ChromeOS/Linux): el explorador no dejaba elegir el archivo.
    Solución: aceptar cualquier archivo y validar la firma OLE2 en el propio visor.

  2. instanceof ArrayBuffer entre realms. Al probar con fake-indexeddb
    desde Node, los ArrayBuffer volvían de otro realm y el parser fallaba con
    Unknown arrayBuffer. En un navegador real no ocurre, pero la app ahora
    normaliza los bytes al realm local (coste cero en el caso normal).

Los tests (sin navegador y sin subir datos reales)

  • Parser en Node: metadata, adjuntos, firmas %PDF/<?xml del contenido extraído.
  • End-to-end con jsdom + fake-indexeddb: carga, render, historial (guardar, reabrir, dedup, borrar), modal con blob: — 17 aserciones.
  • Smoke test del HTML final: script inline, sin recursos remotos, 37 IDs presentes, estado inicial correcto.

Los tests que usan un correo real toman la ruta de una variable de entorno
(MSG_TEST_FILE) y se saltan solos si no existe: así el repo nunca lleva
datos de nadie.

Publícalo tú también (4 comandos)

git init && git add -A && git commit -m "visor local de .msg"
gh repo create lectormsg --public --source=. --push
gh api -X POST repos/TU_USUARIO/lectormsg/pages \
  -f "source[branch]=main" -f "source[path]=/"
# → https://tu-usuario.github.io/lectormsg/

En un minuto tienes tu copia en GitHub Pages. El .gitignore excluye
node_modules/ y cualquier *.msg, así que es imposible subir correos por
accidente.

Enlaces

En el roadmap: render de RTF a HTML con saneamiento, y recursión para mensajes
.msg incrustados. Si te sirve o le ves un fallo, los issues están abiertos.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.