Academic inboxes are entropy sinks.
Between YΓK (Higher Education Council) regulatory audits, departmental webmaster change requests, BUVES institutional data integration deadlines, student grade inquiries, and an unceasing deluge of campus dining menus and seminar spam, an academic researcherβs inbox is rarely a communication channelβit is a distributed queue of unformatted, unprioritized obligations.
Tonight on our server TanriZarAtmaz, we finalized the production deployment of make-me-work: an autonomous, zero-data-retention email ingestion engine designed to continuously triage the BoΔaziΓ§i University mail stream (imap.bogazici.edu.tr), extract actionable deadlines, sanitize sensitive PII, synchronize tasks into an Obsidian vault (ejdertasimsi), and push real-time task cards directly to Telegram via a persistent Hermes agent.
Here is how the system is engineered, why Google Cloud Vertex AI ADC solved our enterprise privacy constraints, and how the entire pipeline runs at full throttle for 16 cents a day.
1. The Threat Model: Academic Privacy & Zero Data Retention
When dealing with university mail streams, naive LLM integrations are compliance disasters. Emails routinely contain Turkish Citizen ID numbers (TCKN), student numbers, mobile phone numbers, exam keys, and faculty bank account IBANs. Feeding this stream into commercial consumer AI endpoints (like Google AI Studioβs free tier) violates both institutional policy and KVKK / GDPR data governance regulations.
To ensure strict zero-data-retention (ZDR) guarantees:
- Local Deterministic PII Sanitizer: Before any raw RFC 822 email body or subject reaches an LLM prompt, it passes through
make_me_work.extraction.sanitizer. A compiled regular-expression engine redacts:- TCKN (11-digit Turkish identification numbers passing Modulo-10 checksum validation) ->
[TCKN_REDACTED] - BoΔaziΓ§i Student IDs (8 to 10-digit matriculation codes) ->
[STUDENT_ID_REDACTED] - GSM & E.164 phone numbers ->
[PHONE_REDACTED] - Turkish IBAN identifiers ->
[IBAN_REDACTED]
- TCKN (11-digit Turkish identification numbers passing Modulo-10 checksum validation) ->
- Enterprise Contract Isolation: We cut off public consumer endpoints and bound our model ingress strictly to Google Cloud Vertex AI under enterprise CDPA (Customer Data Processing Addendum). Customer inputs on Vertex AI are never logged for model training, never persisted across request boundaries, and governed by Google Cloud Enterprise VPC boundaries.
2. The Auth Topology: ADC vs. Dedicated API Keys
One of the most nuanced engineering lessons of this deployment was mapping the authentication topology across our autonomous agent ecosystem.
ββββββββββββββββββββββββββββββββββββββββββ
β TanriZarAtmaz Server Environment β
ββββββββββββββββββββ¬ββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββ
βΌ βΌ
βββββββββββββββββββββββββββ βββββββββββββββββββββββββββ
β Hermes Agent Gateway β β make-me-work Daemon β
β (systemd service) β β (systemd user service)β
ββββββββββββββ¬βββββββββββββ ββββββββββββββ¬βββββββββββββ
β β
google.auth.default() X-Goog-Api-Key: AQ.Ab8RN6I...
~/.config/gcloud/adc.json (Resolved from ~/.secret.keys)
β β
βΌ βΌ
βββββββββββββββββββββββββββ βββββββββββββββββββββββββββ
β Project: all-api-506817 β β Project: 186704731674 β
β Endpoint: global β β Endpoint: us-central1 β
βββββββββββββββββββββββββββ βββββββββββββββββββββββββββ
Rather than sharing a single credential across unrelated processes, we isolated the failure domains:
- Hermes Gateway: Relies on Application Default Credentials (ADC) via
google.auth.default(). It automatically refreshes short-lived OAuth2 bearer tokens minted from~/.config/gcloud/application_default_credentials.jsonagainst projectall-api-506817. - make-me-work Daemon: Relies on a dedicated
yusufakcakayaAPI Key (AQ.Ab8RN6I...) passed via the HTTPX-Goog-Api-Keyheader targeting Vertex AI project186704731674. - Pi Harness: Retains switchable profiles for both in its
auth.json, enabling seamless runtime toggling between ADC and API-key quotas on demand.
3. Fast-Path Pre-Filtering: Cutting Token Burn by 20%
A critical architectural invariant in autonomous agent design is avoiding LLM calls wherever deterministic logic suffices.
Every incoming email is first evaluated against a fast-path compiled regex pre-filter (rules.py). Broadcast senders (duyuru-noreply@bogazici.edu.tr, etkinlik-noreply@, bounhaber@, postmaster@) and automated subject signatures (Spammail report:, HaftalΔ±k Yemek Listesi, Seminer Duyurusu) are categorized as campus noise and marked skipped_noise immediately:
# Evaluated in < 0.1ms at zero token cost:
def pre_filter_email(email_data: dict[str, Any]) -> PreFilterResult:
sender = (email_data.get("sender") or "").strip()
subject = (email_data.get("subject") or "").strip()
for pat in _NOISE_SENDER_PATTERNS:
if pat.search(sender):
return PreFilterResult(is_noise=True, reason="Matched broadcast sender")
for pat in _NOISE_SUBJECT_PATTERNS:
if pat.search(subject):
return PreFilterResult(is_noise=True, reason="Matched broadcast subject")
return PreFilterResult(is_noise=False)
Across our local 488-email historical mirror in SQLite (data/state.db), this single check instantly filtered 99 emails (20.3%) at $0.00 and 0 tokens, preserving our LLM budget strictly for human-authored academic correspondence.
4. The 25,000-Token Recovery: Anatomy of a βStuck in Typingβ Turn
During deployment, we restarted the hermes-gateway.service while a turn was actively in flight. Immediately upon recovery, Telegram showed Hermes persistently βtypingβ¦β without delivering a message for over a minute.
A lesser debugging session would have assumed a deadlock and force-killed the process. Instead, we traced the socket telemetry with ss -tie:
ESTAB 0 0 [2a01:4f8:1c1c:5cf2::1]:39798 [2a00:1450:4001:c21::5f]:443
cubic cwnd:89 bytes_sent:112232 bytes_received:11240 lastrcv:56ms
The socket wasnβt hungβit was drinking from a firehose.
When the gateway restarted, Hermes restored the interrupted session state from sessions.json, injected a recovery turn, and transmitted the full conversation context back to Vertex AI: 25,577 prompt tokens.
2026-09-15 05:08:45 INFO agent.turn_context: conversation turn: session=20260915_044335_270fbee5
2026-09-15 05:09:52 INFO agent.conversation_loop: API call #1: model=google/gemini-3.8-flash
in=25577 out=816 total=26393 latency=66.5s cache=21460/25577 (84%)
2026-09-15 05:09:52 INFO gateway.run: response ready: time=105.1s response=3719 chars
Because the context was 25.5k tokens, Vertex AI required 66.5 seconds to compute the prompt and generate an 816-token response (which rendered 3,719 characters of formatted output). Throughout that interval, the Telegram adapter sent sendChatAction(action="typing") every 4.5 seconds to prevent client timeout. Notice the 84% prompt cache hit (cache=21460/25577): Vertex AIβs context caching dramatically reduced the compute cost of the turn.
5. The Economics: 100 Emails a Day for 16 Cents
What does it actually cost to operate an autonomous, always-on academic email ingestion pipeline in production with Gemini 3.8 Flash?
Live Google Vertex AI Pricing (Introductory Rate through Dec 31, 2026):
- Input (Prompt): $0.75 / 1,000,000 tokens ($0.00075 / 1k)
- Output (Completion): $3.75 / 1,000,000 tokens ($0.00375 / 1k)
Daily Pipeline Metrics (100 Incoming Emails/Day):
- Fast-path noise filter: 20 emails rejected at $0.00 (0 tokens)
- LLM extraction calls: 80 emails processed
- Average Prompt Tokens: ~1,500 tokens (sanitized headers + body snippet + JSON schema)
- Average Output Tokens: ~250 tokens (structured JSON tasks and executive summary)
$$\text{Daily Input Cost} = 80 \times 1,500 \text{ tokens} \times \frac{$0.75}{1,000,000} = $0.090$$
$$\text{Daily Output Cost} = 80 \times 250 \text{ tokens} \times \frac{$3.75}{1,000,000} = $0.075$$
$$\text{Total Daily Operating Cost} = $0.090 + $0.075 = \mathbf{$0.165 / \text{day}}$$
$$\text{Monthly Operating Cost (30 Days)} = 30 \times $0.165 = \mathbf{$4.95 / \text{month}}$$
For less than the price of a single campus cafeteria lunch, the system runs 24 hours a day, filtering noise, extracting administrative action items, categorizing department vs. doctoral duties, and syncing deadlines into Obsidian.
6. Closing the Loop: Real-Time IDLE & Telegram Cards
Rather than relying on noisy periodic cron polling, make-me-work employs an IMAP IDLE push listener (BounIdleListener).
The connection sits parked in a low-power socket wait on imap.bogazici.edu.tr:993. When a new email arrives, the server emits an untagged * <N> EXISTS event. The daemon immediately drops out of IDLE, fetches the raw message, parses the MIME structure into SQLite, runs the pre-filter and Gemini Flash extraction cascade, updates server flags (\Flagged, todo), injects markdown tasks into the appropriate Obsidian vault file (MISWebMaster.md, BUVES.md, or MIS612-Statistics-2026.md), and fires an HTML-formatted task card directly to Hermes in Telegram:
β‘ <b>MIS WebMaster Task Extracted</b>
<b>From:</b> BΓΆlΓΌm BaΕkanlΔ±ΔΔ± <mis@boun.edu.tr>
<b>Subject:</b> Bahar 2027 Ders DaΔΔ±lΔ±mΔ± Web GΓΌncellemesi
<b>Target:</b> <code>life/university/RND/MISWebMaster.md</code>
β’ <b>Action:</b> Δ°lgili ΓΆΔretim ΓΌyelerinin profil sayfalarΔ±nΔ± gΓΌncelle
β’ <b>Priority:</b> β‘ High Priority
β’ <b>Due Date:</b> 2026-09-22
<i>Reply to this message in Telegram to instruct Hermes Agent.</i>
When you reply in Telegram, Hermes picks up the context, executes the shell or web task, and closes the loop.
Autonomous agents do not need to be multimillion-token black holes that burn hundreds of dollars on massive reasoning models. By coupling disciplined Unix daemon architecture, local deterministic sanitization, IMAP IDLE push sockets, and Gemini 3.8 Flashβs blistering speed, you can build an enterprise-grade administrative cockpit that runs continuously, silently, and securely for five dollars a month.