Skip to content

How it works ​

katha is two small processes over two databases.

The two processes ​

  • katha serve — a small HTTP server that handles the links in the email (send / settings / unsubscribe) and a /health endpoint. Long-running.
  • katha run-due — the scheduler. Run it on a timer (a daily cron is typical). It finds every reader who is due an email given their chosen frequency, builds their picks, renders a localized email, and sends it.

The two databases ​

  • Kavita's database — opened read-only. This is where katha reads your books, your enrolled readers, and each reader's Kavita locale. It is never written to.
  • katha's own database — a small SQLite file katha owns. It records what each reader has been sent, what has been suggested, and their preferences.

How a pick is chosen ​

katha uses a widen-then-recycle strategy. For each reader it prefers books they've never been suggested, in order of affinity:

  1. Same author — an author the reader has already sent to their Kindle.
  2. Adjacent author — an author related to one they enjoy: first any the operator names in adjacency.toml's [map], then those katha computes from the genres the two share.
  3. Rest of the library — the cold-start path for a reader with no history yet.

Only when fresh books run out does it recycle older suggestions, least-recently-suggested first. The picker is a pure, deterministic function — the same inputs always produce the same picks, which makes it easy to test and reason about.

katha never stores passwords or issues sessions. Every actionable link in the email is a compact, HMAC-signed, time-limited token that encodes who the reader is, what the action is, and when it expires. When the reader taps it, katha verifies the signature (in constant time) and expiry before doing anything. The signing key is KATHA_HMAC_SECRET — rotating it invalidates every outstanding link.

Four link types:

LinkAction
SendDeliver this book's epub to the reader's Kindle address.
Send me one nowBuild and email a fresh pick immediately, independent of the schedule (rate-limited, and a confirmation step keeps mail scanners from triggering it).
SettingsOpen the reader's self-service page — frequency, book count, language, pause.
UnsubscribePause the reader's emails — or resume them if they are already paused (a confirmation step keeps mail scanners from triggering it).

Localization ​

Emails render in French or English. A reader's language is resolved as: their explicit choice on the settings page → (optionally) their Kavita locale, if you enable KATHA_LANGUAGE_FROM_KAVITA → the instance default KATHA_DEFAULT_LANGUAGE. See Configuration.

Signing in on the site ​

The links in the email stay the fastest path, but the site itself is signed-in too: the front page emails a one-time sign-in link (/auth/…) to any address enrolled in Kavita. Opening it shows a confirmation button — that interstitial is what makes the link safe against mail gateways that prefetch every URL — and tapping it establishes a server-side session carried by a __Host--prefixed, HttpOnly, SameSite=Lax cookie. Only a hash of the session secret is stored, sessions expire on an absolute schedule (KATHA_SESSION_TTL_DAYS), and signing out revokes the session server-side — a stolen cookie dies with it.

Signed in, the reader gets their pick as the front page, can send it to their Kindle, answer pas pour moi / déjà lu (which immediately surfaces the next idea), tune their taste per author and per category, and choose which collections their picks come from. The operator's account additionally gets the announcement composer.

Released under the MIT License.