Archive for October, 2009

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.

Comments (4) comments feed