Back to index
03AI Automation

Assistant

A private Telegram assistant that runs on my homelab. It answers questions about my schedule and courses, messages me 10 minutes before every lecture with a recap of where the last one stopped, and can search my university email read-only. The rule it's built on: code decides the facts, the AI only writes the words — and every reply is checked against the source data.

RoleDesign, build, ops
StartedSep 2026
Runs onHomelab — n8n + Claude Code
StatusLive, in daily use

Timeline

Click a stop to open the chapter
Sep 2026

n8n on the homelab

Stood up n8n in its own container behind the reverse proxy, firewalled so only the proxy and dashboard can reach it. Instead of a paid API key, n8n reaches Claude Code in the AI dev container over SSH, using a key that only works from n8n's address and can't open a shell session.

—Uses my existing Claude subscription through the Claude Code CLI
—The dev container accepts key-only SSH
—Workflows are exported, edited and re-imported as files
Sep 2026

Facts in code

The first version let the model read my schedule, and it dropped an event once in eight runs. I redesigned it: plain Python parses my timetable and deadlines, the model only turns that list into a friendly reply, and the script checks that every event title made it into the answer before sending it.

—If a title is missing, it retries with Codex, then sends the plain list with a warning
—When nothing is scheduled, a fixed message goes out with no AI call at all
—Same rule everywhere since: code decides the facts, the AI writes the words
Sep 2026

Telegram bot

Connected it to a Telegram bot that answers only me. It polls Telegram rather than using a webhook, because nothing on the homelab is exposed to the internet. Commands cover the next hour, today, tomorrow and the week; free text goes to a general assistant that can read my project notes and read or edit my uni and personal notes — and nothing else.

—No shell access and no delete — enforced by Claude Code's permission layer, verified by testing
—Private notes, credentials and system files are blocked even if asked directly
—Messages are marked as received before the slow AI step, so overlapping polls can't answer twice
Oct 2026

Lecture reminders

An n8n workflow checks my timetable during university hours and sends a reminder 10 minutes before each lecture, with the course's week topics and a recap of the previous lecture. Code picks which slides were covered from a progress log I update by message; the model only writes 3–4 bullets about them, with slide titles as the fallback.

—Course info (instructor, office hours, grading) written up from the syllabuses
—Slides converted to text so the recap can name what was covered
—Each workflow node carries a note explaining what it does
Oct 2026

Uni email, read-only

My university account doesn't allow app passwords, so instead of IMAP the assistant reaches Gmail through an MCP connector. Only the search and read tools are allowed; every write tool — send, reply, forward, draft, delete, label — is explicitly denied. Email access only switches on when my message is actually about email.

—Verified: asking it to send an email is refused
—Fixed a race where the connector wasn't ready on about 2 of 3 runs
—Tool list trimmed to keep each question's token cost down
Now

Daily use

It's in daily use. The same bot also carries gym reminders generated from a single plan file, and the homelab's uptime and update alerts. Next on the list is a morning brief.

—Gym reminders at 4 pm and 7 pm, read from one editable plan file
—Uptime Kuma and Diun alerts arrive through the same bot

Milestones

What broke

The AI dropped an event 1 time in 8
Letting the model read the schedule itself meant it occasionally left things out. Now plain code builds the list, the model only words it, and the script checks every title appears in the reply — falling back to Codex, then to the plain list, if anything is missing.
Overlapping polls answered the same message twice
n8n polls every 30 seconds, but a Claude reply can take longer than that, so the next poll saw the same message. Marking messages as received before the slow AI step fixed it.
University email has no app passwords
The uni disabled 2-Step Verification, so IMAP app passwords weren't an option. I used a Gmail MCP connector instead, locked to read-only tools, and forced it to finish connecting before each run after it raced and missed about 2 runs in 3.

From the repo

bin/uni-ask (condensed)
# code decides the facts, the AI only writes the words
events = agenda(window)            # parsed from timetable.md
if not events:
    return "Nothing scheduled."    # no AI call at all

for writer in (claude_haiku, codex):
    reply = writer(prompt(events, question))
    # every real event title must survive into the reply
    if reply and all(norm(e.title) in norm(reply) for e in events):
        return reply

return plain_list(events) + "\n(AI unavailable, raw list)"
Orchestrationn8n, self-hosted
WriterClaude Haiku / Sonnet via Claude Code CLI
FactsPython, Bash
FallbackCodex CLI → plain list
InterfaceTelegram Bot API, polling
EmailGmail via MCP, read-only
Next project
Jarvis
Open →