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.