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):
-
Arrastras el
.msga la ventana (o click para elegirlo con el explorador). - Ves asunto, remitente, destinatarios, fecha, ID del mensaje, cuerpo y las cabeceras originales.
- Click en un adjunto → vista previa aquí mismo: PDF embebido, imágenes y texto/XML, con botones para descargar o abrir en pestaña.
- 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)
-
display: flexpisa el atributohidden. 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.
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.instanceof ArrayBufferentre realms. Al probar con fake-indexeddb
desde Node, losArrayBuffervolví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/<?xmldel 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.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.