A lightweight, fully autonomous automation service that connects multiple Gmail accounts, fetches unread emails daily, filters out noise, and delivers a clean aggregated digest to Telegram and Discord β completely hands-free.
Deployed on Render with MongoDB Atlas for persistent cloud storage and cron-job.org for external scheduling.
- Multi-Account Support β Connect multiple Gmail accounts via Google OAuth 2.0
- Daily Automated Digest β Runs via external cron (production) or internal scheduler (development)
- Deterministic Filtering β Strips out promotions, social updates, newsletters, and spam without AI/NLP
- Dual Delivery β Sends digest to both Telegram and Discord simultaneously
- Rich Discord Embeds β Each account gets a color-coded embed card for instant visual differentiation
- Telegram HTML Formatting β Clean, structured digest with emoji and grouped sections
- Failure Isolation β If one delivery channel fails, the other still works
- Retry Logic β Automatic retry with 3-second delay on delivery failures
- Protected API β
/digest/runendpoint secured with Bearer token authentication - Cloud Persistence β MongoDB Atlas ensures data survives container restarts and redeploys
- Run History β Track past digest executions via the API
- Graceful Error Handling β Partial failures, expired tokens, and empty inboxes are all handled cleanly
The system operates as 5 autonomous agents coordinating through a pipeline:
| # | Agent | Role | Module |
|---|---|---|---|
| 1 | Orchestrator | Timekeeper & process manager | src/scheduler/cron.ts |
| 2 | Fetcher | Gmail API data retriever | src/gmail/fetcher.ts |
| 3 | Bouncer | Deterministic noise filter | src/gmail/filter.ts |
| 4 | Editor | Digest formatter (Telegram + Discord) | src/digest/formatter.ts |
| 5 | Courier | Delivery to Telegram & Discord | src/telegram/sender.ts, src/discord/sender.ts |
Cron Trigger (external or internal)
β Load Gmail accounts from MongoDB
β For each account: Authenticate β Fetch unread emails β Filter noise
β Format digest (Telegram HTML + Discord Embeds)
β Deliver to Discord
β Deliver to Telegram
β Log run result to MongoDB
| Component | Technology |
|---|---|
| Runtime | Node.js + TypeScript |
| Database | MongoDB Atlas (cloud) |
| Hosting | Render (free tier) |
| Scheduling | cron-job.org (external) |
| Delivery | Telegram Bot API + Discord Webhooks |
daily-gmail-digest/
βββ src/
β βββ auth/ # Google OAuth 2.0 flows
β β βββ oauth.ts # OAuth client, auth URL, token exchange & refresh
β β βββ routes.ts # Express routes: /auth/google, /auth/google/callback
β βββ config/ # Environment variable validation
β β βββ index.ts # Typed config with startup validation
β βββ db/ # Database layer
β β βββ client.ts # MongoDB connection via mongoose
β β βββ schema.ts # Mongoose models (GmailAccount, DigestRun)
β βββ digest/ # Digest formatting
β β βββ formatter.ts # Telegram (HTML) and Discord (embeds) formatters
β βββ discord/ # Discord delivery
β β βββ sender.ts # Webhook-based delivery with retry & embed batching
β βββ gmail/ # Gmail integration
β β βββ fetcher.ts # Fetches unread emails via Gmail API
β β βββ filter.ts # Deterministic spam/promo filtering
β βββ scheduler/ # Cron orchestration
β β βββ cron.ts # Daily pipeline coordinator
β βββ telegram/ # Telegram delivery
β β βββ sender.ts # Bot API delivery with retry & message chunking
β βββ utils/ # Shared utilities
β β βββ logger.ts # Structured logging with agent tags
β βββ server.ts # Main entry point (Express + MongoDB + cron startup)
βββ docs/ # Project planning documents
β βββ AGENTS.md
β βββ ARCHITECTURE.md
β βββ IDEA.md
β βββ IMPLEMENTATION.md
β βββ PLAN.md
βββ .env.example # Template for environment variables
βββ .gitignore
βββ LICENSE
βββ package.json
βββ package-lock.json
βββ tsconfig.json
βββ README.md
- Node.js v18+ (required for native
fetchsupport) - npm v8+
- A Google Cloud Project with Gmail API enabled
- A MongoDB Atlas cluster (free tier available)
- A Telegram Bot (created via @BotFather)
- A Discord Webhook URL (optional β created in Discord channel settings)
git clone https://github.com/DevDebpriyo/Daily-Digest.git
cd Daily-Digestnpm installcp .env.example .envEdit .env and fill in your credentials:
# Google OAuth 2.0 Credentials
GOOGLE_CLIENT_ID=your_client_id
GOOGLE_CLIENT_SECRET=your_client_secret
GOOGLE_REDIRECT_URI=http://localhost:3000/auth/google/callback
# Telegram Bot
TELEGRAM_BOT_TOKEN=your_telegram_bot_token
TELEGRAM_CHAT_ID=your_telegram_chat_id
# Discord Webhook (optional β skip to disable Discord delivery)
DISCORD_WEBHOOK_URL=your_discord_webhook_url
# MongoDB Atlas
MONGODB_URI=mongodb+srv://user:password@cluster.mongodb.net/daily-digest
# Scheduler (cron expression β default: every day at 6:00 AM)
CRON_SCHEDULE=0 6 * * *
# Server
PORT=3000
# Internal / External CRON
ENABLE_INTERNAL_CRON=true # true -> local development, false -> production
# Secret token for protecting the /digest/run endpoint
CRON_SECRET=my_super_secret_key# Build TypeScript β JavaScript
npm run build
# Start the production server
npm startOr run directly in development mode:
npm run dev- Open your browser and navigate to:
http://localhost:3000/auth/google - Sign in with your Google account and grant Gmail read-only access
- You'll receive a confirmation with the connected email address
- Repeat for each Gmail account you want to monitor
- Go to the Google Cloud Console
- Create a new project (or select an existing one)
- Enable the Gmail API under APIs & Services β Library
- Go to APIs & Services β Credentials
- Click Create Credentials β OAuth 2.0 Client ID
- Set Application type to Web application
- Add
http://localhost:3000/auth/google/callbackas an Authorized redirect URI - Copy the Client ID and Client Secret into your
.envfile
- Go to MongoDB Atlas and create a free account
- Create a new Shared Cluster (M0 free tier works fine)
- Go to Database Access β Add a database user with read/write permissions
- Go to Network Access β Add
0.0.0.0/0to allow connections from anywhere (required for Render) - Go to Database β Click Connect β Choose Connect your application
- Copy the connection string and paste it into your
.envasMONGODB_URI - Replace
<password>with your database user's password
- Open Telegram and message @BotFather
- Send
/newbotand follow the prompts to create your bot - Copy the bot token into your
.envasTELEGRAM_BOT_TOKEN - To get your Chat ID:
- Message your bot, then visit:
https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates - Find the
chat.idvalue in the response
- Message your bot, then visit:
- Copy the chat ID into your
.envasTELEGRAM_CHAT_ID
- Open Discord and go to the channel where you want digests delivered
- Click the gear icon (Edit Channel) β Integrations β Webhooks
- Click New Webhook, name it (e.g., "Daily Digest"), and copy the Webhook URL
- Paste the URL into your
.envasDISCORD_WEBHOOK_URL
Note: Discord delivery is optional. If
DISCORD_WEBHOOK_URLis not set, the system will skip Discord and still deliver to Telegram.
| Method | Endpoint | Description |
|---|---|---|
GET |
/ |
Health check β returns service status |
GET |
/auth/google |
Initiates Google OAuth flow (redirects to Google) |
GET |
/auth/google/callback |
OAuth callback β exchanges code for tokens |
GET |
/auth/accounts |
Lists all connected Gmail accounts |
DELETE |
/auth/accounts/:id |
Removes a connected Gmail account |
POST |
/digest/run |
Manually triggers the digest pipeline (requires Authorization: Bearer <CRON_SECRET> header) |
GET |
/digest/history |
Returns the last 20 digest run records |
# Trigger a digest run manually (requires CRON_SECRET)
curl -X POST http://localhost:3000/digest/run \
-H "Authorization: Bearer my_super_secret_key"
# Check run history
curl http://localhost:3000/digest/history
# List connected accounts
curl http://localhost:3000/auth/accountsMongoDB Atlas is used for cloud persistence with the following collections:
| Collection | Purpose |
|---|---|
gmailaccounts |
Connected Gmail accounts with OAuth refresh tokens |
digestruns |
Audit log of every pipeline execution (time, status, details) |
Data persists permanently in the cloud β container restarts and redeploys on Render do not affect stored data.
The application supports two scheduling modes:
| Mode | ENABLE_INTERNAL_CRON |
How It Works |
|---|---|---|
| Production | false |
External service (e.g., cron-job.org) calls POST /digest/run with the CRON_SECRET |
| Development | true |
Internal node-cron runs on the CRON_SCHEDULE expression |
The CRON_SCHEDULE environment variable accepts standard cron expressions:
| Expression | Schedule |
|---|---|
0 6 * * * |
Every day at 6:00 AM (default) |
0 8 * * * |
Every day at 8:00 AM |
0 */6 * * * |
Every 6 hours |
*/30 * * * * |
Every 30 minutes (for testing) |
The Bouncer agent filters emails using deterministic rules β no AI required:
- Gmail Categories β Excludes
CATEGORY_PROMOTIONS,CATEGORY_SOCIAL,CATEGORY_FORUMS - Label IDs β Excludes
SPAMandTRASH - Keyword Patterns β Filters senders/subjects containing:
unsubscribe,no-reply,noreply,newsletter,notification@,updates@,marketing@,promo@,bulk@,mailer-daemon
| Scenario | Behavior |
|---|---|
| Gmail auth failure for one account | Skips the failed account, processes remaining, notes error in digest |
| All Gmail accounts fail | Sends a failure notification to Telegram & Discord |
| Telegram delivery fails | Retries once after 3 seconds; logs failure if retry also fails |
| Discord delivery fails | Retries once after 3 seconds; logged only, never crashes pipeline |
| Empty inbox (no new emails) | Sends a "No new emails today" confirmation digest |
| Missing Discord webhook | Silently skips Discord delivery, Telegram still works |
| Long message exceeds limits | Telegram: auto-splits at 4096 chars; Discord: batches at 10 embeds |
| MongoDB connection fails | Application logs error and exits β will be restarted by Render |
- Connect your GitHub repository to Render
- Create a new Web Service
- Set Build Command:
npm install && npm run build - Set Start Command:
npm start - Add all environment variables in the Render dashboard
- Set
ENABLE_INTERNAL_CRON=false(use external cron)
- Go to cron-job.org and create a free account
- Create a new cron job:
- URL:
https://your-app.onrender.com/digest/run - Method:
POST - Schedule: Your desired frequency (e.g., daily at 6:00 AM)
- Headers: Add
Authorization: Bearer <your_CRON_SECRET>
- URL:
- Save and enable the job
Why external cron? Render free tier instances sleep after inactivity. External cron ensures the digest runs reliably even if the instance was sleeping.
- β MongoDB Atlas cluster created and configured
- β All environment variables set on Render
- β
ENABLE_INTERNAL_CRON=falsefor production - β External cron job configured with correct URL and Bearer token
- β
Gmail accounts connected via
/auth/google - β
Verify health:
curl https://your-app.onrender.com/
- OAuth Tokens β Refresh tokens are stored in MongoDB Atlas; never exposed to any frontend
- Secrets β All credentials live in
.env(local) or platform env vars (production), excluded from version control via.gitignore - Gmail Scope β Only
gmail.readonlyis requested β the app cannot send, delete, or modify emails - Endpoint Protection β
/digest/runis protected by Bearer token authentication - No External Dependencies for HTTP β Telegram and Discord delivery use native
fetchβ no third-party HTTP libraries
This project is licensed under the MIT License.