Code
Clone this repository
Search code…
1.2.0 이후 목록의 감염 첨부와 검사 대기 표시, 확인 필요 수, 처리 뒤 다음 메일 열기와 되돌리기 복귀, 관련 대화 목록, 설정 삭제 재인증이 추가돼 1.3.0으로 올립니다. 검증: client lint, npm test(pass 102, fail 0, Linux 전용 4개 건너뜀), verify:ui, verify:secrets, Docker 이미지 빌드와 verify-docker.js(106 통과, 건너뜀 0), 이미지 비밀값 검사 통과. npm audit 운영 의존성 0건. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
개인 메일함
내 도메인으로 오는 메일을 Cloudflare Email Routing으로 받아서 내 NAS에 원문 그대로 보관하고, 웹 브라우저로 읽는 수신 전용 메일함입니다. 관리자 한 명이 쓰도록 만들었고, 로그인은 패스키로만 합니다.
메일 서버를 직접 운영하지 않아도 됩니다. SMTP 수신은 Cloudflare가 맡고, 이 프로젝트는 Worker가 서명해서 넘긴 원문을 받아 저장하고 보여 줍니다.
기술 스택: React 19, Node.js 24, SQLite FTS5, Cloudflare Email Worker, Docker Compose
주요 기능
- 원문 보관: 받은 메일을
.eml바이트 그대로 저장합니다. 파싱이나 검사가 실패해도 원문은 남아 있고, 언제든 다시 처리하거나 내보낼 수 있습니다. - 읽기 좋은 본문: HTML 메일의 색상, 표 레이아웃, 반응형 규칙을 살려서 보여 줍니다. 스크립트, 폼, 외부 CSS는 막고 외부 이미지는 버튼을 눌러야 불러옵니다.
- 첨부파일: PDF와 이미지는 바로 미리 보고, ZIP은 목록을 본 뒤 필요한 파일만 꺼냅니다. 암호가 걸린 PDF와 ZIP도 등록한 암호로 엽니다.
- 악성코드 검사: 모든 첨부는 ClamAV 검사를 통과해야 미리보기와 다운로드가 열립니다. 첨부마다 검사 상태와 지금 할 수 있는 일을 함께 보여 줍니다.
- 암호화 메일: OpenPGP(PGP/MIME, 인라인)와 S/MIME 메일을 복호화하고 서명을 확인합니다. 개인키와 암호는 암호화해서 보관합니다.
- 정리와 검색: 한글 부분 검색, 라벨, 별표, 중요 표시, 보관함, 스팸, 휴지통, 대화 묶음을 지원합니다.
from:,to:,label:,has:attachment,is:unread,before:같은 검색 연산자로 범위를 좁힐 수 있고, 오래된 스팸은 정한 기간이 지나면 휴지통으로 옮깁니다. - 대화별 보기: 목록 머리의 버튼 하나로 같은 대화의 메일을 한 줄로 묶어 메일 수와 안 읽은 수를 보여 줍니다. 묶은 행을 보관하거나 휴지통으로 보내면 지금 보는 폴더와 검색에 맞는 대화 속 메일이 함께 옮겨집니다.
- 빠른 정리와 되돌리기: 여러 통을 골라 읽음, 보관, 스팸, 휴지통, 별표를 한 번에 적용합니다. 보관, 스팸, 휴지통 같은 정리 뒤에는 화면 알림에 되돌리기 버튼이 붙고, Z 키로도 바로 되돌립니다.
- 수신 주소 규칙: catch-all로 받는 별칭마다 넣을 폴더, 붙일 라벨, 알림 여부를 정합니다. 더는 쓰지 않는 주소는 차단하면 저장하지 않고 SMTP 단계에서 거부합니다.
- 발신 인증 표시: 믿을 수신 서버로 지정한 곳이 기록한 SPF, DKIM, DMARC 결과를 메일 상세에서 보여 주고, 뉴스레터의 수신 거부 페이지를 확인 후 엽니다. DMARC 실패나 서명 문제가 있는 메일은 목록에서부터
사칭 의심,서명 문제표시가 붙습니다. - 내보내기와 백업: 메일함 전체나 폴더를 mbox 파일로 내려받습니다. 매일 만드는 백업은 바뀌지 않은 파일을 하드 링크로 공유해서 DB 스냅샷 말고는 새 메일만큼만 공간을 씁니다.
- 어느 기기에서나: PC는 3단 화면, 태블릿은 폴더 아이콘 레일과 목록, 본문, 휴대폰은 목록과 본문을 오가는 화면으로 바뀝니다. 다크 모드는 직접 고르기 전까지 기기 설정을 따르고, 키보드 단축키(J/K, /, Esc)도 있습니다.
- 앱 설치와 알림: 홈 화면에 앱으로 설치할 수 있고, 새 메일이 오면 Web Push로 알려 줍니다. 알림에는 발신자나 제목을 넣지 않습니다.
화면
PC에서는 폴더, 목록, 본문을 한 화면에 둡니다. 목록과 본문 사이 경계를 끌어서 목록 너비를 바꿀 수 있습니다. 목록의 사칭 의심 표시는 믿을 수신 서버가 DMARC 실패로 기록한 메일입니다.
첨부파일은 검사가 끝나면 앱 안에서 바로 열어 봅니다.
| 이미지 미리보기 (다크 모드) | PDF 미리보기 |
|---|---|
![]() |
![]() |
휴대폰에서는 목록, 본문, 설정이 한 화면씩 나옵니다.
화면은
npm run docs:screenshots로 만든 예시 메일입니다. 메일 내용과 주소는 모두 가상입니다.
동작 방식
보낸 사람 ─SMTP→ Cloudflare Email Routing → Email Worker (원문 + HMAC 서명)
│ HTTPS
▼
리버스 프록시 → 앱 컨테이너 /webhook/v2
│
원문 파일 저장(fsync, 원자 이동) + SQLite 기록
│ 여기까지 끝나야 성공 응답
▼
백그라운드 처리: MIME 파싱 → 복호화 → ClamAV 검사 → 웹 화면
- Worker는 받은 원문의 SHA-256과 봉투 정보(envelope)를 HMAC으로 서명해서 서버로 보냅니다. 서버가 잠시 응답하지 않으면 약 1분 동안 같은 요청을 몇 번 더 보내므로, 컨테이너를 짧게 다시 만드는 동안 온 메일은 재시도로 전달됩니다.
- 서버는 서명, 시각, 크기를 확인하고 원문을 디스크에 저장한 뒤 DB에 기록합니다. 이 두 가지가 끝나야 Worker에 성공을 돌려줍니다. 차단한 주소로 온 메일은 본문을 받기 전에 거부합니다.
- 본문 파싱, 첨부 추출, 바이러스 검사는 그 다음에 별도 프로세스에서 진행합니다. 여기서 실패해도 원문은 이미 안전하게 저장된 상태이고, ClamAV가 잠시 멈추면 돌아온 뒤 자동으로 다시 검사합니다.
자세한 프로토콜과 저장 구조는 구조 문서에 있습니다.
시작하기
필요한 것:
- Docker Compose를 쓸 수 있는 Linux amd64 서버(NAS 등). 메모리 한도는 앱 768 MiB, ClamAV 3 GiB입니다.
- Cloudflare에서 Email Routing을 켠 도메인
- HTTPS로 앱을 연결할 리버스 프록시
# 1. 설정 파일을 만들고 이미지 주소, 앱 주소, 수신 도메인, 비밀값을 채웁니다.
cp .env.example .env
# 2. 저장 디렉터리와 마스터키를 준비합니다.
mkdir -p data/data data/logs data/backups data/clamav-db data/secrets
openssl rand -hex 32 > data/secrets/master.key
sudo chown -R 1000:1000 data
chmod 700 data/data data/logs data/backups data/clamav-db
chmod 400 data/secrets/master.key
# 3. 컨테이너를 실행합니다.
docker compose pull
docker compose up -d --no-build
그다음 Cloudflare Worker를 배포하고 Email Routing의 catch-all 규칙을 Worker로 연결합니다. 브라우저에서 앱 주소를 열고 .env의 BOOTSTRAP_PASSWORD를 입력하면 첫 패스키를 등록할 수 있습니다.
단계별 설명, 설정값, 프록시 조건은 운영 가이드를 따라가세요.
문서
| 문서 | 내용 |
|---|---|
| 운영 가이드 | 설치, 설정값, 프록시, Worker 연결, 앱 설치와 알림, 발신 인증 표시, 수신 주소 규칙, 복호화 키, 스팸 보관 기간, 내보내기, 상태 확인, 업데이트, 백업과 복원, 패스키 복구, 기존 DB 이관 |
| 구조 | 수신 프로토콜, Worker 재시도, 저장 구조, 메일 처리와 재시도, 검색, 인증과 보안 설계, 코드 위치 |
| 개발 | 로컬 실행, 시험과 검증 명령, 화면 캡처 갱신, 릴리스 |
| AGENTS.md | 코딩 에이전트가 지킬 작업 규칙 |



