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.

3 Comments »

  1. Chris Said,

    November 13, 2009 @ 8:47 pm

    This looks like an awesome solution to this problem – but I have zero idea how to implement this.

    Have you considered putting it in the appexchange? (i have no idea if this is a difficult request or not).

    I suspect it would be popular. http://sites.force.com/appexchange/browse?type=Apps

    Chris

  2. Rob Crawley Said,

    December 9, 2009 @ 9:57 pm

    Scott – If I have lead data coming to me in emails on a daily basis from a Lead gen service is there a way to use this Email to Salesforce to have the data populated into a lead record like email to case? Pardon me if I’m having a senior moment, but I don’t think I can?

    I know of a utlity called Copy2Contact (formely Anagram) that could be the solution if we were a PC shop.

    Would welcome your thoughts and thanks for this posting.

    Rob

  3. Scott Hemmeter Said,

    December 10, 2009 @ 3:04 pm

    @Rob Crawley, yes, this could be done. The key is building an email service inside of Salesforce that is expecting to receive emails in a certain way. The Apex Code would be written to parse those lead gen emails you get. Trick #2 is being able to setup a forwarding rule that routes those emails you get to the assigned Salesforce email service. This is a simple task in Google Mail (gmail or apps) and I assume its simple in other email systems.

RSS feed for comments on this post · TrackBack URI

Leave a Comment

All comments are moderated. Other visitors will not see your comment until it has been approved.