CorrectICS

How to Generate Correct ICS Files From Your App (Best Practices + Templates)

A practical guide to producing ICS files that import cleanly in Google, Outlook, Apple Calendar, Teams, and iCal

If your app exports calendar events — for reminders, schedules, bookings, appointments, or syncing — you’ll eventually need to generate an ICS (iCalendar) file.

ICS looks simple, but generating a correct ICS that imports consistently across:

  • Google Calendar

  • Outlook / Office 365 / Exchange

  • Apple Calendar (iCal)

  • Teams

  • mobile calendar apps

  • legacy vendors

…is harder than it seems.

This guide walks you through:

  • minimal working ICS templates

  • common pitfalls when generating files

  • exporting recurring events correctly

  • time zone best practices

  • how to test your ICS generator

  • programmatically validating output

If you’re building ICS export functionality in a SaaS app, a booking engine, a scheduling tool, or a system that sends downloadable events, this is the definitive playbook.


1. Minimal Valid ICS Template (The Smallest File That Works Everywhere)

Every ICS file requires the following structure:

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//YourApp//EN
BEGIN:VEVENT
UID:123456@example.com
DTSTAMP:20250310T120000Z
DTSTART:20250310T130000Z
DTEND:20250310T140000Z
SUMMARY:Example Event
END:VEVENT
END:VCALENDAR

Required fields:

Component

Required Fields

VCALENDAR

BEGIN:VCALENDAR, VERSION:2.0, PRODID, END:VCALENDAR

VEVENT

BEGIN:VEVENT, UID, DTSTART, DTEND* or DURATION, END:VEVENT

(*DTEND is strongly recommended for broad compatibility.)

Why this works everywhere

  • Correct structure

  • UTC times (Zulu Z)

  • Valid UID

  • Minimal fields but complete

This is your baseline template before adding complexity like time zones, recurrence, or descriptions.


2. Generating UIDs (Most Developers Get This Wrong)

UIDs must be:

  • unique

  • deterministic if possible

  • stable across updates

  • globally resolvable (not required to be an actual domain)

A good pattern:

UID:${eventId}@yourapp.com

Example:

UID:evt1234@myapp.com

Do not use random UUIDs on every export of the same event — otherwise sync tools treat each export as a new event.


3. Timestamp Rules (DTSTART, DTEND, DTSTAMP)

DTSTAMP: When the event was created (always UTC)

DTSTART/DTEND: When it happens (your choice of UTC or local time)

Best practice:

  • Use UTC for single events

  • Use local time (with TZID) for recurring events that must respect DST

Examples:

UTC Timestamp

DTSTART:20250310T140000Z

Local Time With TZID

DTSTART;TZID=America/New_York:20250310T090000

Incorrect:

DTSTART;TZID=EST:20250310T090000

(EST is not a valid IANA TZID.)


4. Generating Time Zones Correctly (TZID + VTIMEZONE)

If you export events with local times without a Z suffix, you MUST include TZID.

Valid:

DTSTART;TZID=Europe/London:20250310T090000

Invalid:

DTSTART;TZID=GMT:20250310T090000
DTSTART;TZID=EST:20250310T090000

Do you need a VTIMEZONE block?

It depends:

Client / Platform

Requires VTIMEZONE?

Google Calendar

❌ No

Outlook Web

❌ No

Outlook Desktop

✔️ Yes

Exchange

✔️ Yes

Apple Calendar

✔️ Usually

If supporting Outlook Desktop or Exchange → include VTIMEZONE.

Example (New York):

BEGIN:VTIMEZONE
TZID:America/New_York
BEGIN:STANDARD
DTSTART:20241103T020000
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:20240310T020000
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
END:DAYLIGHT
END:VTIMEZONE

But: generating this manually is error-prone — most developers rely on an upstream library or normalize time zones via a tool like CorrectICS.


5. Recurring Events: RRULE Best Practices

This is the most common area where ICS generation breaks.

Simple weekly recurrence:

RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR

Monthly event:

RRULE:FREQ=MONTHLY;BYMONTHDAY=15

End date:

RRULE:FREQ=DAILY;UNTIL=20250331T000000Z

Common mistakes

❌ Missing FREQ:

RRULE:BYDAY=MO

❌ Invalid UNTIL date:

RRULE:FREQ=DAILY;UNTIL=20250230T000000Z

❌ Infinite recurring events without COUNT or UNTIL
(some clients reject or slow down on import)


6. Line Folding (The Least Known ICS Requirement)

ICS lines should be no longer than ~75 bytes and must be folded using:

  • CRLF (\r\n)

  • continuation lines starting with a space

Broken:

DESCRIPTION:This is a very long description that some calendar apps will reject because it is too long to be on one line...

Correct:

DESCRIPTION:This is a very long description that some calendar apps will reject
  because it is too long to be on one line...

Most developers forget this → causes Outlook import failures.

CorrectICS handles folding automatically.


7. Complete ICS Templates You Can Copy

Single Event (UTC)

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//MyApp//EN
BEGIN:VEVENT
UID:event-1234@myapp.com
DTSTAMP:20250201T120000Z
DTSTART:20250310T140000Z
DTEND:20250310T150000Z
SUMMARY:Sample Event
DESCRIPTION:This is an example event.
END:VEVENT
END:VCALENDAR

Local Time with Time Zone

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//MyApp//EN
BEGIN:VTIMEZONE
TZID:America/New_York
BEGIN:STANDARD
DTSTART:20241103T020000
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:20240310T020000
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
UID:event-5678@myapp.com
DTSTAMP:20250201T120000Z
DTSTART;TZID=America/New_York:20250310T090000
DTEND;TZID=America/New_York:20250310T100000
SUMMARY:Local Time Event
END:VEVENT
END:VCALENDAR

Recurring Event Template

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//MyApp//EN
BEGIN:VEVENT
UID:class-4321@myapp.com
DTSTAMP:20250201T120000Z
DTSTART;TZID=America/Denver:20250310T090000
DTEND;TZID=America/Denver:20250310T100000
SUMMARY:Class
RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR
END:VEVENT
END:VCALENDAR

8. How to Test Your ICS Generator

1. Import into multiple clients

  • Google Calendar

  • Outlook Web

  • Outlook Desktop

  • Apple Calendar

2. Check time zone conversions

Ensure no shifts occur around DST transitions.

3. Validate with CorrectICS

curl -X POST https://api.correctics.com/v1/validate \
  -H "Content-Type: text/calendar" \
  --data-binary @event.ics

4. Autofix output before delivering to customers

curl -X POST https://api.correctics.com/v1/autofix \
  -H "Content-Type: text/calendar" \
  --data-binary @event.ics \
  -o clean.ics

5. Include ICS validation in CI

(more examples in previous article)


9. Common Mistakes When Generating ICS Files

❌ Invalid or missing TZID

❌ Missing END:VEVENT / END:VCALENDAR

❌ Inline long lines without folding

❌ Missing UID

❌ Malformed RRULE

❌ Using Windows time zones

❌ Exporting invalid dates (Feb 30, etc.)

❌ Forgetting CRLF line endings

CorrectICS catches all of these.


10. Recommended Best Practices (TL;DR)

  • Use UTC for simple events

  • Use TZID + VTIMEZONE for local-time recurring events

  • Always include UID, DTSTAMP, VERSION, PRODID

  • Fold lines properly

  • Validate every ICS file before sending it

  • Provide downloadable ICS + add-to-calendar links

  • Use CorrectICS to clean, validate, and autofix your output


11. Conclusion: Good ICS Generation = Fewer Support Tickets

Exporting correct ICS files is essential for:

  • SaaS scheduling tools

  • booking applications

  • edtech platforms

  • CRM → calendar integrations

  • workplace shift scheduling

  • ICS feed publishers

A single faulty ICS export can result in:

  • wrong time zones

  • missing events

  • customer confusion

  • silent failures in Google/Outlook

Using the best practices above — and validating your output automatically — ensures your exports “just work” everywhere.

👉 Validate or autofix your ICS files instantly with CorrectICS
👉 Use the API in your ICS generator for guaranteed correctness