Service and Support Blog

Service and Support Blog

Autocreating A Contact From Web To Case Or Email To Case

Marco Casalaina Mar 11, 2009

Today's post is not for everyone.  There's a very good reason why Salesforce.com does not autocreate contacts from Web To Case or Email To Case, and that reason is that one generally does not know enough about the person to associate him or her to an account.  "Can't you just use the domain from the email address?" people often ask me, and the answer is no.  If I get an Email To Case from somebody@yahoo.com, do I assume that person works for Yahoo?  Probably not.

That said, there are Salesforce.com users who legitimately need to autocreate contacts in this way.  If you're one of those users, then today's post is for you.  Even if you're not, the trigger in today's post shows some solid techniques for bulkifying triggers, so if you intend to write Apex triggers, read on anyway.

The best way to autocreate contacts from cases like this is to use an Apex trigger.  I'll post a link to the code here and then I'll explain what I've done.

Here's the code and the corresponding test class.  Be careful when copying and pasting that code to your own instance of Salesforce.com, as these blog pages sometimes insert line breaks in strange places.

So how does this work?  Let's examine the code a bit.

First of all, whenever you're writing Apex, you must bear in mind that these cases may be submitted in bulk, as many as 100 at a time.  As such you have to be very careful about where you do your queries -- for example, it's rarely a good idea to do a query inside of a loop!

Web To Case and Email To Case both use the SuppliedEmail field (that's the API name of that field -- you can see it on your Case Page Layout as "Web Email") to try to identify the person who submitted the case.  If the SuppliedEmail does not match the email address of any known contact in the system then Web To Case and Email To Case will leave the Contact field unfilled -- that's where our trigger comes in.  There's another field called SuppliedName that stores the name the person entered for himself (or the name associated with the email address).  We'll need that too.

So the first thing we do is make a list of the SuppliedEmail email addresses in the cases that have just been submitted:

    List<String> emailAddresses = new List<String>();
    //First exclude any cases where the contact is set
    for (Case caseObj:Trigger.new) {
        if (caseObj.ContactId==null &&
            caseObj.SuppliedEmail!='')
        {
            emailAddresses.add(caseObj.SuppliedEmail);
        }
    }

Having done that, we can now find any of the email addresses that already exist.  Why would we do that, you might ask?  If the email address already exists as a Contact, wouldn't Web To Case or Email To Case have already associated it?  Good question, there, reader.  You're using your noodle.  Normally, yes -- unless the email address is associated to more than one Contact!  In that case the system doesn't know which contact to choose, and so it doesn't associate any contact to the case.  We have to weed out any of the emails that are associated to multiple contacts, because this trigger also has no idea which one to associate to, so we'll leave those cases alone:

    //Now we have a nice list of all the email addresses.  Let's query on it and see how many contacts already exist.
    List<Contact> listContacts = [Select Id,Email From Contact Where Email in :emailAddresses];
    Set<String> takenEmails = new Set<String>();
    for (Contact c:listContacts) {
        takenEmails.add(c.Email);
    }

OK, that's done.  Note what I didn't do there -- I didn't do that query inside the loop of cases coming up, but rather I did it outside the loop, using the in operator in my query.  That's good Apex practice.

Another thing you don't want to do inside a loop is a DML operation -- inserts, updates and deletes.  I will momentarily be updating a bunch of cases, but it's a no-no to do that inside my upcoming loop, so I'll make a list of cases to update, and the contacts I'm going to insert, and put my cases in that list (and contacts in a map) so I can insert the contacts and update the cases all in a couple of bulk statements later:

    Map<String,Contact> emailToContactMap = new Map<String,Contact>();
    List<Case> casesToUpdate = new List<Case>();

Good then.  Now I only want cases that have no contact associated to them, but have a SuppliedEmail and a SuppliedName (because there's no sense making a new contact with no name), and that SuppliedEmail isn't associated to multiple contacts, so:

    for (Case caseObj:Trigger.new) {
        if (caseObj.ContactId==null &&
            caseObj.SuppliedName!=null &&
            caseObj.SuppliedEmail!=null &&
            caseObj.SuppliedName!='' &&
            !caseObj.SuppliedName.contains('@') &&
            caseObj.SuppliedEmail!='' &&
            !takenEmails.contains(caseObj.SuppliedEmail))

In the loop, I take each case's SuppliedName and split it by the space.  This works reasonably well for 2-word Anglican and Latin names, but it can produce a funny looking name if, for example, somebody enters his name like "Marco S. Casalaina."  If you want better looking names, feel free to tweak the logic here. What can I say, I'm just lazy!

With our name nicely formatted, we can create a spartan contact which will have nothing but a name and an email address, since that's all we know:

            //The case was created with a null contact
            //Let's make a contact for it
            String[] nameParts = caseObj.SuppliedName.split(' ',2);
            if (nameParts.size() == 2)
            {
                Contact cont = new Contact(FirstName=nameParts[0],
                                            LastName=nameParts[1],
                                            Email=caseObj.SuppliedEmail,
                                            Autocreated__c=true);
                emailToContactMap.put(caseObj.SuppliedEmail,cont);
                casesToUpdate.add(caseObj);
            }

Now leaving the loop, we're good to go -- we can create our contacts now:

    List<Contact> newContacts = emailToContactMap.values();
    insert newContacts;

Now that we've inserted the contacts, they're "real" and have IDs, so we can associate the cases to them:

    for (Case caseObj:casesToUpdate) {
        Contact newContact = emailToContactMap.get(caseObj.SuppliedEmail);
        
        caseObj.ContactId = newContact.Id;
    }

And that's it!

So, you ask: where's the update statement for the cases?  Wow, reader, you do have a sharp eye.  We don't need one!  This code is running in a before insert trigger on Case.  Since the cases in this list are the ones that actually fired this trigger, if I modify them here, I don't need to call update on them -- I just have to change their field values and they will automagically be updated.

The code given here is by no means perfect, and as I've said, it's not for everyone.  Some people may want to modify this to create a Person Account instead of a Contact; others may want to tweak how I'm splitting the name, since as I said before, I was a bit lazy with it.  But this code does demonstrate two things.  First, it shows how to autocreate contacts, as the title of this blog indicates.  More importantly, though, it's shows some techniques for writing triggers that behave properly in bulk.

As Martha Stewart would say, "Bulkified triggers -- they're a good thing."

 

7 Comments

Jeremy

Hi, thanks for a very helpful trigger. I am new to apex development, and am trying to implement this trigger, but when i save i get a compile error:
Error: Compile Error: unexpected token: newContact.Id at line 52 column 29

Any advice on how to fix this would be much appreciated

Matt Kaufman

This doesn't work. BeforeInsert triggers do not run on Cases submitted via Web-to-Case forms. You'd have to modify this code somewhat to work with an AfterInsert trigger.

Matt Kaufman

My mistake, it works but not if you move the code into a Class, all the code has to stay in the Trigger.

Matt Kaufman

Ok, not enough caffeine today, it works in a class too!

Marco Casalaina

Please post any questions about this blog entry to the General Development board, http://community.salesforce.com/sforce/board?board.id=general_development

pwb

I'm not sure why anyone would *not* want an Account/Contact created off of a Case. Salesforce's problem is that it still doesn't work very well for services that use email address as the main identifier (which is pretty much every online service that's been created over the past 15 years).

pwb

Can this work on Pro or is EE required?

Post a comment

If you have a TypeKey or TypePad account, please Sign In.