The Salesforce to iCalendar (ICS) Protocol Bridge: A Technical Reference
A definitive guide to implementing RFC-5545 compliant meeting invitations in Salesforce using Apex and LWC. Covers persistent versioning, recurrence rules (RRULE), timezone handling, and cross-client compatibility for Outlook and Gmail.
1. Technical Objective & Architectural Context
In enterprise environments, Salesforce often serves as the "Source of Truth" for customer interactions. However, without a bridge to email clients, meeting data remains siloed. The objective of this implementation is to bypass proprietary sync tools (like Einstein Activity Capture or Lightning Sync) to build a platform-agnostic, standards-compliant meeting orchestration engine.
By adhering strictly to RFC-5545 (Internet Calendaring and Scheduling Core Object Specification), we transform Salesforce from a passive data store into an active Calendar Authority.
The Architectural Flow
- Trigger Event: A Salesforce
Eventrecord is Created, Updated, or Deleted. - Protocol Translation: Apex logic translates the SObject data into an ICS text stream.
- MIME Encapsulation: The text stream is wrapped in a specific MIME type (
text/calendar). - Transmission: Salesforce
Messaging.SingleEmailMessagetransmits the payload. - Client Interpretation: Outlook/Gmail parses the MIME header, recognizes the
METHOD, and renders native UI controls (Accept/Decline/Propose New Time).
Operational Advantages
- Zero Client-Side Plugins: Works natively with any client supporting standard internet email.
- Deterministic Versioning: Solves the "Duplicate Meeting" problem via strict
UIDandSEQUENCEmanagement. - Auditability: Every invitation sent is loggable within Salesforce, unlike client-side syncs which function as black boxes.
At a Glance
Aspect Key Point Goal Native "Accept/Decline" buttons in Outlook/Gmail from Salesforce Standard RFC-5545 (iCalendar) Critical PropertiesUID (persistent), SEQUENCE (increment on updates), METHOD (REQUEST/CANCEL)
Common Failure
Random UIDs → duplicate "ghost" meetings
Solution
UID = Event.Id@OrgId.sfdc, SEQUENCE = custom field
2. Protocol Anatomy: The RFC-5545 Framework
The iCalendar format is a flat-file database format. It relies on strict line delimitation using CRLF (\r\n). While the specification defines many properties, a functional implementation requires a specific subset.
Below is the exhaustive list of functional properties categorized by their architectural impact.
A. Root & Control Properties (VCALENDAR Level)
These properties form the "Envelope" of the file. They define the intent of the message and how the client interprets the file structure.
Property Description Engineering Note & Effect of ModifyingMETHOD
Defines the transaction type. Values:
REQUEST, CANCEL, PUBLISH.
Critical. •
REQUEST: Forces "Accept/Decline" buttons.•
CANCEL: Removes the event from the calendar.•
PUBLISH: Informational only (no buttons). If you erroneously send PUBLISH instead of REQUEST, the user cannot RSVP.
VERSION
The iCal specification version.
Must be 2.0. Lowering this will cause modern clients (Outlook 365, Gmail) to reject the file as legacy data.
PRODID
Unique identifier for the generator.
Recommended format: -//<Company>//<App>//EN. No functional effect on the meeting, but used by IT admins to track where the invite originated.
B. The VEVENT Component (The Payload)
The VEVENT block encapsulates the actual meeting logic.
1. Identity & Versioning Properties
These control whether the meeting is treated as "New," an "Update," or a "Duplicate."
Property Description Engineering Note & Effect of ModifyingUID
The persistent Unique Identifier for the event.
High Risk. If you change the UID, the client creates a new meeting. If you keep it the same, it updates the existing meeting. Do not generate this randomly.
SEQUENCE
The revision number (starts at 0).
If you change the time but keep SEQUENCE static, clients (especially Outlook) will ignore the update. You must increment this (0, 1, 2...) to force an update.
DTSTAMP
The timestamp of file generation.
Used to resolve race conditions. If a client receives two invites with the same UID, it keeps the one with the later DTSTAMP.
2. Timing & Availability Properties
These dictate the "When" and how it affects the user's availability.
Property Description Engineering Note & Effect of ModifyingDTSTART
Meeting start time (format: YYYYMMDDTHHMMSSZ).
Changes the position of the event on the calendar grid. Always use UTC (Z suffix).
DTEND
Meeting end time.
Controls the "Length" of the block. If missing, some clients default to 0 minutes or 1 hour.
TRANSP
Time Transparency. Values:
OPAQUE, TRANSPARENT.
• OPAQUE: Blocks time (Busy).•
TRANSPARENT: Does not block time (Free).
RRULE
Recurrence Rule (e.g., FREQ=WEEKLY).
Transforms a single meeting into a series. Modifying this adds or removes future occurrences effectively.
3. Descriptive & Logistics Properties
These provide the "What" and "Where" for the human user.
Property Description Engineering Note & Effect of ModifyingSUMMARY
The meeting title.
Changes what is displayed in the calendar box.
DESCRIPTION
The body text/agenda.
Changes what the user sees when they click for details. Supports plain text.
LOCATION
Physical address or URL.
Affects where "Map" links point.
URL
A link to a resource.
Often used for "Join Teams/Zoom" links in modern clients.
CATEGORIES
Labels (e.g., WORK, URGENT).
Colors the meeting or allows filtering in the user's calendar app.
4. Participant Properties
These control the "Who" and the communication loop.
Property Description Engineering Note & Effect of ModifyingORGANIZER
The owner of the meeting.
This email address receives all "Acceptance" notifications. Changing this redirects the RSVPs.
ATTENDEE
The participants.
Must include RSVP=TRUE and ROLE=REQ-PARTICIPANT. If RSVP=FALSE, the user is invited but not asked to confirm, and no buttons appear.
5. Alarm (VALARM) Properties
These control client-side notifications.
Property Description Engineering Note & Effect of ModifyingTRIGGER
Timing for the alert (e.g., -PT15M).
Changes when the device "dings" before the meeting (e.g., 15 mins prior).
ACTION
How to alert (usually DISPLAY).
Determines if it's a pop-up or (rarely) an email alert.
Minimal Working Example
Before diving into the complexities, here's the simplest valid ICS file that produces "Accept/Decline" buttons:
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Your Company//Your App//EN
METHOD:REQUEST
BEGIN:VEVENT
UID:unique-id-12345@yourdomain.com
SEQUENCE:0
DTSTAMP:20240115T100000Z
DTSTART:20240120T140000Z
DTEND:20240120T150000Z
SUMMARY:Project Kickoff Meeting
ORGANIZER:mailto:organizer@example.com
ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:attendee@example.com
END:VEVENT
END:VCALENDAR
Test this locally: Save the above as
test.ics, email it as an attachment withContent-Type: text/calendar; method=REQUEST, and verify the buttons appear.
3. Advanced Engineering Challenges
I. The UID & Sequence Imperative (Objective Reality Check)
Warning: The
UIDis the #1 cause of ICS implementation failures. Get this wrong, and your users will have "ghost meetings" they cannot delete.
The biggest blind spot for developers is the UID. If your Salesforce logic generates a random UID (e.g., Crypto.getKey()) every time the code runs, you are creating "ghost meetings."
- The Scenario: You send an invite. Later, you update the time and send another.
- The Failure: If the UID changes, the client sees a new meeting, leaving the old meeting stranded on the calendar. The user cannot delete the old one without sending a manual cancellation.
- The Fix: Map
Event.Id+OrgIDto theUID. Map a custom fieldEvent.ICS_Sequence__ctoSEQUENCE.
II. The Timezone Paradox (UTC vs. Floating)
Salesforce stores data in UTC. Users view data in Local Time. The ICS protocol supports "Floating" (dangerous) and "Local with Reference" (verbose).
Best Practice: Always emit Absolute UTC with the
Zsuffix (e.g.,20230101T100000Z). Let the receiving client handle conversion to local time.
This approach delegates timezone math to the client, which already knows the user's local settings. Avoid "Floating" time (no timezone indicator), as it causes meetings to shift when users travel.
III. Line Folding (The 75-Octet Rule)
RFC-5545 Section 3.1 mandates that lines SHOULD NOT be longer than 75 octets (bytes). Long lines must be "folded."
- Mechanism: Insert a
CRLF(\r\n) immediately followed by a single whitespace character (space or tab). - Risk: If you fail to fold, Outlook often truncates the description or marks the attachment as corrupt.
Tip: The
applyLineFolding()utility in the implementation section handles this automatically. Never skip this step for production code.
4. Implementation: The Robust Apex Protocol Engine
This class includes handling for Reminders, Cancellations, and Text Escaping.
/**
* @description Core engine for generating RFC-5545 compliant ICS payloads.
* Handles escaping, folding, and structure for Request and Cancel methods.
*/
public class IcsProtocolBridge {
public enum Method { REQUEST, CANCEL }
public static String createInvite(Event evt, List<String> attendees, Integer sequence, Method methodType) {
String methodStr = (methodType == Method.CANCEL) ? 'CANCEL' : 'REQUEST';
String statusStr = (methodType == Method.CANCEL) ? 'CANCELLED' : 'CONFIRMED';
List<String> lines = new List<String>();
// --- VCALENDAR HEADER ---
lines.add('BEGIN:VCALENDAR');
lines.add('PRODID:-//Intech Solution//SFDC-ICS-Bridge//EN');
lines.add('VERSION:2.0');
lines.add('CALSCALE:GREGORIAN');
lines.add('METHOD:' + methodStr);
// --- VEVENT BODY ---
lines.add('BEGIN:VEVENT');
// Construct Persistent UID using Salesforce ID + Org Domain
lines.add('UID:' + evt.Id + '@' + UserInfo.getOrganizationId() + '.sfdc');
lines.add('SEQUENCE:' + sequence);
lines.add('DTSTAMP:' + formatUTC(Datetime.now()));
// Dates
lines.add('DTSTART:' + formatUTC(evt.StartDateTime));
lines.add('DTEND:' + formatUTC(evt.EndDateTime));
// Core Metadata
lines.add('SUMMARY:' + escapeIcsText(methodType == Method.CANCEL ? 'CANCELED: ' + evt.Subject : evt.Subject));
lines.add('DESCRIPTION:' + escapeIcsText(evt.Description));
lines.add('LOCATION:' + escapeIcsText(evt.Location));
lines.add('STATUS:' + statusStr);
lines.add('TRANSP:OPAQUE'); // Show as Busy
// Organizer
lines.add('ORGANIZER;CN="' + escapeIcsText(UserInfo.getName()) + '":mailto:' + UserInfo.getUserEmail());
// Attendees
for(String email : attendees) {
lines.add('ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION:mailto:' + email);
}
// --- ALARM / REMINDER (15 Minutes Prior) ---
if(methodType == Method.REQUEST) {
lines.add('BEGIN:VALARM');
lines.add('TRIGGER:-PT15M');
lines.add('ACTION:DISPLAY');
lines.add('DESCRIPTION:Reminder');
lines.add('END:VALARM');
}
lines.add('END:VEVENT');
lines.add('END:VCALENDAR');
// Join with CRLF and apply strict folding
return applyLineFolding(String.join(lines, '\r\n'));
}
// --- UTILITIES ---
private static String formatUTC(Datetime dt) {
if(dt == null) return '';
return dt.formatGmt('yyyyMMdd\'T\'HHmmss\'Z\'');
}
private static String escapeIcsText(String input) {
if (input == null) return '';
return input.replace('\\', '\\\\')
.replace(';', '\\;')
.replace(',', '\\,')
.replace('\r\n', '\\n')
.replace('\n', '\\n');
}
private static String applyLineFolding(String raw) {
String folded = '';
for (String line : raw.split('\r\n')) {
while (line.length() > 75) {
folded += line.substring(0, 75) + '\r\n '; // CR LF Space
line = line.substring(75);
}
folded += line + '\r\n';
}
return folded.trim();
}
}
5. Deployment & MIME Type Strategy
The success of the bridge depends on how the email is constructed. If the MIME type is wrong, Gmail will display raw text, and Outlook will treat it as a suspicious attachment.
The "Updates" and "Cancellations" Workflow
To support the full lifecycle, you need a mechanism to track the Sequence.
- Field Creation: Add a custom Integer field
ICS_Sequence__cto the Event object (Default: 0). - Trigger Logic:
- On
Insert: SendSEQUENCE:0. - On
Update: IncrementICS_Sequence__cby 1. SendSEQUENCE:1. - On
Delete: SendMETHOD:CANCELwith the current sequence number.
- On
Apex Email Implementation
public static void sendMeetingInvite(Id eventId, List<String> recipients, Boolean isCancellation) {
Event evt = [SELECT Id, Subject, Description, StartDateTime, EndDateTime, Location, ICS_Sequence__c FROM Event WHERE Id = :eventId];
// Determine Method
IcsProtocolBridge.Method method = isCancellation ? IcsProtocolBridge.Method.CANCEL : IcsProtocolBridge.Method.REQUEST;
// Generate Payload
String icsBody = IcsProtocolBridge.createInvite(evt, recipients, (Integer)evt.ICS_Sequence__c, method);
Blob icsBlob = Blob.valueOf(icsBody);
Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage();
// Attachment Configuration
Messaging.EmailFileAttachment invite = new Messaging.EmailFileAttachment();
invite.setFileName(isCancellation ? 'cancel.ics' : 'invite.ics');
invite.setBody(icsBlob);
// CRITICAL: The Content-Type header determines client behavior
String methodParam = isCancellation ? 'CANCEL' : 'REQUEST';
invite.setContentType('text/calendar; charset=UTF-8; method=' + methodParam);
email.setFileAttachments(new Messaging.EmailFileAttachment[] { invite });
email.setSubject((isCancellation ? 'Canceled: ' : 'Invitation: ') + evt.Subject);
email.setPlainTextBody('Please refer to the attached calendar file for meeting details.');
email.setToAddresses(recipients);
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { email });
}
6. Client Compatibility Matrix
Client Behavior Quirks Microsoft Outlook (Desktop) Excellent RequiresMETHOD to match in both MIME header and ICS body. Very sensitive to SEQUENCE numbering.
Gmail (Web)
Good
Automatically adds to Google Calendar. "Yes/No" buttons appear in the email header. Sensitive to UID format.
Apple Mail / iOS
Good
Renders a native UI card. Extremely strict about Line Folding (75-octet rule).
Outlook (Web/PWA)
Variable
Sometimes treats the ICS as a file attachment rather than an invite if PRODID is not recognized.
7. Troubleshooting Checklist (The "Rescue" Guide)
If your invites are failing, check these 5 points immediately:
- The "Ghost" Update:
- Symptom: You sent an update, but the user's calendar didn't change.
- Cause: You didn't increment the
SEQUENCEproperty, or theUIDchanged.
- The Corrupt File:
- Symptom: The attachment won't open.
- Cause: Check line folding. Ensure there is a SPACE after the CRLF on folded lines.
- Timezone Shifts:
- Symptom: Meeting is off by several hours.
- Cause: You formatted the date as local time but appended a
Z.
- Missing Buttons:
- Symptom: User sees an attachment but no "Accept" button.
- Cause: The
ATTENDEEproperty is missingRSVP=TRUEorPARTSTAT=NEEDS-ACTION.
- Character Encoding:
- Symptom: Special characters (é, ñ, ö) appear as garbage.
- Cause: Ensure the Blob is generated using
UTF-8and theContent-Typeheader specifiescharset=UTF-8.
Quick Reference Card
Essential Properties
Property Purpose ExampleUID
Unique event identifier
00A1B2C3@org.sfdc
SEQUENCE
Version number (0, 1, 2...)
SEQUENCE:1
DTSTAMP
File generation timestamp
20240115T100000Z
METHOD
Intent (REQUEST/CANCEL)
METHOD:REQUEST
Date/Time Format
YYYYMMDDTHHMMSSZ
│ │ └─ UTC indicator
│ └─ Time separator
└─ Date (no separators)
Example: January 15, 2024 at 2:30 PM UTC → 20240115T143000Z
ATTENDEE Property Template
ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION:mailto:user@example.com
METHOD Values
Value Use Case Client BehaviorREQUEST
New invite or update
Shows Accept/Decline buttons
CANCEL
Delete meeting
Removes from calendar
PUBLISH
FYI only
No action buttons
Key Takeaways
- UID must be deterministic — Use
EventId@OrgId.sfdc, never random values - SEQUENCE must increment — Updates without incrementing are silently ignored
- Always use UTC — Append
Zsuffix; let clients handle timezone conversion - Fold long lines — Lines over 75 bytes cause corruption in strict clients
- Match METHOD everywhere — MIME header and ICS body
METHODmust agree - Include RSVP=TRUE — Without it, no Accept/Decline buttons appear
- Test on multiple clients — Outlook, Gmail, and Apple Mail behave differently
Further Reading
- RFC 5545: iCalendar Specification — The authoritative standard
- Salesforce Apex Developer Guide: Messaging Class — Email attachment handling
- iCalendar Validator — Online tool to validate ICS syntax
- CalConnect — Calendar interoperability standards organization
8. Conclusion
Implementing an RFC-5545 bridge in Apex provides Salesforce architects with granular control over the calendaring experience. While the protocol is unforgiving regarding syntax, adhering to the property definitions and sequence logic defined in this reference ensures a seamless "Native" feel for end-users on Outlook, Google, and Apple platforms.
