9 Replies Latest reply on May 13, 2017 7:50 AM by Derek Vansant

    Introducing Agical.io, the smarter ICS file generator

    Sanford Whiteman

      If you've used Marketo's dynamic ICS files (the "Calendar File" token type) you're probably not completely happy with 'em.


      About a year ago, I published a (free) microservice that fills in the biggest gaps. With a little more attention on it lately (read: 2 people cared!), I blogged about it.


      Read more on TEKNKL :: Blog →

        • Re: Introducing Agical.io, the smarter ICS file generator

          Hey Sanford,


          I hope you're doing well. I'm trying to use the Agical.io ICS file generator you built and am running into an issue that I hope you can help me resolve. Thank you for creating this tool and offering it to the Marketo community.


          I have two velocity scripting based tokens for the event start date and end date that are record specific and need to be in the URL for the ICS file. When I try to reference those tokens as values in a URL an extra space is added, causing the hyperlink to fail when clicked. By fail, I mean that the event start time and end time reders as a static value: 12/31/1969 4:00 PM. I believe this is due to the URL being rewritten by Marketo for tracking purposes. If you copy and paste the non-rewritten URL in a browser, the link works just fine.


          Rewritten/tracked URL: http://em.sungevity.com/dc/s4ZLka5FfdFC9m1cmI_hrfzCmWMofwKTi4SUWBNAIw3LVZb3cBJV4iftjWnumggM45sqe1n3FwjsAI39bP3VjsLB2At9G…


          Copied and pasted URL with populated custom tokens: http://ics.agical.io/?subject=Sungevity%20Home%20Visit&organizer=Sungevity&reminder=45&location=1205 Foster Ter&dtstart= 2017-01-04T23:00:00Z&dtend= 2017-01-04T20:00:00Z


          See the spaces below. The issues looks specific to the token being included in a URL as the first two bullets are just non-linked token values. The rest are hyperlinks and are problematic. Please let me know if you have any ideas to resolve this issue. Thanks very much!




            • Re: Introducing Agical.io, the smarter ICS file generator
              Sanford Whiteman

              Are you positive there isn't whitespace being output from your script?  Remember, Velocity preserves whitespace like line breaks, because the output context may require it (think text part of an email as opposed to HTML part).

                • Re: Introducing Agical.io, the smarter ICS file generator

                  Thanks for your quick reply. That's great to know. I've removed all visible line breaks and spaces, but the link still populates with spaces. Do you have any other ideas for what could be causing the issue?


                  Here's script for the first token that's referenced as the dtstart parameter.


                  ##Start of Script

                  ## first want to grab the value of the home visit date time and convert it into format we want

                  ## BASED ON SFDC CONFIG, ASSUMING THIS VALUE IS IN PST plus 2 hours

                  ## used parseDate (string value, string format) within velocity

                  #set($dateObj = $convert.parseDate($Milestone1_Project__cList.get(0).Home_Visit_Date__c, 'yyyy-MM-dd HH:mm'))

                  ## then we convert to a calendar object

                  #set($calendarObj = $convert.toCalendar($dateObj))

                  ## comparing Time Zone on the Project and the to localize appointment time

                  #if ($Milestone1_Project__cList.get(0).Time_Zone__c == "EST" && $Milestone1_Project__cList.get(0).Home_Visit_Date__c.contains("12:00:00"))

                  ## 10 represents 'hour' in calendar object?

                  ## add 7 hours + 8 hours as it's translated into Greenwich in the final output.


                  #elseif ($Milestone1_Project__cList.get(0).Time_Zone__c == "CST" && $Milestone1_Project__cList.get(0).Home_Visit_Date__c.contains("12:00:00"))


                  #elseif ($Milestone1_Project__cList.get(0).Time_Zone__c == "MST" && $Milestone1_Project__cList.get(0).Home_Visit_Date__c.contains("12:00:00"))


                  #elseif ($Milestone1_Project__cList.get(0).Time_Zone__c == "PST" && $Milestone1_Project__cList.get(0).Home_Visit_Date__c.contains("12:00:00"))


                  ## comparing Time Zone and value in Home Visit Date field to update all records that don't have an appointment at 12:00pm

                  #elseif ($Milestone1_Project__cList.get(0).Time_Zone__c == "EST" && !$Milestone1_Project__cList.get(0).Home_Visit_Date__c.contains("12:00:00"))


                  #elseif ($Milestone1_Project__cList.get(0).Time_Zone__c == "CST" && !$Milestone1_Project__cList.get(0).Home_Visit_Date__c.contains("12:00:00"))


                  #elseif ($Milestone1_Project__cList.get(0).Time_Zone__c == "MST" && !$Milestone1_Project__cList.get(0).Home_Visit_Date__c.contains("12:00:00"))


                  #elseif ($Milestone1_Project__cList.get(0).Time_Zone__c == "PST" && !$Milestone1_Project__cList.get(0).Home_Visit_Date__c.contains("12:00:00"))




                    • Re: Introducing Agical.io, the smarter ICS file generator
                      Sanford Whiteman

                      (Yikes, I really want to rewrite that #if...#elseif block, but I'll hold myself back.)


                      The final line does likely have a line break.  End the whole script with ##.


                      Also, whenever you do "no-output output" like:




                      that's still output.  If you're just calling a method and not expecting output, better practice is:


                           #set( $tmp = $calendarObj.add(10,9) )


                      This is guaranteed to be a #set directive only, not an output line.

                        • Re: Introducing Agical.io, the smarter ICS file generator
                          Sanford Whiteman

                          (Yikes, I really want to rewrite that #if...#elseif block, but I'll hold myself back.)

                          I couldn't stop myself...


                          You don't have to do this now (it isn't causing your problem) but think about a collection-first coding approach. It makes for fewer errors because there's less repetition. In the Velocity world, this is especially important because VTL swallows errors that would be fatal in other languages, and also because of the whitespace issue as noted above (the fewer lines that may generate output, the easier it is to trim whitespace).


                          When you think of collections first, long lists of conditions become index and/or property lookups instead. Though "magic strings" and "magic numbers" are not completely eliminated, they're all in one place where they can be easily maintained and typos more easily caught.


                          #set( $DATE_PART_HOUR = 10 )
                          #set( $tzInfo = {
                            "EST" : {
                              "12:00" : 0,
                              "" : 12
                            "CST" : {
                              "12:00" : 23,
                              "" : 11
                            "MST" : {
                              "12:00" : 22,
                              "" : 10
                            "PST" : {
                              "12:00" : 21,
                              "" : 9
                            "" : {
                              "" : 0
                          } )
                          #set( $project = $Milestone1_Project__cList[0] )
                          #set( $project.homeVisitCal = $convert.toCalendar($convert.parseDate($project.Home_Visit_Date__c, 'yyyy-MM-dd HH:mm') ) )
                          #set( $project.homeVisitTime = $date.format('HH:mm', $project.homeVisitCal) )
                          ## /* get tz info based on stored tz abbr (including empty string) */
                          #set( $tzOffsets = $tzInfo[$project.Time_Zone__c] )
                          ## /* get current offset based on "magic" time slot(s) or default slot */
                          #set( $currentOffset = $tzOffsets[$project.homeVisitTime] )
                          #if( !$currentOffset )#set( $currentOffset = $tzOffsets[""] )#end
                          ## /* add hours to Calendar */
                          #set( $tmp = $project.homeVisitCal.add($DATE_PART_HOUR, $currentOffset) )


                          Note the use of the empty string as the default key -- not something I'd do in other contexts but helpful in Marketo, where an empty value comes in often. So you have $obj["token_value_1"], $object["token_value_2"], or $object[""], where the last one is the default.


                          Also, as I'm sure I mentioned elsewhere, the manual management of hour offsets is prone to error in its own right. I'm not changing that approach here, but I would feel more comfortable if you used Java's timezone-awareness instead of adding to the calendar object. The only time I would use Calendar::add and such is if I weren't dealing with time zones but some other non-standard date arithmetic, like days of a trial period, etc.

                          • Re: Introducing Agical.io, the smarter ICS file generator

                            Hey Sanford,


                            Yep, the token outputs the correct value without spaces. Thank you for providing a more scalable and easily modified structure for the token.


                            I encountered a bizarre issue when using that token within a URL or just as a standalone text value. This definitely looks like Marketo bug and I brought to Marketo Support who has already escalated internally to their engineering team since this is such bizarre behavior. See below for context. This issues doesn't seem to be specific to these tokens or tokens in general; it's a larger issue that seems to randomly remove sections of text and URLs in the email in both samples and in production.


                            1) I expected to see the static/non-token text that's in the emai draft prior to sending a sample.  See the text in purple below.


                            Inline image 1



                            2) I also expect to see the mytokens populated in one email with the following values for a specific Marketo Lead we've been testing. We tested these token individually and they populated, but when they are combined in one email (only the tokens, no URL or other text), only one token populates.


                            When these tokens did populate correctly in a sample email, they produced the following values as expected:

                            • {{my.Project HV Start Date Calendar}}: 2017-01-04T23:00:00Z
                            • {{my.Project HV End Date Calendar}}: 2017-01-05T02:00:00Z


                            3) I also expect to see the same URL referenced in the email draft with the same tokens populated. Instead though, the parameter "dtend=" just dissapears entirely and then the token value for the {{my.Project HV Start Date Calendar}} is shown twice: http://ics.agical.io/?subject=Sungevity%20Home%20Visit&organizer=Sungevity&reminder=45&location=1205%20Foster%20Ter&dtstart=2017-01-04T23:00:00Z2017-01-04T23:00:00Z


                            When the URL is tracked, it just fails all together meaning it creates an ICS file, but the start and end date are incorrect.


                            Here's a screenshot of the sample email:


                            Inline image 1



                            Here's a screenshot of the production email:

                            Inline image 1

                    • Re: Introducing Agical.io, the smarter ICS file generator
                      Derek Vansant

                      Great stuff, thanks Sanford. I think this is exactly what I am looking for.