Associate Email to Salesforce Task to Opportunity
I use the Email to Salesforce functionality every single day. This feature allows you to get a random email address similar to emailtosalesforce@0235ffdsdfsad98dvfj4i549540njh3.in.salesforce.com (this is just a sample) that you bcc on emails and Tasks get created in your system that are auto-associated to your Leads/Contacts.
There is an option to associate the Task to Opportunities, but instead of creating a Task and associating it to the Lead/Contact AND the Opportunity, 2 tasks are created: one against the Lead/Contact and another against the Opportunity. I have no idea why it was designed this way, but it was. Given that, I don’t use the option to associate the Task to Opportunities.
After months of manually assigning to Opportunities after I send the email, I got fed up and wrote a trigger that senses an email to salesforce record and auto associates it to the nearest Open Opportunity. I thought I’d share it with y’all.
This code assumes the following:
- You are using Email to Salesforce
- You have the Email to Salesforce option for Leads & Contacts enabled and the one for Opportunities disabled
- You associate Contacts to your Opportunities via the OpportunityContactRole object
Trigger
trigger Tasks on Task (before insert) { // BEFORE INSERT if(Trigger.isBefore && Trigger.isInsert){ Tasks t = new Tasks(); t.AssociateOpportunity(Trigger.new); } }
Class
public class Tasks { // Default Constructor public Tasks() { } // Associates a new Task generated by Email to Salesforce to an open opportunity, if one exists for the Account public void AssociateOpportunity(Task[] tasks) { /*************** * Variables ***************/ list<Task> l_Tasks = new list<Task>(); // Tasks we'll be updating 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')){ l_Tasks.add(t); s_ContactIDs.add(t.WhoId); } } } /*************** * Create Maps ***************/ // Maps Contact ID to an Opportunity ID map<ID, ID> map_cID_to_oID = new map<ID, ID>(); // Query for the Contact's Open Opportunities. Sort by CloseDate DESC so the Task gets assigned to the earliest Opportunity as it loops for (OpportunityContactRole ocr:[select Id, OpportunityId, ContactId from OpportunityContactRole where ContactId in :s_ContactIDs AND Opportunity.IsClosed = false order by Opportunity.CloseDate DESC ]) { map_cID_to_oID.put(ocr.ContactId, ocr.OpportunityId); } /*************** * Process Records ***************/ for (Task t:l_Tasks) { // If the Contact has an Opportunity mapped to it, update the Task with that Opportunity if (map_cID_to_oID.get(t.WhoId) != null) { t.WhatId = map_cID_to_oID.get(t.WhoId); } } } }
Test Class
@isTest private class Tasks_Test { static testMethod void AssociateOpportunity_Test() { // Create a Lead Lead l = new Lead(); l.FirstName = 'Test'; l.LastName = 'Lead'; l.Company = 'Test Company'; l.Email = 'leademail@example.com'; insert l; // Create an Account Account a = new Account(); a.Name = 'Test Account'; insert a; // Create a Contact Contact c = new Contact(); c.FirstName = 'Test'; c.LastName = 'Contact'; c.AccountId = a.Id; c.Email = 'contactemail@example.com'; insert c; // Create Opportunities list<Opportunity> l_Opps = new list<Opportunity>(); Opportunity o = new Opportunity(); o.AccountId = a.id; o.Name = 'Test Opportunity'; o.CloseDate = date.today(); o.StageName = 'Qualified'; o.Description = 'Test Opportunity Description'; l_Opps.add(o); Opportunity o2 = new Opportunity(); o2.AccountId = a.id; o2.Name = 'Test Opportunity'; o2.CloseDate = date.today().addDays(30); o2.StageName = 'Qualified'; o2.Description = 'Test Opportunity Description'; l_Opps.add(o2); Opportunity o3 = new Opportunity(); o3.AccountId = a.id; o3.Name = 'Test Opportunity'; o3.CloseDate = date.today().addDays(60); o3.StageName = 'Closed Won'; o3.Description = 'Test Opportunity Description'; l_Opps.add(o3); insert l_Opps; // Create Opportunity Contact Roles list<OpportunityContactRole> l_Ocr = new list<OpportunityContactRole>(); OpportunityContactRole ocr1 = new OpportunityContactRole(); ocr1.ContactId = c.id; ocr1.OpportunityId = o.id; ocr1.IsPrimary = true; ocr1.Role = 'Decision Maker'; l_Ocr.add(ocr1); OpportunityContactRole ocr2 = new OpportunityContactRole(); ocr2.ContactId = c.id; ocr2.OpportunityId = o2.id; ocr2.IsPrimary = true; ocr2.Role = 'Decision Maker'; l_Ocr.add(ocr2); insert l_Ocr; /* Create Tasks for Test Cases */ list<Task> l_Tasks = new list<Task>(); // Task associated to Lead, not Contact Task t1 = new Task(); t1.Subject = 'Email: something'; t1.Status = 'Completed'; t1.WhoId = l.id; t1.ActivityDate = Date.today(); l_Tasks.add(t1); // Task with wrong subject Task t2 = new Task(); t2.Subject = 'something'; t2.Status = 'Completed'; t2.WhoId = c.id; t2.ActivityDate = Date.today(); l_Tasks.add(t2); // Task with no WhoId Task t3 = new Task(); t3.Subject = 'something'; t3.Status = 'Completed'; t3.ActivityDate = Date.today(); l_Tasks.add(t3); // Task with a What ID already Task t4 = new Task(); t4.Subject = 'something'; t4.Status = 'Completed'; t4.WhoId = c.id; t4.WhatId = o2.id; t4.ActivityDate = Date.today(); l_Tasks.add(t4); // Task that should get triggered fully Task t5 = new Task(); t5.Subject = 'Email: something'; t5.Status = 'Completed'; t5.WhoId = c.id; t5.ActivityDate = Date.today(); l_Tasks.add(t5); insert l_Tasks; /* Asserts */ // Task 1 should not have a What ID populated Task t = [select Id, WhoId, WhatId from Task where Id = :t1.id limit 1]; system.assertEquals(t.WhatId, null); // Task 2 should not have a What ID populated t = [select Id, WhoId, WhatId from Task where Id = :t2.id limit 1]; system.assertEquals(t.WhatId, null); // Task 3 should not have a What ID populated t = [select Id, WhoId, WhatId from Task where Id = :t3.id limit 1]; system.assertEquals(t.WhatId, null); // Task 4 should have the same What ID it had originally populated t = [select Id, WhoId, WhatId from Task where Id = :t4.id limit 1]; system.assertEquals(t.WhatId, o2.id); // Task 5 is the one that should've had the Opportunity ID auto populated t = [select Id, WhoId, WhatId from Task where Id = :t5.id limit 1]; system.assertEquals(t.WhatId, o.id); } }
Let me know if you have any suggestions.