- Gmail to SQLite é um aplicativo em Python que sincroniza mensagens do Gmail com um banco de dados SQLite local para uso em análise e arquivamento
- O comportamento padrão é a sincronização incremental, que baixa apenas mensagens novas; com a opção de sincronização completa, é possível baixar todas as mensagens e também detectar exclusões
- A importação de mensagens usa processamento paralelo com múltiplas threads e inclui tratamento de erros e encerramento, como tentativas automáticas com recuo exponencial e suporte a CTRL+C
- Para executar, são necessários Python 3.8 ou superior, um Google Cloud Project com a Gmail API ativada e o arquivo OAuth 2.0
credentials.json
- Os dados armazenados incluem remetente, destinatários, rótulos, corpo, tamanho, status de leitura, status de envio, status de exclusão e outros, permitindo analisar diretamente os padrões de uso do Gmail com SQL
Ferramenta de sincronização local de mensagens do Gmail
- Gmail to SQLite é um aplicativo em Python que armazena mensagens do Gmail em um banco de dados SQLite local
- O objetivo é permitir analisar e arquivar os dados do Gmail
- Todo o código usa type hints para oferecer segurança de tipos
Modo de sincronização e confiabilidade
- A sincronização padrão funciona como sincronização incremental, baixando apenas mensagens novas
- Ao usar a opção
--full-sync, todas as mensagens são sincronizadas e mensagens excluídas no Gmail são detectadas
- A importação de mensagens é feita com processamento paralelo multithread para melhorar o desempenho
- O tratamento de erros inclui novas tentativas automáticas e recuo exponencial
- Ao pressionar CTRL+C, um procedimento de encerramento gracioso é executado
- Para de aceitar novos trabalhos
- Espera os trabalhos em execução terminarem
- Salva o progresso dos trabalhos concluídos
- Encerra normalmente
- Se CTRL+C for pressionado mais uma vez, o programa é encerrado imediatamente
Instalação e requisitos de preparação
- O ambiente de execução requer Python 3.8 ou superior
- É necessário um Google Cloud Project com a Gmail API ativada
- O arquivo de autenticação OAuth 2.0
credentials.json deve estar na raiz do projeto
- O fluxo de instalação consiste em clonar o repositório e instalar as dependências com
uv sync
- A configuração de autenticação da Gmail API é feita criando ou selecionando um projeto no Google Cloud Console, ativando a Gmail API e depois gerando credenciais OAuth 2.0 para Desktop application, salvas como
credentials.json
Uso dos comandos
- A sincronização incremental padrão é executada assim
python main.py sync --data-dir ./data
# or: uv run main.py sync --data-dir ./data
- Para sincronização completa com detecção de exclusão, use
--full-sync
python main.py sync --data-dir ./data --full-sync
- Para sincronizar apenas uma mensagem específica, use
sync-message e --message-id
python main.py sync-message --data-dir ./data --message-id MESSAGE_ID
- Para detectar e marcar apenas mensagens excluídas, use
sync-deleted-messages
python main.py sync-deleted-messages --data-dir ./data
- O número de threads worker pode ser definido com
--workers; o padrão é o número de núcleos da CPU
python main.py sync --data-dir ./data --workers 8
- Os argumentos de linha de comando são os seguintes
command: obrigatório; um entre sync, sync-message ou sync-deleted-messages
--data-dir: obrigatório; diretório onde o banco de dados SQLite será armazenado
--full-sync: opcional; força uma sincronização completa
--message-id: obrigatório em sync-message; ID específico da mensagem a sincronizar
--workers: opcional; número de threads worker
--help: mostra a ajuda dos comandos e opções
Esquema SQLite e exemplos de análise
- A tabela
messages do banco de dados SQLite gerado inclui os campos necessários para analisar mensagens do Gmail
message_id: ID único da mensagem no Gmail
thread_id: ID da thread no Gmail
sender: informações do remetente em JSON, com nome e e-mail
recipients: JSON de destinatários por tipo to, cc, bcc
labels: array de rótulos do Gmail
subject: assunto da mensagem
body: corpo da mensagem em texto simples
size: tamanho da mensagem em bytes
timestamp: horário da mensagem
is_read: status de leitura
is_outgoing: indica se a mensagem foi enviada pelo usuário
is_deleted: indica se a mensagem foi excluída no Gmail
last_indexed: horário da última sincronização
- É possível agregar a quantidade de e-mails por remetente
SELECT sender->>'$.email', COUNT(*) AS count
FROM messages
GROUP BY sender->>'$.email'
ORDER BY count DESC
- Também é possível contar e-mails não lidos por remetente para identificar quem envia muitos e-mails sem interesse
SELECT sender->>'$.email', COUNT(*) AS count
FROM messages
WHERE is_read = 0
GROUP BY sender->>'$.email'
ORDER BY count DESC
- Com
strftime, dá para agregar a quantidade de e-mails por ano, mês, dia, dia da semana ou hora
SELECT strftime('%Y', timestamp) AS period, COUNT(*) AS count
FROM messages
GROUP BY period
ORDER BY count DESC
- Dá para encontrar e-mails com
newsletter ou unsubscribe no corpo e agrupar newsletters por remetente
SELECT sender->>'$.email', COUNT(*) AS count
FROM messages
WHERE body LIKE '%newsletter%' OR body LIKE '%unsubscribe%'
GROUP BY sender->>'$.email'
ORDER BY count DESC
- Também é possível verificar o tamanho total de e-mails por remetente e identificar grandes remetentes em MB
SELECT sender->>'$.email', sum(size)/1024/1024 AS size
FROM messages
GROUP BY sender->>'$.email'
ORDER BY size DESC
- O número de e-mails enviados para si mesmo pode ser calculado com o JSON
recipients e a condição do e-mail em 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'
- Também é possível ver quais remetentes ocupam mais espaço entre os e-mails recebidos
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
- Mensagens excluídas podem ser consultadas com a condição
is_deleted=1
SELECT message_id, subject, timestamp
FROM messages
WHERE is_deleted=1
ORDER BY timestamp DESC
1 comentários
Comentários no Hacker News
Fiquei curioso sobre o motivo de destacar alguns cabeçalhos específicos no esquema.
recipients,subjectesenderpoderiam ficar como campos JSON, mas também daria para colocar tudo em um únicoheaderse incluir ali o restante dos cabeçalhos da mensagemSe a questão for desempenho, dá para manter
headerscomo um único blob JSON e transformar os campos necessários em colunas geradas. Por exemplo,subjectpode ser criado comjson_extract("headers", '$.Subject')e indexadoEsse modelo era poderoso porque o usuário podia adicionar, com
ALTER TABLE, colunas geradas indexadas conforme a necessidade das próprias consultas. Também daria para extrair o estado do DKIM com"Dkim-Signature", criar uma coluna e um índice, e depois usarGROUP BYCREATE INDEX subjectidx ON messages(json_extract(headers, '$.Subject')); assim o índice é usado em qualquer lugar que referencie essa expressãoDepois de criar índices desse jeito, achei mais útil criar uma
VIEWusando essa expressão do que darALTERna tabela principal para adicionar uma coluna geradaNormalmente prefiro separar em colunas os campos que serão usados de forma recorrente. Isso vale ainda mais para algo estável como cabeçalhos de e-mail; embora a coluna
headerspossa facilitar um pouco mudanças de esquema, ela acaba trocando dor na escrita por dor na leitura e ainda abre espaço para falhas silenciosasDepois de uns dois meses, quando fica claro quais campos realmente importam, eu os preencho a partir do JSON, mantenho a API atualizada ou crio uma view. Isso ajudou bastante a evitar as dores de crescimento do “vamos jogar tudo no MongoDB” ou “vamos deixar tudo no sistema de arquivos”, sem um custo grande
dkimfoi definida comoNOT NULL, então fiquei curioso sobre o que acontece quando uma mensagem de e-mail não tem o cabeçalhoDkim-SignatureAlguns anos atrás fiz uma ferramenta de visualização de grandes volumes de e-mail no estilo Gmail: https://github.com/terhechte/postsack
Fiquei curioso se há opção de tamanho. Eu gostaria de ver quais remetentes estão consumindo mais do meu armazenamento. E o certificado SSL do site expirou
Já o arquivo de cache do qdirstat é fácil de criar, então ele pode ser usado para visualizar vários tipos de alvos que se parecem com arquivos
É realmente uma pena que agora não dê mais para entrar nem com senha de app, e que seja preciso criar um cliente OAuth e passar por todo o fluxo OAuth. O e-mail é meu, mas o Google tirou de mim um padrão aberto para eu poder acessá-lo
Principalmente porque estou recebendo cada vez mais avisos de que meu e-mail de freelancer está caindo em spam nos sistemas dos destinatários. Só não faço ideia de como abandonar os hábitos que criei dentro do ecossistema do Google
Recentemente tentei integrar o Gmail ao meu app https://github.com/rumca-js/Django-link-archive e gastei tempo demais nisso; concluí que não vale a pena dar suporte ao Gmail
O Gmail to SQLite explica a configuração de credenciais em 6 etapas, mas no meu caso não foi assim. Mesmo depois da sexta etapa, o Google dizia que o app não estava publicado e que eu precisava publicá-lo; dizia também que, como eu não era usuário do Workspace, não podia deixá-lo como app interno; e, quando mudei para app externo, dizia que eu não poderia usá-lo antes da verificação
No processo de verificação, pediram domínio, endereço, outros detalhes, justificativa para os escopos e até um vídeo explicando como o app seria usado. Também disseram que levaria tempo para verificar os dados enviados. Tudo isso parece um labirinto de configuração, e fazer o usuário passar por todos esses obstáculos impostos pelo Google é demais
Estou curioso para saber qual é o melhor software open source de backup do Gmail que existe hoje. Também queria saber se alguém já montou algo assim incluindo arquivamento de anexos
Como referência, também existe o https://www.mailstore.com/en/products/mailstore-home/. Não é open source, mas tem uma GUI com índice, então é bom para pesquisar e-mails localmente, e a retomada só funciona para backup, então restaurações grandes geralmente falham
Eu deixo um lembrete para rodar isso a cada poucos meses e atualizar meu backup local. Pelo que lembro, ele baixa como arquivos mbox compactados com gzip
Parece que isso deveria se chamar algo como “IMAP to SQLite”, e não “Gmail to SQLite”. Não entendo por que amarrar isso a um único provedor de e-mail específico
IMAP é bem mais difícil e bem mais lento, além de ficar preso aos limites de largura de banda do Google
Não sei se ele estava em algum armazenamento frio demais e por isso dava timeout. Então entendo que usar a API proprietária do Google pode funcionar melhor
Hoje em dia o Google Takeout inclui mbox, funciona direito e é bem rápido, mas não serve para atualizações contínuas. Acabei migrando para outro provedor de e-mail, o Infomaniak, e agradeci a mim mesmo por já ter usado meu domínio de e-mail lá atrás
Seria bom se também fosse possível ativar busca em texto completo
Ontem eu também fiz a mesma coisa. Eu queria listar os e-mails dos destinatários por domínio. O código está uma bagunça, mas está aqui: https://github.com/hugoferreira/gmail-sqlite-db
Isso me lembrou um pouco o Archiveopteryx, um servidor IMAP baseado em PostgreSQL: https://github.com/aox/aox
O esquema do AOX sempre pareceu bom, mas nunca cheguei a usá-lo de verdade. O uso principal era análise e busca de e-mails, não um servidor IMAP principal para uso diário
Fico curioso sobre como funcionam os custos de largura de banda aqui. Como alguém com uma conta do Gmail com mais de 40 GB, queria saber se transferir com essa ferramenta geraria cobrança
É fácil de resolver. Como o Google Takeout aparentemente é gratuito, basta baixar por lá primeiro e fazer o parse dos arquivos. Ainda assim, do ponto de vista de começar imediatamente, esta ferramenta parece mais rápida