Building a CalDav server
There are good ideas, bad ideas and the ideas I get when sitting on a couch holding the paw of my dog for two weeks (while she is recovering from surgery). One thing I am really not a fan of is the current landscape of CalDav servers. So what better to do than read some RFCs while a coding agent bootstraps a “kind of not working but close enough” skeleton that I can make actually work in between treats, all the pets required for recovery and "who is the best girl?! YOU ARE THE BEST GIRL!".
“But… Why?”, you might rightfully ask. First, because I can. (Same answer as always.) Second, because I want to keep my calendar and appointments on infrastructure I control. And third, because the current landscape of self hosting a CalDav server does not look too hot, if you ask me. I cannot make it worse than it already is.
One feature I am missing with most existing solutions is sending invites via email to attendees of a meeting and seeing their responses. Something you get used to pretty quickly when most of your clients use Google or Outlook. Shared calendars are also not optional. We have one for some generic household organisation like which trash is picked up when (thanks to our service provider we get an ICS file to import for the whole year) and when we plan to be out with friends.
At the same time I do not need a web ui, admin ui or any of the other stuff some of the existing solutions ship. The most common ones you will see suggested are Radicale, Baikal, Davis and NextCloud. The first three are not too bad but are all lacking in some areas or are a bit too much. NextCloud is... I really do not like it. I have been a user for many years. It kind of works, sometimes. I will also not link the Docker socket into a container because shipping a compose file is considered "bad UX" or something (this discussion was a long time ago, I do not know if they revisited this decision).
So many RFCs
Let us ignore the RFCs defining how HTTP works and what the GET method is supposed to do. Thankfully we do not have to go that deep down the rabbit hole. But there are still enough RFCs left to read through.
RFC 4918 WebDav core, the underlying technology introducing the concept of collections and new HTTP verbs that need to be supported. PROPFIND, PROPPATCH and MKCOL. Beside that it also defines a new DEPTH header indicating how deep a directory query goes and a 207 multi status XML response. Because obviously it is XML.
RFC 3744 introduces access control called principals. So beside some mapping in a database like you would expect it also requires some more endpoints, URLs and obviously new XML attributes.
RFC 4791 the CalDav extension for WebDav introducing the concept of calendars (as resource) and a few more HTTP methods to support. MKCALENDAR to create new calendars and REPORT for querying and filtering calendar events. Also yet another mandatory XML element.
RFC 6638 to handle invites to events and book rooms if you need to. This comes with more URLs and XML elements, obviously.
RFC 5545 last but not least the standard defining how an ICS file is structured and what data goes in it. Because at the end of day all we do is build a stitched together standard to move .ics over a network.
Sounds fun, right?
From RFC to kind of working Go code
Apples XML parser is picky and their calendar apps seem really good at sticking to the standards. Or they just force you to implement things no one else does or aren’t part of the spec. I am not certain which one it is yet, but so far I believe a bit of both. But believe me you will implement them or nothing will work.
That said, I managed to get the server to a point where I can create an event on my Mac, attach a FaceTime call to the location and it synchronises fine to my iPad. So I would call this a first success.
You also want to deploy this behind a valid SSL endpoint, do not even try to work around this or clients will have to pretend to be okay with it if you press the "I know what I am doing, just talk to the server" button, but they really are not.
The app compiles to a single Go binary, uses SQLite for storage and has four CLI flags to configure it. I was considering going fully plaintext on the filesystem, but I might add an API later that does not force me to implement and CalDav client for Endirillia just so she knows when to remind me of an appointment.
Thankfully go makes it relatively painless to work with XML except for adding some annotations. That said, it feels like the whole standard could use some fresh paint. But considering how "successful" (/s) JMAP is compared to IMAP I do not think there is an any point in trying. CalDav is a standard that works and as long as you do not own the whole stack and can lock your customers into your ecosystem it is what we got.
A considerable amount of work and time would need to be invested to improve it, and I got to admit I do not think we would ever see the benefit. CalDav works, it does what it is supposed to do. Sure, every client thinks a bit differently about how the RFC is interpreted, but this will also happen for a new RFC defining the replacement. Once you iron out the little kinks, it is fine.
Progress
I think I can successfully manage calendar entries. I will deploy it and see if it falls apart somewhen this week while I add a few more features such as proper email invites and some basic unit tests before pushing the code to Forgejo.
Yesterday I actually managed to pick up work on my 3D model again, since I was not able to make progress the last few weeks. So back to the hair. I might have figured out why I did not get to the point I wanted it to be before I call it good enough. The shaders were so far off from what they are supposed to be, it never stood a chance. So I will likely spend a week working on a new hair shader while telling me it will be alright. Surely it will be, right? RIGHT?
posted on June 28, 2026, 9:01 p.m. in golang, lazerbunny