Lugar Común — Build Log

Building a Telegram-to-git commonplace book on n8n, TrueNAS, and a small AWS Lambda — log, snags, open questions.

A Telegram-driven commonplace book pipeline, built across phases for an n8n job application portfolio piece.

The idea

Telegram bot → n8n (self-hosted, TrueNAS) → AI enrichment → atomic commit to the life repo → Vercel rebuild → entries appear at life.tinkerer.tools/commonplace/.

Photos and text only for the MVP. Videos deferred. The author of an entry can be Ignacio, Laura, Zoe, or Diego — the AI infers from message content, with an ask-back to Telegram on low confidence.

Phase 0 — Requirements

  • n8n deployment: TrueNAS catalog app.
  • Content destination: git-native. The workflow commits markdown files to the life repo and the site rebuilds.
  • Media storage: photos in repo (public/commonplace/media/{slug}/). Videos deferred to S3 if and when re-enabled.
  • Enrichment: AI infers who, on, and summary from message context. Spanish summaries, original language preserved in the body.
  • Slug collisions: time suffix — {who}-{YYYY-MM-DD}-{HHmm} in Atlantic/Canary tz.
  • Push auth: small Lambda (commit-service) wraps the CodeCommit CreateCommit API, called via API key from n8n. Avoids hand-rolling SigV4 in n8n.
  • Allowlist: Ignacio only (Telegram user id 908844549).
  • Photo-only messages: bot replies “caption?” and waits. The caption becomes ground truth, no vision call.
  • Privacy hard line: kid faces never go to any LLM, so vision is out — text-only enrichment.
  • Confidence handling: ask-back via Telegram inline buttons when AI is unsure of who or on.

Phase 1 — Plan

Architecture

Telegram ──► n8n (TrueNAS) ──► Anthropic API (text only)

                └──► Lambda: commit-service ──► CodeCommit (life)
                                              └──► Vercel rebuilds

Schema extension to life (commonplace collection)

z.object({
  who: z.enum(['ignacio', 'laura', 'zoe', 'diego']),  // added ignacio
  on: z.coerce.date(),
  summary: z.string().optional(),
  media: z.array(z.object({
    src: z.string(),
    alt: z.string().optional(),
  })).optional(),
})

Phase 2 — Execution log

Step 1 — Schema PR

Claude Code delivered a clean diff. astro check and npm run build both pass. The person color token for ignacio (life-ember) already existed in family.ts.

Step 2 — Commit-service Lambda

Project: commonplace-commit-service, separate repo from life. Stack deployed to eu-west-1. Node 22, arm64, 512 MB, 30 s timeout.

Endpoints:

  • GET /recent?limit=10 — last N entries from CodeCommit, frontmatter-parsed, sorted by on desc.
  • POST /commit — atomic multi-file commit (markdown + photos) via CreateCommitCommand, retry on ParentCommitIdOutdatedException.

IAM scoped to codecommit:GetFile, GetFolder, GetBranch, CreateCommit on the life repo only. No wildcards. Auth via API Gateway with an x-api-key header.

Live test: throwaway entry committed and read back successfully, then deleted.

Step 3 — n8n install on TrueNAS

Catalog app version 1.10.29. Stack includes Postgres + Redis + n8n. Initial install failed with postgres_upgrade exit 1 — Not writable path [/var/lib/postgresql]. Resolved with TrueNAS UI’s “automatic permissions” toggle, no manual chown needed. n8n up and reachable on the LAN.

Step 4 — Telegram bot

Created via BotFather. Token saved. Privacy mode disabled so the bot can see all DM text, not just commands.

Step 4.5 — Cloudflare Tunnel + n8n MCP

Subdomain n8n.tinkerer.tools already wired via existing Cloudflare Tunnel. Required env vars on the n8n app:

  • WEBHOOK_URL=https://n8n.tinkerer.tools/
  • N8N_EDITOR_BASE_URL=https://n8n.tinkerer.tools/

Without these, n8n advertises its internal NodePort (e.g. :30109) in webhook and MCP URLs, which the Cloudflare proxy drops — it only forwards 80/443.

n8n’s built-in instance-level MCP enabled, connected to Claude Code via:

claude mcp add --transport http n8n https://n8n.tinkerer.tools/mcp \
  --header "Authorization: Bearer <token>"

Step 5 — MVP workflow (in progress)

Three credentials staged in n8n: Telegram - Lugar Común, Anthropic, Commonplace Commit Service.

Workflow design (text-only MVP, no photos, ask-back, or recent-context yet):

  1. Telegram Trigger
  2. IF allowlist (from.id === 908844549)
  3. HTTP Request to Anthropic — strict JSON schema in the system prompt
  4. Code node — parse, build slug with HHmm time suffix
  5. HTTP Request to commit-service /commit
  6. Telegram Send Message — confirmation with live URL
  7. Error workflow → Telegram DM with node + error message

Snag — Vercel doesn’t watch CodeCommit

I had assumed Vercel had a native CodeCommit integration. It doesn’t. Commits land but nothing rebuilds.

Two paths considered:

  • A. Migrate life to GitHub. Vercel’s native integration replaces the deploy-hook plumbing, the commit-service gets simpler (GitHub Contents API instead of CodeCommit SDK + SigV4), tooling improves end to end. Recommended path.
  • B. Add a Vercel deploy hook call to the commit-service Lambda. Tactical fix that ships the MVP today and leaves CodeCommit debt for later.

Decision pending.

Open items

  • Resolve the Vercel rebuild trigger (path A or B above).
  • Vaultwarden has been down ~2 months on the same postgres_upgrade exit 1 pattern. Fix in the same session, then move secrets there.
  • Build photo branch.
  • Build ask-back (Telegram inline keyboard + wait node).
  • Wire recent-entries context into enrichment.
  • Final canvas polish for the recruiter screenshot.

Reference values

  • Telegram allowlist: user id 908844549
  • Commit-service API: https://1i8i4lnuz5.execute-api.eu-west-1.amazonaws.com/prod
  • Commit-service API key id: 65pk8m4vf4
  • Region: eu-west-1
  • CodeCommit repo: life (account 071372696451)
  • Public n8n: https://n8n.tinkerer.tools
  • Site: https://life.tinkerer.tools/commonplace/
  • Timezone: Atlantic/Canary