Gmail을 SQLite로 저장하기
(github.com/marcboeker)- Gmail to SQLite는 Gmail 메시지를 로컬 SQLite 데이터베이스로 동기화해 분석과 보관에 활용하는 Python 애플리케이션임
- 기본 동작은 새 메시지만 내려받는 증분 동기화이며, 전체 동기화 옵션으로 모든 메시지를 내려받고 삭제 여부도 감지할 수 있음
- 메시지 가져오기는 멀티스레드 병렬 처리를 사용하며, 지수 백오프 기반 자동 재시도와 CTRL+C 처리 같은 오류·종료 대응을 포함함
- 실행에는 Python 3.8 이상, Gmail API가 활성화된 Google Cloud Project, OAuth 2.0
credentials.json파일이 필요함 - 저장된 데이터는 발신자, 수신자, 라벨, 본문, 크기, 읽음 여부, 발신 여부, 삭제 여부 등을 포함해 SQL로 Gmail 사용 패턴을 직접 분석할 수 있음
Gmail 메시지 로컬 동기화 도구
- Gmail to SQLite는 Gmail 메시지를 로컬 SQLite 데이터베이스에 저장하는 Python 애플리케이션임
- 목적은 Gmail 데이터를 분석하고 보관할 수 있게 만드는 것임
- 코드베이스 전반에 타입 힌트를 적용해 타입 안전성을 갖추고 있음
동기화 방식과 안정성
- 기본 동기화는 증분 동기화로 동작해 새 메시지만 다운로드함
--full-sync옵션을 사용하면 전체 메시지를 동기화하고 Gmail에서 삭제된 메시지를 감지함- 메시지 가져오기는 멀티스레드 병렬 처리로 수행해 성능을 높임
- 오류 처리에는 자동 재시도와 지수 백오프가 포함됨
- CTRL+C를 누르면 우아한 종료 절차가 실행됨
- 새 작업 수락을 중단함
- 실행 중인 작업이 끝나기를 기다림
- 완료된 작업의 진행 상태를 저장함
- 정상 종료함
- CTRL+C를 한 번 더 누르면 즉시 종료함
설치와 준비 조건
- 실행 환경에는 Python 3.8 이상이 필요함
- Gmail API가 활성화된 Google Cloud Project가 필요함
- OAuth 2.0 인증 파일
credentials.json이 프로젝트 루트에 있어야 함 - 설치 흐름은 저장소를 클론한 뒤
uv sync로 의존성을 설치하는 방식임 - Gmail API 인증 설정은 Google Cloud Console에서 프로젝트를 만들거나 선택하고, Gmail API를 활성화한 뒤 Desktop application용 OAuth 2.0 자격 증명을 생성해
credentials.json으로 저장함
명령어 사용법
- 기본 증분 동기화는 다음처럼 실행함
python main.py sync --data-dir ./data
# or: uv run main.py sync --data-dir ./data
- 전체 동기화와 삭제 감지는
--full-sync를 사용함
python main.py sync --data-dir ./data --full-sync
- 특정 메시지만 동기화하려면
sync-message와--message-id를 사용함
python main.py sync-message --data-dir ./data --message-id MESSAGE_ID
- 삭제된 메시지만 감지하고 표시하려면
sync-deleted-messages를 사용함
python main.py sync-deleted-messages --data-dir ./data
- 워커 스레드 수는
--workers로 지정할 수 있으며, 기본값은 CPU 코어 수임
python main.py sync --data-dir ./data --workers 8
- 명령줄 인자는 다음과 같음
command: 필수이며sync,sync-message,sync-deleted-messages중 하나--data-dir: 필수이며 SQLite 데이터베이스가 저장될 디렉터리--full-sync: 선택 사항이며 전체 동기화를 강제함--message-id:sync-message에서 필수이며 동기화할 특정 메시지 ID--workers: 선택 사항이며 워커 스레드 수--help: 명령과 옵션 도움말 표시
SQLite 스키마와 분석 예시
- 생성되는 SQLite 데이터베이스의
messages테이블은 Gmail 메시지 분석에 필요한 필드를 포함함message_id: 고유 Gmail 메시지 IDthread_id: Gmail 스레드 IDsender: 이름과 이메일을 담은 JSON 발신자 정보recipients:to,cc,bcc유형별 수신자 JSONlabels: Gmail 라벨 배열subject: 메시지 제목body: 일반 텍스트 메시지 본문size: 바이트 단위 메시지 크기timestamp: 메시지 시각is_read: 읽음 상태is_outgoing: 사용자가 보낸 메시지 여부is_deleted: Gmail에서 삭제된 메시지 여부last_indexed: 마지막 동기화 시각
- 발신자별 이메일 수를 집계할 수 있음
SELECT sender->>'$.email', COUNT(*) AS count
FROM messages
GROUP BY sender->>'$.email'
ORDER BY count DESC
- 읽지 않은 이메일을 발신자별로 집계해 흥미 없는 이메일을 많이 보내는 발신자를 확인할 수 있음
SELECT sender->>'$.email', COUNT(*) AS count
FROM messages
WHERE is_read = 0
GROUP BY sender->>'$.email'
ORDER BY count DESC
strftime을 사용해 연도, 월, 일, 요일, 시간 단위로 이메일 수를 집계할 수 있음
SELECT strftime('%Y', timestamp) AS period, COUNT(*) AS count
FROM messages
GROUP BY period
ORDER BY count DESC
- 본문에
newsletter또는unsubscribe가 포함된 메일을 찾아 뉴스레터를 발신자별로 묶을 수 있음
SELECT sender->>'$.email', COUNT(*) AS count
FROM messages
WHERE body LIKE '%newsletter%' OR body LIKE '%unsubscribe%'
GROUP BY sender->>'$.email'
ORDER BY count DESC
- 발신자별 총 메일 크기와 큰 이메일 발신자를 MB 단위로 확인할 수 있음
SELECT sender->>'$.email', sum(size)/1024/1024 AS size
FROM messages
GROUP BY sender->>'$.email'
ORDER BY size DESC
- 자신에게 보낸 메일 수를
recipientsJSON과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'
- 수신한 메일 중 발신자별 총 용량이 큰 순서를 확인할 수 있음
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
- 삭제된 메시지는
is_deleted=1조건으로 조회함
SELECT message_id, subject, timestamp
FROM messages
WHERE is_deleted=1
ORDER BY timestamp DESC
댓글과 토론
Hacker News 의견들
-
스키마에서 특정 헤더를 따로 빼낸 이유가 궁금함.
recipients,subject,sender를 JSON 필드로 둘 수도 있지만,headers하나에 전부 넣고 메시지의 나머지 헤더까지 담을 수도 있음
성능 때문이라면headers를 단일 JSON blob으로 두고 필요한 필드는 생성 컬럼으로 만들면 됨. 예를 들어subject는json_extract("headers", '$.Subject')로 만들고 인덱스를 걸 수 있음
이 모델은 사용자가 자기 쿼리에 필요한 인덱싱된 생성 컬럼을ALTER TABLE로 추가할 수 있어서 강력했음. DKIM 상태도"Dkim-Signature"를 뽑아 컬럼과 인덱스를 만든 뒤GROUP BY하면 됨- 실제로는 생성 컬럼도 필요 없고, SQLite는 표현식 인덱스를 지원함. 예를 들어
CREATE INDEX subjectidx ON messages(json_extract(headers, '$.Subject'))처럼 만들면 해당 표현식을 참조하는 곳에서 인덱스를 사용함
이런 식으로 인덱스를 만든 뒤, 메인 테이블을ALTER해서 생성 컬럼을 추가하기보다 해당 표현식을 쓰는VIEW를 만드는 편이 유용했음 - 일회성 쿼리를 위해 인덱스를 추가하는 건 나쁜 습관처럼 보임
보통 꾸준히 쓰게 될 컬럼은 따로 빼는 편을 선호함. 이메일 헤더처럼 안정적인 대상이라면 더 그렇고,headers컬럼이 스키마 변경은 조금 쉽게 만들 수 있어도 쓰기 시점의 고통을 읽기 시점의 고통으로 바꾸는 셈이며 조용히 실패할 여지도 남김 - PostgreSQL로 시스템을 키워갈 때 비슷한 패턴을 자주 씀. 처음엔 필요하다고 아는 필드를 생각해 테이블을 만들고, 남는 메타데이터는 JSON 컬럼에 넣어 둠
두 달쯤 지나 실제로 필요한 필드가 보이면 JSON에서 채워 넣고, API가 계속 최신으로 유지하게 하거나 뷰를 만들거나 함. “그냥 전부 MongoDB에 넣자”나 “그냥 파일 시스템에 두자”에서 오는 성장통을 피하는 데 꽤 도움이 됐고 비용도 크지 않았음 dkim컬럼을NOT NULL로 정의했는데, 이메일 메시지에Dkim-Signature헤더가 없으면 어떻게 되는지 궁금함
- 실제로는 생성 컬럼도 필요 없고, SQLite는 표현식 인덱스를 지원함. 예를 들어
-
몇 년 전에 Gmail 같은 대량 이메일 시각화 도구를 만들었음: https://github.com/terhechte/postsack
- 꽤 멋짐. 디스크 사용량 시각화 도구와 비슷하지만, 디스크 사용량보다는 메일의 총량에 초점을 맞춘 것 같음
크기 옵션도 있는지 궁금함. 어떤 발신자가 내 저장공간을 가장 많이 쓰는지 보고 싶음. 그리고 웹사이트 SSL 인증서가 만료돼 있음 - 흥미로워 보임. README의 gmvault 링크는 이제 죽었는데, 이게 맞는지 궁금함: https://github.com/gaubert/gmvault
- 흥미로워 보임. 예전에 qdirstat로 비슷한 걸 직접 해봤지만, 날짜 폴더처럼 이메일을 특정 방식으로 배치해야 하고 다른 기준으로 다시 자르기 어려웠음
반면 qdirstat 캐시 파일은 만들기 쉬워서 파일처럼 생긴 여러 대상을 시각화하는 데 쓸 수 있음
- 꽤 멋짐. 디스크 사용량 시각화 도구와 비슷하지만, 디스크 사용량보다는 메일의 총량에 초점을 맞춘 것 같음
-
이제는 앱 전용 비밀번호로도 로그인할 수 없고, OAuth 클라이언트를 만들고 OAuth 흐름을 거쳐야 한다는 점이 정말 아쉬움. 내 이메일인데도 Google이 내가 접근하기 위한 공개 표준을 빼앗아 감
- 무료 Gmail 주소로 받는 스팸의 양과, Gmail 서버에서 비-Gmail 계정으로 오는 스팸의 양을 보면 점점 탈Google 쪽으로 마음이 기움
특히 내 프리랜서 이메일이 수신자 시스템에서 스팸으로 들어간다는 정보를 점점 더 많이 받음. 다만 Google 생태계에 익숙해진 습관을 어떻게 버릴지는 막막함 - 왜 앱 전용 비밀번호는 공개 표준이고 OAuth는 아니라고 보는지 궁금함
- 앱 비밀번호를 쓰면 IMAP 전체 접근이 가능하다는 점에서 무슨 말을 하려는지 잘 모르겠음
- 무료 Gmail 주소로 받는 스팸의 양과, Gmail 서버에서 비-Gmail 계정으로 오는 스팸의 양을 보면 점점 탈Google 쪽으로 마음이 기움
-
최근 내 앱 https://github.com/rumca-js/Django-link-archive에 Gmail을 통합하려고 했는데 시간을 너무 많이 썼고, Gmail 지원은 할 가치가 없다고 판단함
Gmail to SQLite는 자격 증명 설정을 6단계로 설명하지만, 내 경우엔 그렇지 않았음. 6단계 뒤에도 Google은 앱이 게시되지 않았다고 해서 게시해야 했고, Workspace 사용자가 아니라 내부 앱으로 둘 수 없다고 했고, 외부 앱으로 바꾸자 검증 전에는 쓸 수 없다고 했음
검증 과정에서는 도메인, 주소, 기타 세부 정보, 범위에 대한 정당화, 앱 사용 방식을 설명하는 동영상까지 요구했고, 제출한 데이터를 검증하는 데 시간이 걸린다고 했음. 전체가 설정 미로 같고, 사용자가 Google이 요구하는 장애물을 넘게 만드는 건 너무 과함- Google이 API 키 하나 받는 데도 사람들에게 시키는 절차는 완전히 말도 안 됨. 왜 이렇게까지 나쁜지 아는 사람이 있는지 궁금함
- 그냥 예전 방식의 IMAP과 앱 비밀번호를 쓰면 됨. Google의 장애물에 맞춰 뛰어넘을 필요 없음
-
지금 존재하는 최고의 오픈소스 Gmail 백업 소프트웨어가 뭔지 궁금함. 첨부파일 보관까지 포함해서 이런 걸 구성해 본 사람이 있는지 궁금함
- https://github.com/GAM-team/got-your-back가 있음. 오픈소스이고, 재개 기능이 있어서 백업과 복원이 결국 완료됨
참고로 https://www.mailstore.com/en/products/mailstore-home/도 있음. 오픈소스는 아니지만 인덱스가 있는 GUI라 로컬 메일 검색에 좋고, 재개는 백업에만 돼서 대용량 복원은 보통 실패함 - 정확히 원하는 답은 아닐 수 있지만, Google에는 Takeout이라는 서비스가 있어서 Gmail을 포함한 모든 Google 서비스 데이터의 백업을 요청하고 내려받을 수 있음
몇 달마다 이걸 실행하라고 알림을 걸어 두고 로컬 백업을 갱신함. 기억으로는 gzip 압축된 mbox 파일로 내려옴 - IMAP 클라이언트를 쓰고 오프라인/다운로드 모드로 설정하면 전체를 내려받아 로컬에 저장할 수도 있음. Evolution에서는 “오프라인 모드”라고 부르는 것 같은데 Thunderbird나 다른 클라이언트에서는 이름이 다를 수 있음
- https://github.com/GAM-team/got-your-back가 있음. 오픈소스이고, 재개 기능이 있어서 백업과 복원이 결국 완료됨
-
이건 “Gmail to SQLite”가 아니라 “IMAP to SQLite” 같은 이름이어야 하지 않나 싶음. 왜 특정 이메일 제공자 하나에 묶는지 모르겠음
- 실제로 Gmail 전용이기 때문임. OAuth와 아마 API 접근을 사용하고 있음
IMAP은 훨씬 어렵고 훨씬 느리며, Google의 대역폭 제한에도 묶임 - 몇 년 동안 IMAP으로 Gmail 계정을 백업하려고 했지만, Gmail 전용 도구를 포함해 한 번도 성공하지 못했음. 제일 나은 동기화 도구도 한 달 동안 돌다가 특정 메일을 가져오지 못하는 지점에 걸렸음
너무 차가운 저장소에 있어서 시간 초과가 난 건지 모르겠음. 그래서 Google의 독점 API를 쓰는 쪽이 더 잘 작동할 수 있다는 건 이해함
요즘은 Google Takeout에 mbox가 포함되고 제대로 작동하며 꽤 빠르지만, 지속적인 업데이트는 안 됨. 결국 다른 메일 제공자인 Infomaniak으로 옮겼고, 예전에 내 메일 도메인을 써 둔 나 자신에게 감사했음
- 실제로 Gmail 전용이기 때문임. OAuth와 아마 API 접근을 사용하고 있음
-
전체 텍스트 검색도 켤 수 있으면 좋겠음
- 검색 회사가 운영하는 것치고 Gmail의 전체 텍스트 검색이 놀랄 만큼 별로라고 느낌
-
어제 나도 같은 걸 만들었음. 수신자 이메일을 도메인별로 나열하고 싶었기 때문임. 코드는 엉망이지만 여기 있음: https://github.com/hugoferreira/gmail-sqlite-db
-
PostgreSQL 기반 IMAP 서버 Archiveopteryx가 조금 떠오름: https://github.com/aox/aox
AOX의 스키마는 늘 좋아 보였지만 실제로 제대로 써보지는 못했음. 주 용도는 데일리 드라이버 IMAP 서버가 아니라 메일 분석과 검색이었음- Manitou-Mail도 떠오름. 데일리 드라이버로 쓸 수 있는 강력한 PostgreSQL 기반 전용 메일 클라이언트이고 꽤 견고함: https://www.manitou-mail.org/
-
여기서 대역폭 비용은 어떻게 되는지 궁금함. 40GB 이상 Gmail 계정을 가진 입장에서 이 도구로 전송하면 요금이 청구되는지 알고 싶음
고치기는 쉬움. Google Takeout이 무료인 것 같으니 먼저 내려받고 파일을 파싱하면 됨. 그래도 바로 시작한다는 관점에서는 이 도구가 더 빠를 듯함