A simple, self-hosted Gmail unread counter using OAuth2.
- Check Gmail unread count without opening Gmail
- OAuth2 authentication (no password storage)
- Dark/light theme toggle
- Single-user, self-hosted
This app is designed for single-user, authenticated access behind a reverse proxy. Without proper authentication, anyone with the URL could:
- Trigger OAuth flows using your credentials
- Access your Gmail data if already authenticated
- Web frontend joined to cloudflare
- Go to Google Cloud Console
- Create a new project (or select existing)
- Enable the Gmail API:
- Navigate to "APIs & Services" > "Library"
- Search for "Gmail API"
- Click "Enable"
- Configure OAuth consent screen:
- "APIs & Services" > "OAuth consent screen"
- Choose "Internal" (if using Google Workspace) or "External"
- Fill in app name, user support email, developer contact
- Add scope:
https://www.googleapis.com/auth/gmail.readonly
- Create credentials:
- "APIs & Services" > "Credentials"
- Click "Create Credentials" > "OAuth client ID"
- Application type: "Web application"
- Add authorized redirect URI:
https://mail.yourdomain.com/api.php?action=callback - Click "Create"
- Copy the Client ID and Client Secret
-
Copy
config.json.exampletodata/config.json:mkdir -p data cp config.json.example data/config.json
-
Edit
data/config.jsonwith your credentials:{ "client_id": "your-client-id.apps.googleusercontent.com", "client_secret": "GOCSPX-your-client-secret", "redirect_uri": "https://mail.yourdomain.com/api.php?action=callback" } -
Set permissions:
chmod 600 data/config.json
-
Create SWAG subdomain config (
/config/nginx/proxy-confs/mail.subdomain.conf):server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name mail.*; include /config/nginx/ssl.conf; location / { include /config/nginx/proxy.conf; include /config/nginx/resolver.conf; # Add authentication include /config/nginx/authelia-server.conf; proxy_pass http://mail-check:80; } }
-
Add to docker-compose.yml:
mail-check: image: aevrin/easy:latest container_name: mail-check volumes: - ./app:/app - ./data:/app/data networks: - swag restart: unless-stopped
-
Restart SWAG:
docker restart swag
-
Install nginx and certbot:
sudo apt install nginx certbot python3-certbot-nginx
-
Create nginx config (
/etc/nginx/sites-available/mail):server { listen 80; server_name mail.yourdomain.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name mail.yourdomain.com; ssl_certificate /etc/letsencrypt/live/mail.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/mail.yourdomain.com/privkey.pem; # Basic auth (replace with your auth method) auth_basic "Restricted Access"; auth_basic_user_file /etc/nginx/.htpasswd; location / { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }
-
Enable site and get SSL cert:
sudo ln -s /etc/nginx/sites-available/mail /etc/nginx/sites-enabled/ sudo certbot --nginx -d mail.yourdomain.com sudo nginx -t && sudo systemctl reload nginx -
Create basic auth (if using):
sudo apt install apache2-utils sudo htpasswd -c /etc/nginx/.htpasswd yourusername
-
Install cloudflared:
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb sudo dpkg -i cloudflared-linux-amd64.deb
-
Authenticate:
cloudflared tunnel login
-
Create tunnel:
cloudflared tunnel create mail-check cloudflared tunnel route dns mail-check mail.yourdomain.com
-
Create config (
~/.cloudflared/config.yml):tunnel: <tunnel-id> credentials-file: /home/user/.cloudflared/<tunnel-id>.json ingress: - hostname: mail.yourdomain.com service: http://localhost:8080 originRequest: noTLSVerify: true - service: http_status:404
-
Run tunnel:
cloudflared tunnel run mail-check
-
Add Cloudflare Access for authentication (in Cloudflare dashboard)
- Navigate to
https://mail.yourdomain.com - Click "Connect with Google"
- Authorize the application
- You'll be redirected back with your unread count
.
├── index.html # Main UI
├── app.js # Frontend logic
├── style.css # Styling
├── api.php # Backend API
├── config.json.example # Sample config
├── data/ # Runtime data (gitignored)
│ ├── config.json # Your OAuth credentials
│ └── token.json # OAuth tokens (auto-generated)
└── README.md
- Never commit
data/to git - Add to.gitignore - Use authentication - SWAG/Authelia, HTTP basic auth, or Cloudflare Access
- Use HTTPS - Required for OAuth2
- Restrict to internal network - Or use VPN/VPC
- Set file permissions:
chmod 600 data/config.json chmod 600 data/token.json
- Check that
data/config.jsonexists and is valid JSON - Ensure all three fields are filled in
- The
redirect_uriin Google Console must exactly match the one inconfig.json - Include the full path:
https://mail.yourdomain.com/api.php?action=callback
- Click "Disconnect" and re-authenticate
- Check that Gmail API is enabled in Google Cloud Console
- Verify OAuth consent screen is published (if using "External")
- Delete
data/token.jsonand re-authenticate - Check that
client_secrethasn't changed in Google Console
- This app only requests
gmail.readonlyscope (cannot send/delete emails) - Tokens are stored locally in
data/token.json - No data is sent to third parties
- No analytics or tracking
MIT - Do whatever you want with it.