Background

Over the past few weeks I ran into a familiar problem: my personal projects started to feel like work.

Family, work, and then spending evenings on side projects slowly drained my motivation.

So I asked myself: what is actually bothering me, and where does it make sense to invest personal energy? The answer came quickly: my homelab. Not because I didn’t have one, but because I had been neglecting it. A few Docker containers were running on the Raspberry Pi that I had tried out and then forgotten about. The NAS, which was supposed to handle backups, never really worked as planned. And my self-hosted Git instance was only reachable within my home network, which made it almost useless for day-to-day work.

The idea was clear: build a simple setup that automates a lot, is as open source as possible, and takes privacy seriously.

The Stack

The goal was a small, practical stack that solves real problems rather than hosting everything under the sun.

As a foundation I rented a VPS from a German provider for a few euros a month. Everything runs behind Caddy as a reverse proxy, which also handles HTTPS automatically without any configuration on my part.

The first thing I set up was Forgejo as a Git server, reachable via its own subdomain. That sounds small, but for me it made a real difference: I can now work on private projects from anywhere, whether on a train using a hotspot or somewhere else. At the same time I moved my website from GitHub Pages to the new repo and set up a deploy workflow so that the site gets automatically built and deployed on every push. That actually turned out to be smoother than using GitHub Pages.

After that came CommaFeed as an RSS reader and Readeck as a read-it-later app. Both were set up quickly, and the value was immediately noticeable: I can now read anywhere, save articles, and add notes directly.

Up to this point: solid, but unremarkable self-hosting. The interesting part came after.

The Problem with Notes

I read a lot. And like most developers, I have a familiar problem: I save articles, read them, take notes, and then they disappear into the void. The highlights collect dust, the insights fade. I do use the Zettelkasten method for this. But sometimes I simply lacked the focus and time to turn my notes into actual Zettelkasten entries.

While thinking about which other containers I wanted to host, I remembered n8n. I had watched several YouTube videos about what n8n can do.

So I hosted that too and started experimenting. My goal was clear: everything I read and find valuable should flow automatically into my Zettelkasten. Not as a bland summary, but as a thoughtful note that reflects my own thinking and highlights.

The Idea

My setup consists of three components connected through n8n:

  • Readeck: where I save, read, and highlight articles
  • Claude (via the Anthropic API): which generates a Zettelkasten note from my highlights
  • Forgejo: where my Zettelkasten lives as Markdown files

The key idea is that the workflow never sends the entire article to the language model. Instead it only sends my highlights, notes, and a short summary. This keeps it privacy-friendly, saves tokens, and most importantly the generated note actually reflects my understanding rather than a generic summary.

The Reading Process

Before the workflow can do its thing, I need to do my part. My reading process in Readeck looks like this: I read an article and mark the most important passages as highlights. For each highlight I write a short note with my thoughts. At the end I write an overall summary on one of the highlights, prefixed with “Zusammenfassung:”. Finally I assign the label Done, which signals to the workflow: this article is ready.

This convention is essential:

  • the Done label triggers the workflow
  • the Zusammenfassung: prefix tells the LLM what the central idea is

The Readeck API

Readeck offers a REST API and since version 0.22 there are also annotations (highlights with notes) and a Markdown export endpoint that is worth its weight in gold for this workflow.

The endpoint GET /api/bookmarks?labels=Done returns all articles I have marked for processing. For each bookmark I then fetch the content as Markdown via GET /api/bookmarks/{id}/article.md. The nice thing about this: the endpoint delivers not just the article text, but also the highlights as ==highlighted text== and my notes as footnotes [^1], all in a clean Markdown document with YAML frontmatter.

The n8n Workflow

The workflow consists of a loop that processes each article individually. The full chain looks like this:

A Schedule Trigger starts the workflow every night at 2am. An HTTP Request then fetches all bookmarks with the label Done from Readeck. A Loop Over Items node iterates over each bookmark. Inside the loop another HTTP Request fetches the article as Markdown. A Code Node parses the Markdown: it extracts the frontmatter, separates the highlights from the regular text, looks for my Zusammenfassung:, and assembles the input for the language model. A Basic LLM Chain sends everything to the Anthropic API and gets a Zettelkasten note back. A second Code Node encodes the LLM response as Base64 for the Forgejo API and generates a slugified filename. Three HTTP Requests against the Forgejo API create a feature branch, commit the file, and open a pull request. A final HTTP Request updates the label in Readeck from Done to processed.

n8n_workflow

Parsing: Extracting Highlights

The core is the Code Node that takes the Markdown article apart. It extracts the highlights (everything between == characters), maps them to the footnote notes, and specifically looks for my summary.

// Extract highlights with footnote number
const highlightRegex = /==([^=]+)==(?:\[?\^(\d+)\]?)?/g;

// Extract footnotes
const footnoteRegex = /\[\^(\d+)\]:\s*(.*?)$/gm;

// Find summary
for (const h of highlightPairs) {
  const note = h.footnoteNum ? (footnoteMap[h.footnoteNum] || '') : '';
  if (note.toLowerCase().startsWith('zusammenfassung:')) {
    zusammenfassung = note.replace(/^[Zz]usammenfassung:\s*/i, '');
  }
}

The output then contains only the frontmatter, my summary, and the individual highlights with notes, no full article text.

The Prompt

The prompt is designed to generate an atomic Zettelkasten note that reflects my understanding. It took a few iterations before the results felt right.

Key aspects: the LLM should use my summary as a guide and weight it more heavily than individual highlights. The note should distill one central idea, not retell the article. Tags should be thematic and connections to other areas of knowledge should be written as running text. The output format is a fixed Markdown template with YAML frontmatter that fits directly into my Zettelkasten.

A typical note generated by the workflow looks like this:

---
title: "Deliberate Practice Is the Bottleneck of Learning Software Development"
date: "2025-11-12"
tags: [learning, deliberate-practice, software-craftsmanship]
source: "https://example.com/deliberate-practice-programming"
author: "Jane Doe"
type: literature
---

## Core Idea
Improvement in software engineering does not primarily come from accumulated experience but from deliberate practice with feedback. Writing code every day is not the same as intentionally improving one’s skills.

## Note
The central idea of the article is that many developers spend most of their time performing rather than practicing. Day-to-day work usually focuses on delivering features or fixing bugs, not on deliberately improving specific programming skills.

Deliberate practice requires isolating a skill and working on it intentionally. In software engineering this could mean refactoring a small piece of code multiple times, implementing a known algorithm from memory, or experimenting with different design approaches for the same problem.

Another important aspect is feedback. Without feedback it is difficult to know whether a mental model is actually improving. Code reviews, pair programming, and discussions about architecture decisions can provide exactly this kind of feedback loop.

This aligns with my own experience. Many side projects feel productive but often do not improve specific skills. Short, focused exercises such as refactoring a function or re-implementing a concept from scratch tend to lead to deeper learning.

## Highlights
> Expertise is not the result of experience alone, but of structured and deliberate practice.

*Experience alone does not automatically improve programming skills. Improvement requires consciously focusing on specific aspects of the craft.*

> Most professionals spend their time performing, not practicing.

*In daily work the focus is on delivering results rather than improving individual skills.*

> Feedback is the mechanism that turns repetition into learning.

*Practices like code reviews or pair programming create the feedback loops that deliberate practice depends on.*

## Connections
The idea of deliberate practice originates from learning psychology and is widely applied in areas such as music or sports. In software engineering it aligns closely with ideas from the software craftsmanship movement, which emphasizes continuous improvement of programming skills. It also connects to practices such as coding katas and focused refactoring sessions, where the primary goal is learning rather than delivering features.

The Forgejo PR Flow

Instead of committing directly to the main branch, the workflow creates a feature branch and a pull request. This gives me the chance to review and adjust the generated note before merging, in the morning over coffee, without rushing.

The three Forgejo API calls are straightforward: POST /api/v1/repos/{owner}/{repo}/branches creates a branch like zettelkasten/2026-03-15-atomic-habits. Then POST /api/v1/repos/{owner}/{repo}/contents/{path} commits the Markdown file to that branch. Finally POST /api/v1/repos/{owner}/{repo}/pulls opens the pull request.

Cost and Performance

The Anthropic API is billed through prepaid credits. Running one article through Claude Sonnet costs a few cents, since I only send the highlights and metadata rather than the full article text. A budget of 5 euros per month is more than enough.

Stumbling Block

n8n is marketed as a no-code automation platform. But here comes the catch. It works great with mainstream tools like GitHub, which can be connected quickly and easily.

For tools like Forgejo and Readeck you basically have to model the REST calls by hand, exactly as in my workflow. You can import curl commands, but still need to adjust a few things. That raised a question for me: would a script not have been a better solution here? Because apart from the Anthropic API call, I essentially modelled the REST calls directly in n8n.

That said, I will keep using n8n because I am still just getting started with it. There might be better approaches I have not discovered yet. And it gives me a lot of flexibility without having to wrestle with bash syntax.

Conclusion

The entire workflow took one afternoon, from the first Readeck API call to the finished pull request in Forgejo. Most of the time actually went into refining the prompt and parsing the highlights correctly.

The result is a system that connects my reading process seamlessly to my Zettelkasten, without manually transferring notes.

What I like most: the workflow forces me to read deliberately. I have to set highlights, write notes, and compose a summary before an article gets processed. The language model amplifies my thinking, it does not replace it.

The complete stack is self-hosted: Readeck, n8n, and Forgejo run on my own infrastructure. Only the LLM call goes to the Anthropic API. In the future I could imagine replacing that with a local model too, but the quality of Claude Sonnet for this task is hard to beat right now.

And the original motivation problem? Gone. When a project actually improves something you deal with every day, it becomes fun again.