Gmail MailArchive
A self-hosted Gmail archiver that syncs emails via IMAP to .eml files on a PVC, with a three-pane web UI. Runs on any kubernetes with persistent storage.
GitHub - jdtate101/MailArchive: A simple offline storage app for GMail on K8S, designed to archive you emails
A simple offline storage app for GMail on K8S, designed to archive you emails - jdtate101/MailArchive
Architecture
┌─────────────────────────────────────────────────────────┐
│ Browser │
│ ┌──────────────┬──────────────────┐ │
│ │ Folder List │ Email List │ ← top 42% │
│ ├──────────────┴──────────────────┤ │
│ │ Email Detail (HTML rendered) │ ← bottom 58% │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────┐ ┌──────────────────────┐
│ Frontend (nginx) │────▶│ Backend (FastAPI) │
│ React SPA │ │ IMAP sync engine │
│ mailarchive-frontend│ │ mailarchive-backend │
└──────────────────────┘ └──────────┬───────────┘
│
┌─────────▼──────────┐
│ Longhorn PVC 50Gi │
│ /mail/ │
│ INBOX/ │
│ Amazon/ │
│ Apple/ │
│ ... (19 labels) │
└─────────────────────┘
Gmail Setup
Before deploying, you need a Gmail App Password:
- Go to your Google Account → Security
- Enable 2-Step Verification (required for app passwords)
- Go to Security → App passwords
- Create a new app password (name it "MailArchive")
- Copy the 16-character password — you'll need it below
Quick Start
1. Build and push images
# Backend
cd backend/
docker build -t your-registry/mailarchive-backend:latest .
docker push your-registry/mailarchive-backend:latest
# Frontend
cd ../frontend/
docker build -t your-registry/mailarchive-frontend:latest .
docker push your-registry/mailarchive-frontend:latest
Replace your-registry with your Harbor instance (you're running Harbor on the cluster).2. Edit credentials
Edit k8s/secret.yaml with your Gmail address and app password:
stringData:
IMAP_USER: "yourname@gmail.com"
IMAP_PASS: "xxxx xxxx xxxx xxxx" # 16-char app password (spaces OK)
3. Update image references
Edit k8s/backend-deployment.yaml and k8s/frontend-deployment.yaml:
image: your-registry/mailarchive-backend:latest
image: your-registry/mailarchive-frontend:latest
4. Update ingress hostname
Edit k8s/frontend-deployment.yaml, find the Ingress section:
host: mail.home # Change to match your Pi-hole .home TLD setup
5. Deploy
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/secret.yaml
kubectl apply -f k8s/pvc.yaml
kubectl apply -f k8s/backend-deployment.yaml
kubectl apply -f k8s/frontend-deployment.yaml
6. Verify
# Check all pods are running
kubectl get pods -n mailarchive
# Watch backend logs (first sync will start automatically)
kubectl logs -n mailarchive -l component=backend -f
# Check sync status via API
kubectl port-forward -n mailarchive svc/mailarchive-backend 8000:8000
curl http://localhost:8000/api/status
Configuration
All backend config is via environment variables in k8s/backend-deployment.yaml:
| Variable | Default | Description |
|---|---|---|
IMAP_HOST |
imap.gmail.com |
IMAP server |
IMAP_PORT |
993 |
IMAP SSL port |
IMAP_USER |
(from secret) | Gmail address |
IMAP_PASS |
(from secret) | App password |
MAIL_ROOT |
/mail |
PVC mount path |
SYNC_INTERVAL_HOURS |
24 |
Hours between syncs |
Sync Behaviour
- First run: Full sync of all 19 labels + INBOX. This may take a while for large mailboxes.
- Subsequent runs: Only fetches emails with UIDs higher than the last seen UID per folder.
- State file: Stored at
/mail/.sync_state.jsonon the PVC. Delete this file to force a full re-sync. - Manual sync: Hit the "Sync Now" button in the UI, or
POST /api/sync.
Storage Layout
/mail/
.sync_state.json ← sync state (last UID per folder)
INBOX/
1_abc123def456.eml
2_fed654cba321.eml
...
Amazon/
...
Ebay & Paypal/
...
(one folder per Gmail label)
Expanding the PVC
If you need more space:
kubectl patch pvc mailarchive-pvc -n mailarchive \
-p '{"spec":{"resources":{"requests":{"storage":"100Gi"}}}}'
Longhorn will handle the expansion online without downtime.
API Reference
| Endpoint | Method | Description |
|---|---|---|
/api/status |
GET | Sync status and stats |
/api/sync |
POST | Trigger manual sync |
/api/folders |
GET | List all folders with email counts |
/api/emails/{folder} |
GET | List emails in folder (?skip=0&limit=200) |
/api/email/{folder}/{filename} |
GET | Full email detail with body and attachments |
Adding/Removing Labels
Edit the GMAIL_LABELS list in backend/main.py and rebuild the backend image. New labels will be created as folders on the next sync.
Local Development
# Backend
cd backend/
pip install -r requirements.txt
IMAP_USER=you@gmail.com IMAP_PASS=yourapppass MAIL_ROOT=./mail python main.py
# Frontend (separate terminal)
cd frontend/
npm install
npm run dev # Proxies /api to localhost:8000