Creating an .ics calendar file
The .ics file is the universal fallback. Every calendar application on every platform reads it, which makes it the one format worth getting exactly right. RFC 5545 is 168 pages long; here is the part you actually need.
Rather not build the URL by hand? The generator does all of this, including the encoding.
Generate a linkThe minimum viable file
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Your product//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
BEGIN:VEVENT
UID:unique-per-event@yourdomain.com
DTSTAMP:20260601T090000Z
SUMMARY:Q4 product webinar
DTSTART;TZID=Europe/Brussels:20260910T140000
DTEND;TZID=Europe/Brussels:20260910T150000
END:VEVENT
END:VCALENDAR
UID and DTSTAMP are required by the spec and skipped by most hand-rolled generators. Leaving them out mostly works, until a client decides to be strict about it.
The three rules that break files
- CRLF line endings. Every line ends with
\r\n. A file with plain\nis technically invalid, and some Outlook versions reject it outright. - Fold at 75 octets. Longer lines continue on the next line, which must start with a single space. Note octets, not characters: an emoji costs four.
- Escape text values. Backslash, semicolon and comma each get a leading backslash, and a newline becomes the two characters
\n. Miss this and a description containing a comma silently truncates the rest of the field.
A stable UID matters
The UID identifies the event. If you generate a fresh random one every time the file is downloaded, someone who clicks twice gets two events. Derive it from the event details instead, so the same event always yields the same UID and a second download updates the existing entry rather than duplicating it.
Serving the file
Content-Type: text/calendar; charset=utf-8
Content-Disposition: attachment; filename="webinar.ics"
Cache-Control: public, max-age=31536000, immutable
The long cache is safe when the URL fully determines the contents, for example when the event is encoded in the query string. If your URL is a short ID pointing at a mutable record, do not cache it that hard.
Validating
Before you ship, open the file in Apple Calendar, Outlook desktop and Google Calendar's import. Those three disagree about enough edge cases that passing all of them means you are fine. Watch specifically for the event landing an hour off, which almost always means a TZID the client does not recognise.