mangel14.dev & Content Engine
Publishing steadily about what I build without a model making up facts in my name, on a portfolio that search engines and AI assistants can read and cite.
What it does: Turns the notes, documents and links I send it on Telegram into blog posts and LinkedIn posts. No number goes out without its source, and nothing is published without my confirmation.
Overview
The site you're reading: Next.js 16 with static generation, in Spanish and English, with structured data, llms.txt and a sitemap with per-language alternates.
The Content Engine, a LangGraph.js graph, runs in the same Vercel deployment. It takes what I send on Telegram, proposes an angle, drafts the post and the LinkedIn version, checks them against a style linter and waits for my approval of an exact version before publishing.
The need
I wanted to publish steadily about what I build, but a language model drafting on its own makes up numbers, experiences and sources, and whatever is published carries my name. The portfolio also had to be readable and citable by search engines and AI assistants, not only by people.
Constraints
- No separate servers and no fixed cost: Vercel's Hobby plan, with 300-second functions at most and one cron run a day.
- Whatever arrives on Telegram (documents, pages, forwarded messages) can carry hidden instructions: it is untrusted material.
- LinkedIn has no idempotency key, and its text format silently truncates a post when reserved characters are not escaped.
The solution
An agent that works like an editor: it extracts what I send, marks which numbers have a source and which don't, asks me for what's missing, drafts in both languages with a cover and diagrams, and publishes only the version I approve, first on the blog and then on LinkedIn.
How it works
The site is static (SSG) with Next.js 16 and next-intl. The agent runs in the API routes of the same Vercel deployment, with its state in Postgres; there are no separate servers.
- Telegram →
/api/telegramwebhook: checks the secret, accepts only the author's private chat and drops repeated updates - Extraction: notes, Markdown, PDF (unpdf), images and links; each source is marked as the author's or external, and URLs go through a fetch that blocks private networks
- Research: claims with the source that backs them; an external number without support is flagged
- Proposal in Telegram: topics, article type, missing facts and what to do with unsourced numbers; the author answers in plain language
- Drafting, editing against the linter, English version, cover and SVG diagrams; every draft gets a version and a hash
- Publishing confirmed for that exact version: MDX commit to GitHub → Vercel deploy → check of the published page → LinkedIn Posts API → IndexNow
Technical decisions
- Nothing is published without the author's confirmation, and a confirmation applies to one exact version: a button from an older version won't publish the edited one.
- The blog and LinkedIn are approved separately; if one fails, the piece is marked “partially published” and the bot says what's missing instead of announcing a success that didn't happen.
- Every document, page or forwarded message is treated as quoted data, never as an instruction for the agent.
- Images are generated with code (SVG and next/og) in the site's visual identity; if a text doesn't fit, the agent says so instead of cropping it.
- The agent lives inside the Vercel deployment with state in Postgres, instead of a separate server running n8n: fewer moving parts and no fixed cost.
How it's validated
pnpm test:node:testtests for the full graph with a mocked LLM, the Telegram conversation, security (SSRF, MDX, files, secrets), the visuals and content integrity in both languages.pnpm test:e2e: Playwright walks the main routes at seven screen widths and checks horizontal overflow, the mobile menu, the language switch and the 404.
Limits & next steps
- The agent's variables are configured in production (the webhook rejects requests without the secret instead of answering “not configured”), but it hasn't published real content yet.
- The first blog post is still a draft.
- LinkedIn publishing is implemented with the Posts API but hasn't been tested against the real account; LinkedIn has no idempotency key, so an attempt cut off midway asks for confirmation before repeating.
My part
Personal project: design, code, content and deployment.
Stack
Interested in this kind of work?
I'm looking for remote work as a Full Stack Engineer. If you'd like to talk about this project or your team, get in touch.