Blog Updates #1

It’s been a couple of weeks since the last post and the blog has changed quite a bit since then. None of it was on my roadmap. A Reddit comment kicked the whole thing off, and from there it kind of snowballed into a full legal compliance sprint plus a handful of smaller fixes I’d been putting off anyway.
Here’s the rundown.
Privacy Policy
After I posted my Umami v3.1.0 review, a comment on r/selfhosted made me aware that a privacy policy is not optional, even though I had claimed otherwise in the post. So I started researching what I needed to do to be compliant with EU and German privacy law.
First reaction was anger. At the bureaucracy and at the fact that I had to do this at all.
I get why these rules exist. Transparent use of user data, preventing abuse, all that. But this is a small non-commercial personal tech blog. What the hell do I need to put my private address on here for.
So I contemplated quitting the blog. Or moving it to the dark web. Then I read about the “blog address” services some companies offer. You pay a small monthly amount, and you get to use their address in your imprint and privacy policy.
The only requirement under German law is that the address has to be “ladungsfähig”. A court has to be able to reach you. That works as long as the imprint service forwards the mail, either physically or by scanning and emailing it to you.
Sounds great. I keep writing my blog without doxxing myself, for 4 euros a month. So that’s what I did. I went with Online-Impressum.de. Best bang for the buck in my research.
Writing the Policy
Now the actually hard part. The address was the easy buy.
Websites and generators offer boilerplates, but none of them fit my setup. The usual best practice is to not use legal text from AI, but I thought to myself, this is such a lightweight setup, how hard can it be.
So I fired up my favorite LLMs.
First I did some research on my own. Then I had Claude do deep research on what needed to be in the privacy policy and how to structure it. Built a first draft, refined it back and forth maybe fifty times. Dropped the final version into all the common AIs to rate it. They all said it looked solid.
But LLMs are notorious liars. So I went and read the privacy policies of other small blogs and compared section by section. And indeed, mine was actually good. Structure right, legal references right, disclosures matched what my site actually does.
The key is being transparent and making sure all legal requirements are covered. For a small non-commercial blog focused on privacy, the risk of getting sued over this is very small.
The final version lives on the privacy policy page. Long, dense, boring. Which is exactly what a privacy policy should be.
Imprint and Contact Form
With the privacy policy comes the imprint. Easy. Name, address, email. But the law requires two contact options. One is email. For most people the second would be a phone number. I don’t have a spare phone number, so I went with a contact form on the page itself.
And here I ran into the staticness of Hugo. A contact form needs some kind of backend to handle the input. Options ranged from developing my own solution (not difficult but I wasn’t in the mood) to some kind of SaaS. Yikes.
After some digging I found hugo-mx-gateway. Small service that takes the form POST and emails it to me. Drop it into docker-compose, point Traefik at it, fill in the SMTP credentials in an env file. Done. I love it when something just works.
hmmr-contact:
image: rchakode/hugo-mx-gateway:1.0.0
container_name: hmmr-contact
restart: unless-stopped
env_file:
- ./contact.env
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.hmmr-contact.rule=Host(`hmmr.online`) && PathPrefix(`/api/contact`)"
- "traefik.http.routers.hmmr-contact.entrypoints=websecure"
- "traefik.http.routers.hmmr-contact.tls=true"
- "traefik.http.routers.hmmr-contact.middlewares=hmmr-contact-strip"
- "traefik.http.middlewares.hmmr-contact-strip.stripprefix.prefixes=/api/contact"
- "traefik.http.services.hmmr-contact.loadbalancer.server.port=8080"
Added a simple HTML5 form that POSTs to /api/contact/sendmail. Name, email, subject, message, a honeypot field for bots, a privacy consent checkbox. A tiny bit of JS shows a spinner and a success message so the page doesn’t feel dead while the request goes out. Form is here if you want to see it.
Added a layout for the legal pages, dropped them into the footer menu, and now the blog is compliant under German law. Nice.
Other Updates
- Footer menu. The old one was way too big. Icons didn’t fit, spacing was sloppy, the whole row felt like an afterthought (because it was). Trimmed it down, removed the icons, and re-weighted the entries so the legal stuff sits at the end where it belongs instead of cluttering up the middle. Looks like a footer now, not a misplaced navbar.
- Sitemap. Hugo generates a
sitemap.xmlautomatically. Great for search engines. Useless for humans. So I added a proper one too. Plain page, all posts grouped by year, newest first, title and date. That’s it. If you want to scroll through what I’ve written without fighting the homepage pagination, that’s where you go. - Smooth scrolling. The share button at the top of each post used to jump straight to the share section at the bottom. Instant teleport. Felt broken every single time I tried it. Adding
scroll-behavior: smoothon thehtmlelement fixed it in literally one line of CSS. Now it glides. Why this isn’t the browser default in 2026 I genuinely do not understand.
TL;DR
Blog is now legal under German law. Privacy policy, imprint, working contact form. The address comes from a 4-euro-a-month Impressum service so I don’t have to publish my own. Privacy policy written with LLM help and cross-checked against other small blogs. Contact form runs on hugo-mx-gateway behind Traefik. Also redid the footer, added a human-readable sitemap, and made the share button smooth scroll instead of jumping.