“I've adapted Scott Hammeter's dandy code (http://sfdc.arrowpointe.com/2009/10/06/associate-email-to-salesforce-task-to-opportunity/) so I can relate the tasks created by inbound Email-to-Salesforce emails to active campaigns rather than to open opportunities. The heart of the solution is a constructor in a class called by a beforeInsert task trigger. The construct queries for campaigns related to the contact identified in the task's WhoId:

// Associates a new Task generated by Email to Salesforce to an open opportunity, if one exists for the Account
public void AssociateCampaign(Task[] tasks)

    * Variables
    list<Task> l_Tasks = new list<Task>(); // Tasks to update
    set<ID> s_ContactIDs = new set<ID>(); // Set of Contact IDs

    * Initial Loop
    for(Task t:tasks) {

        // Add Task to working list and collect the Contact ID
        if (t.WhatId == null && t.Subject.startsWith('Email:') && t.WhoId != null) {
            // only for Contacts
            if (String.valueOf(t.WhoId).startsWith('003')){


    * Create Maps
    // Maps Contact ID to a Campaign ID
    map<ID, ID> map_cID_to_camID = new map<ID, ID>();
        // Query for the Contact's active Campaigns. Sort CampaignMember by CreatedDate DESC so the Task gets assigned to the campaign to which contact was most recently added
        for (CampaignMember cm:[select Id, CampaignId, ContactId, CreatedDate
                                         from CampaignMember
                                         where ContactId in :s_ContactIDs
                                         AND Campaign.IsActive = TRUE
                                         order by CreatedDate DESC
                                         ]) {
            map_cID_to_camID.put(cm.ContactId, cm.CampaignId);
            System.debug('ContactId - CampaignId:' + map_cId_to_camId);

    * Process Records
    for (Task t:l_Tasks) {

        // If the Contact has a campaign mapped to it, update the Task with that campaign
        if (map_cID_to_camID.get(t.WhoId) != null) {
            t.WhatId = map_cID_to_camID.get(t.WhoId);


This works as intended in orgs where the Shared Activities feature has not been enabled: a task created from an Email-to-Salesforce email has the contact's most recent active campaign in the WhatId (label: "Related To") field.

In orgs with Shared Activities enabled, the code fails to do this and WhatId remains null. Research makes me think that the code needs to insert a TaskRelation object for each Task object, with values AccountId = cm.Contact.AccountId IsWhat = TRUE TaskId = Task.Id RelationId = cm.CampaignId.

Has anyone else experienced this issue of needing to insert or update TaskRelation objects in order to populate Task.WhatId through the API?”

1 Answer 1


I may have some insight on this. Let me know if this helps.

In my experience (with the WhoId, not the WhatId), I had to do the following:

On an initial INSERT, I would populate the Task and respective WhoId. If there were any other records I needed to related, I would use the TaskRelation object using the newly insert Task record's Id.

What happens on the back end, with Shared Activities on, is whatever you marked as the WhoId gets a TaskRelation record behind the scenes. How do I know that? Well, let me go over the UPDATE scenario.

On an update of a Task you cannot change the WhoId or the WhatId - throws an exception. However, if you remove the TaskRelation records that contain the WhatId or WhoId, the next TaskRelation record will take its place.

Having said that, if you need to remove all of the other related records to a Task, be sure to exclude the WhoId and the WhatId.

To relate another record to a Task, you simply add another TaskRelation record.

I hope that clarifies things.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .