- Gmail to SQLite es una aplicación en Python que sincroniza mensajes de Gmail con una base de datos SQLite local para usarlos en análisis y archivado
- Su comportamiento predeterminado es una sincronización incremental, que descarga solo los mensajes nuevos; con la opción de sincronización completa puede descargar todos los mensajes y también detectar si fueron eliminados
- La importación de mensajes usa procesamiento paralelo multihilo e incluye manejo de errores y cierre, como reintentos automáticos con backoff exponencial y manejo de CTRL+C
- Para ejecutarla se requiere Python 3.8 o superior, un Google Cloud Project con la Gmail API habilitada y un archivo OAuth 2.0
credentials.json
- Los datos guardados incluyen remitente, destinatarios, etiquetas, cuerpo, tamaño, estado de lectura, si fue enviado por el usuario y si fue eliminado, lo que permite analizar directamente con SQL los patrones de uso de Gmail
Herramienta de sincronización local de mensajes de Gmail
- Gmail to SQLite es una aplicación en Python que guarda mensajes de Gmail en una base de datos SQLite local
- Su objetivo es permitir analizar y archivar datos de Gmail
- Aplica type hints en toda la base de código para contar con seguridad de tipos
Método de sincronización y confiabilidad
- La sincronización predeterminada funciona como sincronización incremental, descargando solo mensajes nuevos
- Al usar la opción
--full-sync, sincroniza todos los mensajes y detecta los mensajes eliminados en Gmail
- La importación de mensajes se realiza con procesamiento paralelo multihilo para mejorar el rendimiento
- El manejo de errores incluye reintentos automáticos y backoff exponencial
- Al presionar CTRL+C, se ejecuta un procedimiento de cierre ordenado
- Deja de aceptar trabajos nuevos
- Espera a que terminen los trabajos en ejecución
- Guarda el progreso de los trabajos completados
- Finaliza normalmente
- Si se presiona CTRL+C una vez más, finaliza de inmediato
Instalación y requisitos previos
- El entorno de ejecución requiere Python 3.8 o superior
- Se necesita un Google Cloud Project con la Gmail API habilitada
- El archivo de autenticación OAuth 2.0
credentials.json debe estar en la raíz del proyecto
- El flujo de instalación consiste en clonar el repositorio y luego instalar las dependencias con
uv sync
- La configuración de autenticación de la Gmail API se realiza creando o seleccionando un proyecto en Google Cloud Console, habilitando la Gmail API, generando credenciales OAuth 2.0 para Desktop application y guardándolas como
credentials.json
Uso de comandos
- La sincronización incremental básica se ejecuta así
python main.py sync --data-dir ./data
# or: uv run main.py sync --data-dir ./data
- Para sincronización completa y detección de eliminados, usa
--full-sync
python main.py sync --data-dir ./data --full-sync
- Para sincronizar solo un mensaje específico, usa
sync-message y --message-id
python main.py sync-message --data-dir ./data --message-id MESSAGE_ID
- Para detectar y mostrar solo mensajes eliminados, usa
sync-deleted-messages
python main.py sync-deleted-messages --data-dir ./data
- La cantidad de hilos worker puede especificarse con
--workers; el valor predeterminado es la cantidad de núcleos de CPU
python main.py sync --data-dir ./data --workers 8
- Los argumentos de línea de comandos son los siguientes
command: obligatorio; uno de sync, sync-message, sync-deleted-messages
--data-dir: obligatorio; directorio donde se guardará la base de datos SQLite
--full-sync: opcional; fuerza una sincronización completa
--message-id: obligatorio en sync-message; ID del mensaje específico a sincronizar
--workers: opcional; cantidad de hilos worker
--help: muestra la ayuda de comandos y opciones
Esquema SQLite y ejemplos de análisis
- La tabla
messages de la base de datos SQLite generada incluye los campos necesarios para analizar mensajes de Gmail
message_id: ID único del mensaje de Gmail
thread_id: ID del hilo de Gmail
sender: información del remitente en JSON con nombre y correo electrónico
recipients: JSON de destinatarios por tipo to, cc, bcc
labels: arreglo de etiquetas de Gmail
subject: asunto del mensaje
body: cuerpo del mensaje en texto plano
size: tamaño del mensaje en bytes
timestamp: fecha y hora del mensaje
is_read: estado de lectura
is_outgoing: si es un mensaje enviado por el usuario
is_deleted: si es un mensaje eliminado de Gmail
last_indexed: hora de la última sincronización
- Se puede agrupar la cantidad de correos por remitente
SELECT sender->>'$.email', COUNT(*) AS count
FROM messages
GROUP BY sender->>'$.email'
ORDER BY count DESC
- Se pueden agrupar los correos no leídos por remitente para identificar remitentes que envían muchos correos poco interesantes
SELECT sender->>'$.email', COUNT(*) AS count
FROM messages
WHERE is_read = 0
GROUP BY sender->>'$.email'
ORDER BY count DESC
- Se puede usar
strftime para agrupar la cantidad de correos por año, mes, día, día de la semana u hora
SELECT strftime('%Y', timestamp) AS period, COUNT(*) AS count
FROM messages
GROUP BY period
ORDER BY count DESC
- Se pueden buscar correos cuyo cuerpo incluya
newsletter o unsubscribe y agrupar newsletters por remitente
SELECT sender->>'$.email', COUNT(*) AS count
FROM messages
WHERE body LIKE '%newsletter%' OR body LIKE '%unsubscribe%'
GROUP BY sender->>'$.email'
ORDER BY count DESC
- Se puede revisar el tamaño total de correos por remitente y los remitentes de correos grandes en MB
SELECT sender->>'$.email', sum(size)/1024/1024 AS size
FROM messages
GROUP BY sender->>'$.email'
ORDER BY size DESC
- Se puede calcular la cantidad de correos enviados a uno mismo usando el JSON
recipients y una condición sobre el correo del sender
SELECT count(*)
FROM messages
WHERE EXISTS (
SELECT 1
FROM json_each(messages.recipients->'$.to')
WHERE json_extract(value, '$.email') = 'foo@example.com'
)
AND sender->>'$.email' = 'foo@example.com'
- Se puede ver, entre los correos recibidos, el orden de remitentes con mayor volumen total
SELECT sender->>'$.email', sum(size)/1024/1024 as total_size
FROM messages
WHERE is_outgoing=false
GROUP BY sender->>'$.email'
ORDER BY total_size DESC
- Los mensajes eliminados se consultan con la condición
is_deleted=1
SELECT message_id, subject, timestamp
FROM messages
WHERE is_deleted=1
ORDER BY timestamp DESC
1 comentarios
Opiniones de Hacker News
Me da curiosidad por qué sacaron ciertos encabezados por separado en el esquema.
recipients,subjectysenderpodrían estar como campos JSON, pero también podrían poner todo en un soloheaderse incluir ahí el resto de los encabezados del mensaje.Si es por rendimiento, pueden dejar
headerscomo un único blob JSON y convertir los campos necesarios en columnas generadas. Por ejemplo,subjectse puede crear conjson_extract("headers", '$.Subject')y ponerle un índice.Este modelo era potente porque el usuario podía agregar con
ALTER TABLElas columnas generadas indexadas que necesitara para sus consultas. Para el estado de DKIM también se puede extraer"Dkim-Signature", crear una columna y un índice, y luego hacerGROUP BY.CREATE INDEX subjectidx ON messages(json_extract(headers, '$.Subject')), el índice se usa en los lugares que referencian esa expresión.Después de crear índices así, me resultó útil crear un
VIEWque use esa expresión, en lugar de hacerALTERen la tabla principal para agregar una columna generada.Normalmente prefiero sacar por separado las columnas que se van a usar de forma constante. Más aún si se trata de algo estable como los encabezados de email; aunque la columna
headerspueda facilitar un poco los cambios de esquema, en la práctica cambia el dolor del momento de escritura al momento de lectura y deja margen para fallas silenciosas.Después de unos dos meses, cuando aparecen los campos que realmente hacen falta, los cargo desde el JSON y hago que la API los mantenga actualizados, o creo una vista. Ayudó bastante a evitar los dolores de crecimiento de “pongamos todo en MongoDB” o “dejémoslo simplemente en el sistema de archivos”, y el costo no fue grande.
dkimcomoNOT NULL; me pregunto qué pasa si un mensaje de email no tiene el encabezadoDkim-Signature.Hace unos años hice una herramienta de visualización de email masivo tipo Gmail: https://github.com/terhechte/postsack
Me pregunto si también tiene una opción por tamaño. Quiero ver qué remitentes ocupan más espacio de almacenamiento. Y el certificado SSL del sitio web está vencido.
En cambio, los archivos de caché de qdirstat son fáciles de crear, así que se pueden usar para visualizar varios objetos con aspecto de archivos.
Es una verdadera lástima que ahora ya no se pueda iniciar sesión ni siquiera con contraseñas específicas de app, y que haya que crear un cliente OAuth y pasar por el flujo OAuth. Aunque sea mi email, Google me quitó un estándar abierto para acceder a él.
En especial, cada vez recibo más avisos de que mi email de freelance está cayendo en spam en los sistemas de los destinatarios. Aun así, no sé bien cómo abandonar los hábitos que adquirí dentro del ecosistema de Google.
Hace poco intenté integrar Gmail en mi app https://github.com/rumca-js/Django-link-archive y le dediqué demasiado tiempo; terminé concluyendo que no vale la pena dar soporte a Gmail.
Gmail to SQLite explica la configuración de credenciales en 6 pasos, pero en mi caso no fue así. Después de esos 6 pasos, Google decía que la app no estaba publicada y que había que publicarla; luego decía que, como no era usuario de Workspace, no podía dejarla como app interna; y al cambiarla a app externa, decía que no podía usarse antes de la verificación.
En el proceso de verificación pedían dominio, dirección, otros detalles, justificación de los alcances e incluso un video que explicara cómo se usa la app; además decían que tardarían en verificar los datos enviados. Todo es como un laberinto de configuración, y hacer que los usuarios salten los obstáculos que exige Google es demasiado.
Me pregunto cuál es el mejor software open source de respaldo para Gmail que existe hoy. También me interesa saber si alguien armó algo así incluyendo el almacenamiento de adjuntos
Como referencia, también está https://www.mailstore.com/en/products/mailstore-home/. No es open source, pero al ser una GUI con índice es bueno para buscar correo localmente, y la reanudación solo funciona para respaldos, así que las restauraciones grandes suelen fallar
Tengo un recordatorio para ejecutarlo cada pocos meses y actualizar mi respaldo local. Si mal no recuerdo, baja como archivos mbox comprimidos con gzip
Me parece que esto debería llamarse algo como “IMAP to SQLite”, no “Gmail to SQLite”. No entiendo por qué atarlo a un proveedor de email específico
IMAP es mucho más difícil y mucho más lento, y además queda sujeto a los límites de ancho de banda de Google
No sé si estaba en un almacenamiento demasiado frío y por eso daba timeout. Así que entiendo que usar la API propietaria de Google pueda funcionar mejor
Hoy en día Google Takeout incluye mbox, funciona correctamente y es bastante rápido, pero no sirve para actualizaciones continuas. Al final me mudé a otro proveedor de correo, Infomaniak, y agradecí haber usado antes mi propio dominio de correo
Estaría bueno poder activar también la búsqueda de texto completo
Ayer hice algo parecido. Quería listar por dominio los emails de los destinatarios. El código es un desastre, pero está aquí: https://github.com/hugoferreira/gmail-sqlite-db
Me recuerda un poco a Archiveopteryx, el servidor IMAP basado en PostgreSQL: https://github.com/aox/aox
El esquema de AOX siempre me pareció bueno, pero nunca llegué a usarlo realmente bien. Su uso principal era análisis y búsqueda de correo, no ser un servidor IMAP de uso diario
Me pregunto cómo son los costos de ancho de banda en esto. Como alguien con una cuenta de Gmail de más de 40 GB, quisiera saber si me cobrarían por transferirla con esta herramienta
Es fácil de arreglar. Como Google Takeout parece ser gratis, podrías descargar primero desde ahí y parsear los archivos. Aun así, desde la perspectiva de empezar de inmediato, esta herramienta parece más rápida