Gluon Digital

Offical carrier of the Strongforce

Migrating Email Messages to Salesforce with Attachments - A Complete How to Guide.

Migrating Email Messages to Salesforce with Attachments - A Complete How to Guide.
David Masri         Author:
David Masri
Founder & CEO

Updated : 3/19/2024

Historically when migrating email messages to Salesforce we have been asked to load them as tasks, but with the release of the Enhanced Email functionality a while back, it's becoming increasingly common to want to migrate historical email data into the EmailMessage Objects.

Unfortunately loading these objects can be tricky, and worse, error messages returned are often obscure. Sometimes it will tell you that we have "Insufficient Access Rights" even if we are an admin, and other times it will just say "An unexpected error occurred".

But have no fear, this article will guide you through it!

Note: The EmailMessage Objects are only available for organizations that use Email-to-Case or Enhanced Email, which is automatically enabled for most customers.

Note 2: I would advise you to disable email sending while loading, just in case.

Loading EmailMessages is not hard if you follow the right steps and avoid making a few basic mistakes, it's a five-step process that I out line below (with all the gotchas to lookout for). But first, let's take a moment to review the Data Model as it will make this process a lot more understandable.

Descriptive Text

EmailMessage Core Objects

Human Objects : These are the Contact, Lead or User objects

Non-Human Objects : these are Non-human objects such as Accounts, Opportunities or Custom Objects.

EmailMessage(Documentation) : This is the object that holds the core Email data (Subject, Body, etc) It can be parented to a Case (via ParentId), a Non-Human Object (via RelatedToId), and many Human Objects records via the EmailMessageRelation junction object.

Note:The EmailMessage can also be related to a Task (via ActivityId) - but this field is generally used for tracking incoming unread emails. Most likely when migrating data, you won't need to populate this field.

EmailMessageRelation (Documentation) : This is a Junction object relating the Email to all the People (Human Object Records) associated to the Email - ie the From, To, CC Bcc.

Case (Documentation) : This is the standard Case object

ContentDocument (Documentation) : Email attachments are stored using Salesforce Content, this is the object that stores all the Attachment related info, but not the attachment itself. It does contain a lookup to the current version of the document (LatestPublishedVersionId)

ContentVersion (Documentation) : This object actually stores all the attachments, in fact it stores every version of the document\attachment. Email messages don't need documents versioning, so each ContentDocument will only have one version.

ContentDocumentLink (Documentation) : This is the junction object that relates the Documents/ Attachment to any other object, or in our case, the EmailMessage.

Great! Let's Get Loading!

Step 1: Load The EmailMessage Object

When loading the EmailMessage Object, you want to at a minimum set the following Fields:
  1. ParentId or RelatedToId or Both (CaseID or Non-HumanObjectID)
  2. Subject
  3. FromName
  4. TextBody or HTMLBody
  5. MessageDate
  6. And of course, a custom ExternalID Field.

DO NOT set the Status field, if we set the Status to '3' (Sent) it will completely lock down the record making it read only, it will also block your ability to populate the EmailMessageRelation object or add the attachments. We can't unset the status as the record is now read only. Our only option is to delete the record. Once we are done loading all our data, we will go back and update the Status.

DO NOT Set the CreatedBy! If we do, and the EmailMessage is not related to a task, Only the CreatedBy user will be able to delete the record. Even if we are an Admin, we will not be able to delete the data!

(Update) NOTE: Recently, Salesforce added a checkbox IsClientManaged to the EmailMessage Object, if you mark that as True, you can ignore the above 2 warnings. The Record will not Lock.

So, if we loaded 200k emails, with all different CreatedBys and set the Status to '3', we will not be able to move forward with our data migration. We have no choice but to contact Salesforce support and ask them to delete the EmailMessages for us, and then start over. (See This Article for details)

Step 2: Load The EmailMessage Object

Load the EmailMessageRelation. Here we load all the Human Records related to the Object. We need to set the following fields:

  1. EmailMessageID - this is the parent EMailMessage we created in the previous Step.
  2. ContactID - This is the ContactID of the Related Contact
  3. RelationAddress - This is the EmailAddress (of the Contact)
  4. RelationType: Picklist (values: ToAddress,CcAddress,BccAddress,FromAddress,OtherAddress)
  5. This object is not customizable so you can't add an External ID

If you set the ContactID but not RelationAddress, Salesforce will automatically look up the contacts email address and set it for you. If the contact does not have an email address, the insert will fail. Because of this I recommend you always set the email address for the migration to some made-up email address - FirstLast@NoEmail.test** . (Yes, I have had migrated emails from legacy CRMs that didn't have an Email address… and if you do this kind of stuff as often as I do, you will have to too).

If you set the RelationAddress but not the ContactID, Salesforce will look up the Contact, and will probably fail if it doesn’t find one or finds 2. (I don't know, I was too lazy to test it).

**There are 2 top level Domains reserved for testing, so you can use them without having to worry about them ever being real Email \Web Address. They are ".Test", ".Example", and ".Invalid". See here.

Step 3: Load Attachments to ContentVersion

By Loading new Attachments to Content version, Salesforce will automatically create and set the ContentDocument Record. This object is customizable, so you should set an ExternalID.

Step 4: Load ContentDocumentLink

(Update) If the attachment will only be associated to a single record (in this case an Email), you can set the “FirstPublishLocationId” field to the EmailMessageID. Then you can skip this step. (It only needed when the attachment is associated to multiple records.)

Now that we have all our ContentDocuments loaded, we can associate them to the EmailMessages by populating the ContentDocumentLink object. Just set:

  1. linkedEntityID to the EmailMessageID
  2. ContentDocument to the ContentDocumentID (not Content Version).
  3. ShareType to "V" - Because the EmailMessage object locks everything it touches we will have to set ShareType to "V" (View only) as opposed to "I" (Inherited). Setting it to I will throw an error.

Pro Tip - The ContentVersion has your ExternalID plus the its parent ContentDocumentID, so you can use this object as a cross reference between the legacy system attachments and the corresponding Salesforce ones.

Pro Tip # 2: If you want to delete the attachments, its easiest to delete the parent ContentDocument record and let the ContentVersion & ContentDocumentLink records cascade delete. If you try and delete a ContentVersion that is the current version, Salesforce will demand that you set another version as current first.

Step 5: Update your EmailMessages, setting the Status to 3

A Status of 3=Sent, it is the only allowable value if no CaseID is Specified. Once set, everything becomes read-only.

That's it! Bada-Bing, Bada-Boom, Bada-Done!


Updated 5/27/2020

After a chat with Vinay (See the comments section below), we have determined the when inserting a new email messages if you set the Parent ID (must be a CaseID) the Status field will default to '3' – Sent instead of "5"-Draft. As I explained above once the status is sent to "3" the record is completely locked down and will not allow for further updates or creation of child objects.

If you set the ParentID (to a CaseID), you should also Status to "5" as part of the load, then the record will remain updateable. You will likely receive the error message "operation not allowed" (see this article), it's because by default you can't create Draft emails against cases. To enable Email Drafts for Cases, follow the instructions here.

Update 12/10/2020

Apparently, If you set EmailMessage.IsClientManaged to True, you can add attachments even if the Status=3. But you can't make other edits or add or create other related records. See the comments section below, Thank you Sebastian!

This article is adapted from my book: Developing Data Migrations and Integrations with Salesforce.