Category Archives: URL hack

Download multiple ContentVersion files as zip

In our org, there is a relatedList underneath the Contract object of ContentVersions of the contract and any amendments. Users wanted to be able to download all (or download selected) files with one click.

Such relatedList does not provide any download button. You need to select each file in turn, go to the Content page and click Download. Tedious. Users hated it. I hated it.

Googling revealed an interesting post on downloading attachments into a zip using JSZip. However, we had files greater than 15MB and I worried about Heap Limits.

Turns out SFDC provides download ContentVersion as zip already on the Content tab. (Where our users nor I almost never go as the Contract relatedList is more convenient). So, I used the browser developer Tools to see what URL was constructed by SFDC and saw that it exploited the undocumented servlet.shepherd.

The specific URL to use is:

{!URLFOR('/sfc/servlet.shepherd/version/download/' & delimitedSelectedIdList &'?')}

where delimitedSelectedIdList is a list of ContentVersion ids separated by forward slash. from my understanding, the ids separated by slashes is a sometimes-used REST convention to specify a list of resources.

Example (downloads 2 files into single zip)

{!URLFOR('/sfc/servlet.shepherd/version/download/068xxxxxxxxxxxxxxx/068yyyyyyyyyyyyyyy?')}

I tested this on large zip files (~400 MB) and had no issues.

Notes:

  1. servlet.shepherd is undocumented and hence unsupported so this might not be the right answer for a mission critical application.
  2. servlet.shepherd does not work on Attachments, only ContentVersion.

Adventures in URL Hacking ‘Send an Email’ on Cases

URL hacking the SFDC Send an Email page is well-known. See for example:

Adventure starts
In one of my orgs, we were using On Demand Email-to-Case. If you’ve used this, you know that SFDC creates a thread_id value based on the orgId and case Id wrapped in special keywords (e.g. ref:_00DK0AaiLo._500K05CUEs:ref) . Any subsequent email sent from the Case will include this thread_id in the body and subject line so that if there is an email reply to your On Demand email-to-case email address, the reply is attached to the originating case (and doesn’t create a new case).

I had a custom VF page associated to the Case object and had added a custom button to send an email. Here was the URL hack (line breaks added for clarity):

<apex:commandButton 
  action="{!URLFOR($Action.Activity.SendEmail,csW.cs.id,
  [p2_lkid=csW.cs.contactId,
  rtype=LPAD('3',3,'00'),
  p3_lkid=csW.cs.id,
  retURL=$CurrentPage.url])}"
 value="Send an Email"/>
  • csW.cs.id refers to a controller variable csW of type CaseWrapper that had a member variable cs of type Case.
  • Per the URL Hack tips, p3_lkid refers to the ID of the target object used for merge fields – here, a Case object. p2_lkid references the recipient of the email – here, a Contact record id.

Adventure becomes a mystery
Given the following sequence:

  1. Customer sends email to Foo-support@ourdomain.com
  2. Customer receives auto-reply email containing the thread_id
  3. From the Case, Support team replies to the original email message (from: foo-support@ourdomain.com)
  4. Customer replies to message in step 3; this reply is properly attached to the Case.
  5. From the Case, Support team sends, using the custom button, a new email messsage to the customer (from: foo-support@ourdomain.com).
  6. Customer replies to message in step 5. This reply creates a brand new case!

It was as if emails generated from the ‘Email Message’ reply link had the proper thread_id but emails generated from the ‘Send and Email’ url hack did not – yet both resolved to the proper SFDC page with all fields filled in correctly.

Adventure Ends
This took me a while to figure out what with the flurry of emails to inspect and that the SFDC page that sends the emails looked just fine.

Here was the solution:

In my URL hack for ‘send an email’, when merge fields are resolved by SFDC, I got this:

https://cs9.salesforce.com/_ui/core/email/author/EmailAuthor
?p2_lkid=003K000000jbawQIAQ
&rtype=003
&p3_lkid=500K0000005CUEsIAO
&retURL=...

Note that &p3_lkid is an 18 character ID of the Case. When the email message was sent, SFDC used this 18 character ID and generated a threadId of ref:_00DK0AaiLo._500K05CUEsIAO:ref.

Contrast this thread_id with the thread_id generated by SFDC when the Case was originally created: ref:_00DK0AaiLo._500K05CUEs:ref, That is:

ref:_00DK0AaiLo._500K05CUEsIAO:ref
ref:_00DK0AaiLo._500K05CUEs:ref

Even though SFDC normally treats 18 character IDs the same as 15 character IDs, if the value of the URL hack field p3_lkid=csW.cs.id is an 18 character ID, the Send an Email SFDC code (not mine) creates a thread_id that is 3 characters longer than the thread_id normally generated when the Case was created via On Demand Email-to-Case

Obviously, the problem is that the ‘Send an Email’ code expected the value of &p3_lkid=500K0000005CUEsIAO to be a 15 character ID, not an 18 character Id, that is: &p3_lkid=500K0000005CUEs.
In other words, the SFDC Send an Email code is not independent of p3_lkId length (15 versus 18). Compounding the mystery was that Email To Case parses thread_ids in such a way as to ignore the 15 vs 18 character ID origin.

Thus, the solution was to change the URL Hack to the following (by substringing the 18 character Case Id to a 15 character value):

<apex:commandButton 
  action="{!URLFOR($Action.Activity.SendEmail,csW.cs.id,
  [p2_lkid=csW.cs.contactId,
  rtype=LPAD('3',3,'00'),
  p3_lkid=LEFT(csW.cs.id,15),
  retURL=$CurrentPage.url])}"
 value="Send an Email"/>