gjoko-ai-agent
A self-hosted personal AI agent for small, useful automations across everyday tools. It can chat with you through Telegram, summarize Gmail, capture quick notes into Notion, send local weather clothing advice, create and report on Google Calendar events, and expose password-protected setup utilities. Each capability is optional and enabled independently with environment variables; there is no database.
Features
Telegram AI assistant
Message the bot and get OpenAI-powered replies in the same Telegram chat.
Notion note capture
Save *-prefixed Telegram messages to Notion, including automatic subpages with *<name>, <note>.
Reply-to-note capture
Reply to any Telegram message with * to save the replied message text into Notion.
Gmail summaries
Connect up to 5 Gmail accounts with read-only OAuth and summarize unread messages with OpenAI.
Telegram delivery
Receive Gmail summaries in Telegram, including category prefixes for social and promotions.
Daily weather report
Get 07:30 and 20:30 Europe/Ljubljana Vransko forecasts with clothing advice for adults and kids.
Daily calendar report
Get a 20:00 Europe/Ljubljana Telegram message with tomorrow's Google Calendar events, holidays, and birthdays.
Calendar event creation
Send Telegram messages like add event Dentist on 2026-05-21 at 14:30 to create Google Calendar events.
Cron automation
Run scheduled work every 30 minutes through Vercel Cron or any external cron caller.
Manual utilities
Password-protected pages for Telegram webhook setup, Telegram delivery tests, and manual mail checks.
Readiness checks
Setup/status UI shows which optional modules are enabled and which env vars are missing.
No database
Configuration is env-var driven, keeping deployment simple and portable.
The problem
Email fatigue is real. Inbox zero feels unreachable. Important messages get buried under newsletters, receipts, and promotions. On top of that, I kept losing quick thoughts I wanted to jot down — scattered across chat apps, drafts, and sticky notes. I wanted one place that could triage my inbox and capture my notes without extra tabs or apps.
How it works
The agent has three independent features, each enabled only when its required env vars are present:
1. Email Summaries
A cron job fetches unread Gmail messages via the Gmail API, sends them to OpenAI for summarization, and delivers the result to a Telegram chat. It processes inbox, social, and promotions — skipping spam and very short emails. Social emails get a 👥 prefix and promotions get 🏷️.
Requires: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REFRESH_TOKEN, OPENAI_API_KEY, TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID
2. Telegram Assistant
When you send a text message to the bot, it calls OpenAI (default model: gpt-5-nano) and replies in the same chat. No context window management — just quick Q&A whenever you need it.
Requires: TELEGRAM_BOT_TOKEN, OPENAI_API_KEY
3. Notion Notes
Messages that start with * are saved to a Notion page instead of being sent to OpenAI. *note text appends to the root Notes page. *<name>, <note> appends to a subpage with that name, auto-created if it doesn't exist.
Requires: TELEGRAM_BOT_TOKEN, NOTION_API_KEY, NOTION_NOTES_PAGE_ID
What a summary looks like
For each unread email, the AI produces something like:
📧 Short title here
• Bullet 1: key fact
• Bullet 2: key fact
• Bullet 3: key fact (optional)
👥 Social email title
• Who interacted, what happened
🏷️ Promo email title
• Deal summary, deadlineTechnical choices
- No database — all configuration via environment variables
- No globally required vars — each feature self-enables when its vars are present
- Minimal UI — web pages only for OAuth setup, webhook config, and Telegram test
- Serverless — Next.js API routes on Vercel
- Stateless — designed for cron runs, not long-lived processes
Environment variables
| Variable | Used by |
|---|---|
| GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET | Gmail OAuth + email summaries |
| GOOGLE_REFRESH_TOKEN … _5 | Gmail accounts (up to 5) |
| OPENAI_API_KEY | Email summaries + Telegram assistant |
| OPENAI_MODEL | Optional model override (default gpt-5-nano) |
| TELEGRAM_BOT_TOKEN | Assistant, email delivery, webhook, test |
| TELEGRAM_CHAT_ID | Email delivery destination + /telegram-test |
| NOTION_API_KEY, NOTION_NOTES_PAGE_ID | Telegram * notes → Notion |
| APP_URL | Gmail OAuth redirect + webhook setup |
| TEST_PASSWORD | Protected utility pages |
| CRON_SECRET | Optional bearer auth for external cron |
| TELEGRAM_WEBHOOK_SECRET | Optional webhook header validation |
| MAX_EMAILS_PER_RUN, LABEL_FILTER | Optional email-processing tweaks |
Quick setup
Google Cloud (optional — email summaries only)
- Create a project in Google Cloud Console and enable the Gmail API
- Configure the OAuth consent screen (External, add your email as a test user)
- Create OAuth 2.0 credentials and add the redirect URI:
https://your-domain.com/api/auth/gmail
Telegram Bot (optional — needed for Telegram features)
- Message @BotFather to create a bot; copy the token
- Message @userinfobot to get your chat ID
- Open
/telegram-webhookin the UI and submit to register the webhook automatically
Notion Notes (optional)
- Create a Notion integration and copy the secret
- Share a Notion page with the integration and set
NOTION_NOTES_PAGE_ID - Subpages are auto-managed:
*<name>, <note>creates or appends to a subpage
Gmail Refresh Tokens (optional)
- Run
npm run devand click Setup Gmail in the UI - Complete the OAuth flow and copy the refresh token
- Repeat for additional accounts at
/api/auth/gmail?action=init&account=2(up to 5)
Running
npm install
npm run dev # development
npm run build && npm start # productionScheduling
On Vercel the cron runs every 30 minutes automatically. For external cron, hit the endpoint with a bearer token:
*/30 * * * * curl -H "Authorization: Bearer $CRON_SECRET" \
https://your-domain.com/api/cron/processIf email summaries are not configured, the endpoint returns processed: 0 with a skipped reason — no error.
Why I like it
I no longer open Gmail to see what's new. Summaries land in Telegram alongside my regular chats, so I triage without context-switching. When I have a quick question I message the bot directly. And any thought I want to keep goes straight to Notion with a single * prefix — no app-hopping, no friction.