2.8 KiB
calendar-parser
(no submodules)
Purpose
Parses iCalendar (RFC 5545) / ICS payloads embedded in emails — these
arrive either as text/calendar MIME parts or as attachments with the
application/ics media type. Only the subset of fields meaningful to
email automation is extracted: summary, description, start/end,
location, organizer, attendees, UID, and METHOD (REQUEST / REPLY /
CANCEL). The parser is deliberately tolerant of folded continuation
lines and ICS escape sequences.
Features
- Handles RFC 5545 line folding — both
CRLF + SPandCRLF + TABcontinuations. - Unescapes the four standard ICS escapes:
\\n,\\,,\\;,\\\\. - Extracts
mailto:addresses fromORGANIZERandATTENDEElines regardless of precedingCNparameters. - Captures the calendar-level
METHODand surfaces it on every event so consumers can branch on REQUEST vs CANCEL. - Ignores lines outside
BEGIN:VEVENT/END:VEVENTblocks soVCALENDARboilerplate does not leak into event fields.
Usage: Basic
import { parseICalendar } from "@suaveplan/email-processing/calendar";
const events = parseICalendar(icsAttachment);
for (const event of events) {
if (event.method === "CANCEL") {
await cancelMeeting(event.uid);
}
}
Usage: Advanced
import { parseICalendar } from "@suaveplan/email-processing/calendar";
const events = parseICalendar(icsString);
for (const event of events) {
const attendees = event.attendees.map((mailto) => mailto.toLowerCase());
await db.calendar.upsert({
where: { uid: event.uid ?? hash(event.summary ?? "") },
update: {
summary: event.summary ?? null,
dtStart: event.dtStart ?? null,
dtEnd: event.dtEnd ?? null,
location: event.location ?? null,
attendees,
},
create: { ... },
});
}
API Reference
CalendarEvent—{ summary?, description?, dtStart?, dtEnd?, location?, organizer?, attendees, uid?, method? }.parseICalendar(icsContent: string): CalendarEvent[]— returns an empty array when noVEVENTblocks are present. ThrowsEmailProcessingError(INVALID_INPUT)for non-string input.
Implementation Notes
DTSTART and DTEND are returned as the raw ICS date-time strings (e.g.
20250101T120000Z) without conversion. Consumers that need
Date/Temporal instances should call their preferred parser — the
package deliberately does not pick one, since text/calendar payloads
arrive in many timezone shapes (floating, UTC, TZID-suffixed) and
correct handling requires knowledge of the surrounding email context.
The unfolding regexes operate on \r\n first so that \n-only ICS
payloads still split correctly on the second \r?\n split call.