{"slug": "i-just-wanted-a-whatsapp-channel-that-teaches-me-one-german-word-a-day-it-took-a", "title": "I Just Wanted a WhatsApp Channel That Teaches Me One German Word a Day. It Took a Week and a Git History Rewrite.", "summary": "A developer spent a week building a WhatsApp Channel that posts one German word daily, encountering deployment hurdles with session management and hosting platform restrictions. The project ultimately switched to the Baileys library, which uses WhatsApp's multi-device protocol over WebSocket, reducing session size and enabling a GitHub Actions cron job for free hosting. The developer also had to rewrite git history to remove accidentally committed session credentials.", "body_md": "I wanted something dumb simple - a WhatsApp Channel that posts one German word every morning, A1 level, with an example sentence and a mnemonic. Gemini can write that in a second. The actual project turned out to be an argument with WhatsApp's session model, two hosting platforms, and, at one point, my own repo's git history.\n\n`whatsapp-web.js`\n\nis the library most people reach for first. It drives a real headless Chrome instance that logs into web.whatsapp.com like your browser would, and it works - locally. The moment you try to deploy it, you hit the same wall everyone hits: the session it needs to stay logged in is a full Chromium profile. Cookies, IndexedDB, cache, the works. Mine was 43MB.\n\nGitHub Secrets cap out in the kilobytes. So the \"obvious\" fix - the one a dozen tutorials suggest - is: tar it, base64-encode it, shove it into a secret anyway, decode it back into place on every CI run. I actually did this. I had a script called `setup.ts`\n\nwhose entire job was printing a giant base64 blob for me to paste into GitHub's secret editor by hand.\n\nIt's exactly as bad as it sounds, and it doesn't even solve the real problem, because that session expires. Not on a schedule you control — whenever WhatsApp feels like it. Phone offline too long, and it's gone. Which means eventually you're back to scanning a QR code, except now you're doing it on a server that has no screen.\n\nThis is the question I actually came in asking, because I genuinely didn't know. Turns out there are two real answers:\n\nNeither needs a screen physically attached to the server. I'd been overcomplicating this in my head for no reason.\n\nI had this deployed in three places at once - Railway, Render, GitHub — and none of them worked, and I couldn't figure out why. Turns out Railway's fair-use policy explicitly bans \"userbots\": anything that logs into a personal account via an unofficial, reverse-engineered protocol instead of a real bot API. `whatsapp-web.js`\n\nis precisely that. It's not a bug I could fix. It's a platform I had to stop using for this.\n\nRender fared a little better, but I kept hitting a different, more annoying issue: `RemoteAuth`\n\nwith a Mongo-backed session store has open, unresolved GitHub issues where the session saves fine but doesn't reliably restore — silently forcing a fresh QR scan on restart. Not something I broke. Just a library with rough edges.\n\nThe fix that actually stuck was switching libraries entirely, to [Baileys](https://github.com/WhiskeySockets/Baileys). It speaks WhatsApp's multi-device protocol directly over a WebSocket. No Chromium, no Puppeteer, nothing to render. The session is a handful of small JSON credential files — kilobytes, not tens of megabytes.\n\nThat one change collapsed two separate problems at once. The session fits anywhere now — I put it in a free MongoDB Atlas cluster instead of a secret. And without a browser to spin up, the whole thing runs fine on a constrained free-tier box instead of choking on headless-Chrome memory pressure.\n\nIt also made the hosting question moot. My actual requirement was \"post one message a day,\" which doesn't need an always-on server at all — it needs about sixty seconds of compute, once every 24 hours. That's a GitHub Actions cron job, for free. I deleted the Dockerfile, deleted Render, deleted Railway, and the whole deployment surface became one YAML file with a `cron:`\n\nline in it.\n\nSomewhere in the earlier, messier phase, I had committed a full `.wwebjs_auth`\n\nsession folder straight into git — 204 files - plus a couple of standalone tar/base64 dumps of the same thing, at 27MB and 41MB. That's not hypothetical risk. That folder *is* an active login session. Anyone who could read that repo could have hijacked my WhatsApp account with it.\n\nDeleting the files from the working tree isn't enough — they're still sitting in every earlier commit, recoverable by anyone who clones the repo. Fixing it for real meant `git filter-repo`\n\nto strip those paths out of the *entire* history, followed by a force-push. My `.git`\n\nfolder went from 84MB to 284KB in about a second. If you've ever committed something you regret, this is the actual fix — not `git rm`\n\n, the history itself has to go.\n\nI only caught this because I asked for a full audit before making the repo public. Worth doing that even if you're sure you didn't leak anything. I was sure. I was wrong.\n\nGoogle deprecates old models and its numbers so fast the issue isn't \"use the newest model,\" it's \"never hardcode one.\" I wrote a small fallback chain — try the latest Flash model, and on a 404, 429, or 503, fall through to the next one down the list automatically. I know it works because I watched it happen live: the newest model 3.7 hit a genuine `503 Service Unavailable`\n\nmid-run, fell through to the one behind it, and posted anyway without me touching anything.\n\nOne GitHub Actions workflow, triggered on a cron schedule. It pulls a WhatsApp session out of a free MongoDB cluster, asks Gemini for a word it hasn't used yet, posts it to the channel, and writes the updated state back to Mongo. No server. No Docker image. No monthly bill. The entire compute cost is about a minute of free CI time a day.\n\nThe message itself ended up plainer than I expected too — I started with a version stuffed with emoji and flag icons and a line advertising DM commands, and once I actually deleted the interactive bot half of the project, that line was just false advertising to a channel with nobody listening on the other end. It reads like a dictionary entry now. Word, pronunciation, meaning, an example sentence, a note. That turned out to be the right call anyway — it's a vocabulary channel, not a chat app.\n\nIf you're building anything that automates a personal WhatsApp account: expect the session/auth layer to be the actual project, and the thing you originally wanted to build to be the easy 10%.\n\nCheckout the [WhatsApp channel](https://whatsapp.com/channel/0029VbDR5d3LNSaB3gXAaI3V) and my [Github repo](https://github.com/btkcodedev/whatsapp-german)\n\nThanks for reading!", "url": "https://wpnews.pro/news/i-just-wanted-a-whatsapp-channel-that-teaches-me-one-german-word-a-day-it-took-a", "canonical_source": "https://dev.to/btkcodedev/i-just-wanted-a-whatsapp-channel-that-teaches-me-one-german-word-a-day-it-took-a-week-and-a-git-3a5k", "published_at": "2026-08-16 04:42:09+00:00", "updated_at": "2026-08-16 05:11:31.431242+00:00", "lang": "en", "topics": ["developer-tools", "ai-products"], "entities": ["WhatsApp", "Gemini", "Baileys", "GitHub", "Railway", "Render", "MongoDB Atlas", "whatsapp-web.js"], "alternates": {"html": "https://wpnews.pro/news/i-just-wanted-a-whatsapp-channel-that-teaches-me-one-german-word-a-day-it-took-a", "markdown": "https://wpnews.pro/news/i-just-wanted-a-whatsapp-channel-that-teaches-me-one-german-word-a-day-it-took-a.md", "text": "https://wpnews.pro/news/i-just-wanted-a-whatsapp-channel-that-teaches-me-one-german-word-a-day-it-took-a.txt", "jsonld": "https://wpnews.pro/news/i-just-wanted-a-whatsapp-channel-that-teaches-me-one-german-word-a-day-it-took-a.jsonld"}}