11 Jun 2017

Subscribe to a Salesforce Calendar: creating a public ics feed


Our customers frequently want to share their calendars with folks who aren’t users in their Salesforce organizations: it’s one of the most common problems we hear about. This was one of the primary reasons for adding sharing to DayBack Calendar, and creating shares in DayBack is a great way to share specific views and time frames. You can download a free trial of DayBack from the AppExchange to experiment with this.

Google Calendar also provides good options for public sharing. There are lots of solutions on the AppExchange for syncing with Google, and once you’ve achieved the sync, you can use Google’s sharing options to create feeds that can be distributed publicly.

But, what if we want a purely Salesforce option? Does Salesforce provide us tools to do this without syncing to a separate database?

Force.com Sites

Force.com sites allows us to create public facing web sites based on Visualforce pages, so seeing if we can use this feature to publish an ICS feed seems like a good approach. Sites does require the Enterprise edition, but the included usage limits there are generous, and there are some scaling options available. Fortunately, sites is also enabled for the Developer edition with plenty of bandwidth to test and get things working. Finally, sites gives us our public url, now we just need to create a Visualforce page that dynamically publishes an ICS feed.

iCalendar Export on the AppExchange

iCalendar Export is a free app available on the AppExchange by Salesforce Labs, it’s an Unmanaged Package, which means it’s code is open and can be modified. Out of the box, it just works on Events, but it could be modified to work with any object. iCalendar Export doesn’t create a feed, but instead generates an .ics file export from the Events object based on a date range entered by a user. This file can then be imported into iCal or other clients that support the .ics format. This is OK, but the import is static. What if the data changes? Then a new export and import would be required.

So out of the box this won’t work for our feed. But, since it’s an Unmanaged Package, we should be able to use some of its code to get started.

More background: ICS Feed vs. Export

The URL for an ICS feed and an ICS export is actually the same URL, both are of content type “text/calendar,” the only difference is how the URL is accessed. If you open it in the browser, then the browser immediately downloads the content an .ics file, importing it and creating a static calendar. In a calendar app, however, you can subscribe to the URL and receive it as a dynamic feed.

Subscribe to a Salesforce Calendar from iCal

Subscribing to an ICS feed in macOS Sierra’s Calendar

With iCalendar Export, once you enter your date range in it’s Visualforce page you’re immediately taken to another Visualforce page that contains the “text/calendar” content that triggers the browser’s default export behavior. Instead, we want to start with that second visualforce page, and rather than rely on the date range entry from the first page, we want to use url parameters to determine what Events show in the feed. Let’s dive into the iCalendar code and see what we can do there.

Creating Our Feed: Modifying iCalendar 

There are two Apex Classes that come with iCalendar Export (plus a test class). The one we’re after is the iCalendar one. It’s the controller for the second Visualforce page that generates the “text/calendar” content. To access the class, you can either open up the Developer Console, and then open the class as a file, or you can go to setup and do a quick find for Apex Classes. Again, since iCalendar is an Unmanaged Package, it’s completely open.

As we start scanning the methods, we find what we’re looking for with getVCalendar(). This method generates the VCalendar string that makes up the .ics file/feed from a List of Events. The List itself is generated by calling another private method, LoadEvents(), that makes a SOQL query based on the date range entered on the previous page. The getVCalendar() method itself is fine, and formats the events in the ICS file beautifully. It’s the LoadEvents() method that we’re going to change.

For this example, we’ll keep it simple. We want to pass the Event owner’s name in as a url parameter and then just return those events in the feed. We’ll probably want to add additional parameters/criteria later, but we’ll get one working first.

First, let’s add a variable to the top class for capturing the url parameter owner: 

Private Static String owner = ApexPages.currentPage().getParameters().get(‘owner');

Next, let’s modify the SOQL query in LoadEvents() to return events by Owner name using the above owner variable.

List events = [
  SELECT Id , OwnerId , StartDateTime , EndDateTime, IsAllDayEvent, Subject, Location, 	ShowAs, IsPrivate, ReminderDateTime, Description, WhoId, Who.Type,				WhatId,What.Type, RecurrenceActivityId, IsRecurrence, IsGroupEvent, IsChild,
  (SELECT Id, AttendeeID, Status, RespondedDate, Response, Attendee.Name, 			Attendee.Email, Attendee.Type FROM EventAttendees)
  FROM Event
  WHERE Owner.Name = :owner

Now, we want to change the name of the feed, so it makes sense when it shows up in the end user’s client app. This is done by modifying the X-WR-CALNAME parameter in the ICS file. For this example we want to see “User name’s Salesforce Calendar”, so we’ll change that line in getVCalendar() method to:

result += 'X-WR-CALNAME:' + owner + '\'s Salesforce Calendar' + CRLF;

Next, we need to update our unit test to capture that we’re querying Events by owner instead of by date range so we can maintain a production level of code coverage:

static testMethod void Test() {
  iCalendar_TestUtilities testUtils = new iCalendar_TestUtilities();
      iCalendar ics   = new iCalendar();
      owner = 'Jason Young'; /*use appropriate owner name*/
      System.assert( ics.getVCALENDAR() != null );

Finally, since we’re going to be using a custom profile to define the privileges for our force.com site, we want to change the with sharing keyword for the class to without sharing, so the sharing rules for the user/profile are not used.

global without sharing class iCalendar {

We can also remove the date range stuff and the other stuff we’re not using to clean things up and improve our code coverage a little. You can download the full updated class here.

The Visualforce Page

Now that we’ve got our class modified, we can create a super simple Visualforce page that uses it as the controller:

That’s it! Besides referencing our controller and method, we simply specify our content type and that we don’t want any caching, so any changes made to Events will be picked up on the next refresh by the subscribing client. With our Visualforce page all finished, we can complete setting up our Site.

Site Set Up

Site set up is easy, and there’s no code involved. Navigate over to Sites in set-up and find an available domain name for your public site. Once you have your domain, you can create up to 25 subdomains. For this example, I used “calendar” as my sub-domain. Requiring https is optional, but since the common calendar clients support https for subscriptions, there’s no reason not to require it. For “Active Site Home Page,” specify the Visualforce page we just set up.  The rest of the site set-up options are straightforward.

When we’re done we get a url that looks like:


For our subscription, we want to add the Event owner’s name as a parameter so our url now looks like:


When we enter this url into our calendar app as a subscription url, voila, we get a feed of Jason Young’s Events.

Salesforce calendar in iCal

ICS feed in macOS Sierra’s Calendar with related Lead’s Contact Info

Salesforce calendar on iPhone

ICS feed in IOS Calendar

Source Events in SalesForce/DayBack

Going Further: Authentication

Unfortunately, there is no way to have a user authenticate this feed, so it really is public. Calendar clients, like Apple’s, do allow for Basic Authentication, but there’s no way to enable that on a Force.com Site. Instead, we’ll need to rely on obfuscation of the url: certainly not perfect, but better than nothing.

Our first thought would be to have a UUID associated with the Salesforce user and add that as a parameter to our url, so it would need to match in order to return those events. But we already need to add some additional criteria and we don’t want our url to become too heinously long, so let’s shift some of that logic out of the URL and into Salesforce by creating a custom formula field  in events.  You can make this as simple or as complex as you think is required, but here’s a simple one I used for this demo.

IF ( ActivityDate > DATE(2017,1,1) , SUBSTITUTE ( SUBSTITUTE ( Owner:User.Id, "0" , "eL") , "eLe" , "f") , "")

So, for Events that occur after the first, the formula will return a slightly obfuscated version of the user id we can use as our url parameter. In this case: fL536fLfLfLbrcd.

Now let’s go back to our iCalendar class and adjust it accordingly. First we’ll change our parameter name to something less descriptive than owner.

Private Static String p = ApexPages.currentPage().getParameters().get('p');

We also want to adjust or SOQL query to use our new parameter and custom field. We also want to retrieve the Owner’s Name for our feed name, since we’re no longer passing it as a parameter.

List events = [
  SELECT Id , OwnerId , Owner.Name, StartDateTime , EndDateTime, IsAllDayEvent, Subject, Location, 	ShowAs, IsPrivate, ReminderDateTime, Description, WhoId, Who.Type,				WhatId,What.Type, RecurrenceActivityId, IsRecurrence, IsGroupEvent, IsChild,
  (SELECT Id, AttendeeID, Status, RespondedDate, Response, Attendee.Name, 			Attendee.Email, Attendee.Type FROM EventAttendees)
  FROM Event
  WHERE ics_id__c = :p

Now we can change our line that defines the feed name to the following:

result += 'X-WR-CALNAME:' + events[0].Owner.Name + '\'s Salesforce Calendar' + CRLF;

And update our unit test to maintain our production level code coverage.

  iCalendar ics   = new iCalendar();
  p = 'fL536fLfLfLbrcd'; /*use appropriate owner id*/
  System.assert( ics.getVCALENDAR() != null );

You can download the full updated class here.

We can then use a URL that looks like this to get an ICS feed for Jason Young’s events.


Subscribe to Salesforce events on iPhone


I was pretty surprised at how little we actually had to do to get this working. Having a rich ecology of existing code, particularly in the form of Unmanaged Packages, provides an immediate starting point for those of us who are intimidated with having to take something like this on from scratch.  We even got to take advantage of the existing test classes provided with iCalendar Export, making our job just that much easier. Force.com Sites provides a tremendous amount of value for organizations. The effort required to do this without Sites would be significantly more time and effort than what we’ve demonstrated here.

If you have any questions about this post, DayBack or any other Calendar/Scheduling questions, please don’t hesitate to contact us.


Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.