+ Start a Discussion
paul-lmipaul-lmi 

Using Apex Email Services for Email to Case

ok, here's my class (work in progress)

Code:
//version 1.1
global class productEmailCreateCase implements Messaging.InboundEmailHandler {

global Messaging.InboundEmailResult
handleInboundEmail(Messaging.inboundEmail email,
Messaging.InboundEnvelope env){

// Create an inboundEmailResult object for returning
// the result of the Force.com Email Service
Messaging.InboundEmailResult result = new Messaging.InboundEmailResult();

//define your debugging email
string debugEmail = 'youremail@domain.com';

// get contents of email
string subject = email.subject;
string myPlainText = email.plainTextBody;
string emailtype = 'new';
string fromname = email.fromname;
string fromemail = email.fromaddress;
string[] toemail = email.toaddresses;
string product = '';

//print debug info
system.debug('\n\nTo: ' + toemail + '\n' + 'From: ' + fromemail + '\nSubject\n' + subject + '\nBody\n' + myPlainText);

//set the product based on the incoming email
if (toemail != null){
for (string i : toemail){
if (i.contains('logmeinfree')){
product = 'LogMeIn Free';
}else if (i.contains('logmeinpro')){
product = 'LogMeIn Pro';
}else if (i.contains('logmeinitreach')){
product = 'LogMeIn IT Reach';
}else if (i.contains('logmeinrescue')){
product = 'LogMeIn Rescue';
}else if (i.contains('logmeinbackup')){
product = 'LogMeIn Backup';
}else if (i.contains('logmeinhamachi')){
product = 'LogMeIn Hamachi';
}else if (i.contains('remotelyanywhere')){
product = 'RemotelyAnywhere';
}else if (i.contains('networkconsole')){
product = 'Network Console';
}else if (i.contains('logmeinignition')){
product = 'LogMeIn Ignition';
}
}
}

// First, instantiate a new Pattern object "MyPattern"
Pattern MyPattern = Pattern.compile('.*—[:]{3}[a-z0-9A-Z]{15}[:]{3}.*–');

// Then instantiate a new Matcher object "MyMatcher"
Matcher MyMatcher = MyPattern.matcher(myPlainText);

//status of the matcher
boolean reply = MyMatcher.find();
system.debug('Reply: ' + reply);

//regexp for finding caseid \[:[a-zA-Z0-9]{18}:]
//boolean reply = pattern.matches(':::[a-zA-Z0-9]{18}:::', myPlainText);

//determine if this is a new case or a reply to an existing one
if(reply == true){
emailtype = 'reply';
}

if(emailtype == 'new'){
// new Case object to be created
Case[] newCase = new Case[0];

// Try to lookup any contacts based on the email from address
// If there is more than 1 contact with the same email address
// an exception will be thrown and the catch statement will be called
try {
// Add a new Case to the contact record we just found above
newCase.add(new Case(Description = myPlainText,
Subject = subject,
Origin = 'Email',
SuppliedEmail = email.fromAddress,
Product__c = product,
SuppliedName = email.fromName));

// Insert the new Case and it will be created and appended to the contact record
insert newCase;
System.debug('New Case Object: ' + newCase );
}
// If there is an exception with the query looking up
// the contact this QueryException will be called.
// and the exception will be written to the Apex Debug logs

catch (Exception e) {
System.debug('\n\nError:: ' + e + '\n\n');
string body = 'Message: ' + e.getMessage() + '\n' + e.getCause() + '\n\n' + email.subject + '\n' + fromemail + '\n' + fromname + '\n' + toemail;
sendDebugEmail(body, 'reply');
}
}else if (emailtype == 'reply') { //reply handling
// new Case object to be created
Task[] newTask = new Task[0];
//get the WhatId
string match = MyMatcher.group(0);
system.debug('Match String: ' + match);
string caseId = match.replaceall(':','').trim();
system.debug('Case ID: ' + caseId);

Case[] getCase = new Case[0];
getCase = [select casenumber, id from case where id = :caseId limit 1];


// Try to lookup any contacts based on the email from address
// If there is more than 1 contact with the same email address
// an exception will be thrown and the catch statement will be called
try {
// Add a new Case to the contact record we just found above
newTask.add(new Task(Description = myPlainText,
Subject = subject,
Status = 'Completed',
WhatId = caseId));

// Insert the new Case and it will be created and appended to the contact record
insert newTask;
getCase[0].Status = 'Reply from Customer';
update getCase[0];
System.debug('New Case Object: ' + newTask );
}
// If there is an exception with the query looking up
// the contact this QueryException will be called.
// and the exception will be written to the Apex Debug logs

catch (Exception e) {
System.debug('\n\nError:: ' + e + '\n\n');

//set up and send debug email
string body = 'Message: ' + e.getMessage() + '\n' + e.getCause() + '\n\n' + email.subject + '\n' + fromemail + '\n' + fromname + '\n' + toemail + '\nCase ID: ' + caseid;
sendDebugEmail(body, 'reply');
}

// Set the result to true, no need to send an email back to the user
// with an error message
}
result.success = true;

// Return the result for the Force.com Email Service
return result;
}

static testMethod void testCases1() {

// Create a new email and envelope object
Messaging.InboundEmail email = new Messaging.InboundEmail();
Messaging.InboundEnvelope env = new Messaging.InboundEnvelope();

email.plainTextBody = 'Here is my plainText body of the email';
email.fromAddress =debugEmail;
email.subject = 'My test subject';

productEmailCreateCase caseObj = new productEmailCreateCase();
caseObj.handleInboundEmail(email, env);

// Create a new email and envelope object
Messaging.InboundEmail emailReply = new Messaging.InboundEmail();
Messaging.InboundEnvelope envReply = new Messaging.InboundEnvelope();

// Create the plainTextBody and fromAddres for the test
emailReply.plainTextBody = 'Here is my plainText body of the email';
emailReply.fromAddress = debugEmail;
emailReply.Subject = 'Re: LogMeIn Pro Case:00001030 - testing reply';

productEmailCreateCase caseReplyObj = new productEmailCreateCase();
caseReplyObj.handleInboundEmail(emailReply, envReply);

}
static testMethod void testCases2() {

// Create a new email and envelope object
Messaging.InboundEmail email = new Messaging.InboundEmail();
Messaging.InboundEnvelope env = new Messaging.InboundEnvelope();

// Create the plainTextBody and fromAddres for the test
email.plainTextBody = 'Here is my plainText body of the email';
email.fromAddress ='some@email.com';
email.subject = 'My test subject';

productEmailCreateCase caseObj = new productEmailCreateCase();
caseObj.handleInboundEmail(email, env);

// Create a new email and envelope object
Messaging.InboundEmail emailReply = new Messaging.InboundEmail();
Messaging.InboundEnvelope envReply = new Messaging.InboundEnvelope();

// Create the plainTextBody and fromAddress for the test
emailReply.plainTextBody = 'Here is my plainText body of the email';
emailReply.fromAddress ='some@email.com';
emailReply.Subject = 'Re: LogMeIn Pro Case:00001030 - testing reply';

productEmailCreateCase caseReplyObj = new productEmailCreateCase();
caseReplyObj.handleInboundEmail(emailReply, envReply);
}

public void sendDebugEmail(string emailbody, string emailtype){
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
String[] toAddresses = new String[] {'some@email.com'};
mail.setToAddresses(toAddresses);
mail.setSenderDisplayName('Salesforce Error Handling');
mail.setSubject('Error handling inbound email as a ' + emailtype);
mail.setPlainTextBody(emailbody);
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}

static testMethod void testCases3() {

// Create a new email and envelope object
Messaging.InboundEmail email = new Messaging.InboundEmail();
Messaging.InboundEnvelope env = new Messaging.InboundEnvelope();

// Create the plainTextBody and fromAddres for the test
string[] to = new List<string>();
to.add('logmeinfree@salesforce.com');
to.add('logmeinpro@salesforce.com');
to.add('logmeinitreach@salesforce.com');
to.add('logmeinrescue@salesforce.com');
to.add('logmeinbackup@salesforce.com');
to.add('logmeinhamachi@salesforce.com');
to.add('logmeinignition@salesforce.com');
to.add('remotelyanywhere@salesforce.com');
to.add('networkconsole@salesforce.com');
email.plainTextBody = 'This is my test body.';
email.fromAddress ='some@email.com';
email.subject = 'My test subject';
email.toaddresses = to;

productEmailCreateCase caseObj = new productEmailCreateCase();
caseObj.handleInboundEmail(email, env);

// Create a new email and envelope object
Messaging.InboundEmail emailReply = new Messaging.InboundEmail();
Messaging.InboundEnvelope envReply = new Messaging.InboundEnvelope();

// Create the plainTextBody and fromAddres for the test
emailReply.plainTextBody = ':::500700000059WZP:::';
emailReply.fromAddress ='some@email.com';
emailReply.Subject = 'Re: LogMeIn Pro Case:00001030 - testing reply';

productEmailCreateCase caseReplyObj = new productEmailCreateCase();
caseReplyObj.handleInboundEmail(emailReply, envReply);
}

static testMethod void testEmail(){
productEmailCreateCase obj = new productEmailCreateCase();
obj.sendDebugEmail('this is the body', 'new');
}
}

 
you can modify to suit your own needs, as there's 'some' custom stuff particular to our org.  this email class handles both new inbound and reply inbound emails.

a couple notes:
* you need to DELIVER emails to the SF address you associate with the email service.  you cannot just forward them.  this means, you need to be able to have your email server/service deliver to the address directly.  if you don't, you'll lose contact info, which would be mapped to the webname and web email fields.
* contact autocreation is not in this revision, as I use a custom s-control for this now.  i'm open to ideas on what exactly should be done on this front.  we currently create a contact, and map it to an account based on the domain part of the email address.  i'm not interested in coding a solution to handle person accounts.
* you need to modify the 'debugEmail' value to be the one you want errors sent to.  rather than relying on the debug logs of SF, i figured this would be an easier approach.
* code coverage is 90%.  the only code that isn't covered is the catch statement exception handling, as I didn't know how to force an exception at the time I wrote this.
* you need to add the following to the bottom of all your email templates, on a line of its own.
Code:
:::{!case.id}:::

 
i'm open to feedback of course.



Message Edited by paul-lmi on 03-10-2008 09:57 AM
franchisorfranchisor

Do you think that with some changes this could work for email to lead? If not, do you have or know of any solutions or codes to acheive email to lead?  I would be happy to find a basic email parsing tool (with out all the extra features) that would allow me to convert the email body to a csv file. Can you help?  :smileyhappy:

 

Thanks,

Franchisor

 

paul-lmipaul-lmi
it could definitely be modified to handle leads.  i'd actually suggest modifying the task example on the wiki though, as it's much less complex.
franchisorfranchisor
Thank you!
CMcCaulCMcCaul

Thanks very much for posting your code.  I'm sure it will be very helpful to a lot of us.

Has anyone figured out how to use the case thread id that SF automatically adds to the subject or body with the built-in Email2Case enabled?  I would really like to continue using the thread id to identify an existing case.  Using the thread id wouldn't require selecting a template when responding to an e-mail within SF.  Any ideas?

paul-lmipaul-lmi
i'm sure that's buried in the schema somewhere, but honestly, i didn't want to bother with truncating the subject line to fit it, as there's definitely a character limit, and some of our customer haven't figured out the concept that the body of an email goes in the body, not the subject line.  :)
Rasmus MenckeRasmus Mencke
In regards to the subject line. In salesforce the subject line field on a task has a max lenght of 80 chars, but the RFC allows subjects lines to 254 chars.

You might want to check the lenght before inserting and maybe truncate it, if longer than 80 chars.
paul-lmipaul-lmi
exactly the reason why i didn't want to put any tracking junk in the subject line.  thanks for the reaffirmation.
NathanSNathanS

Thanks for the Code!

 

Do you know how you would attach this inbound email to the case so that it will show up as a child record in the "Emails" section of the Cases page?

CMcCaulCMcCaul

Nathan,

You could start with this.  In our code, "rwCase" is the new or existing case to which the email is added and "newTask" is a new task that is created on the case and 'cText' is a string.

// Add a new Email message to the case
    EmailMessage[] newEmail = new EmailMessage[0];
    try {
        newEmail.add(new EmailMessage(FromAddress = email.fromaddress,
            FromName = email.fromname,
            ToAddress = email.toaddresses[0],
            Subject = email.subject,
            TextBody = email.plainTextBody,
            HtmlBody = email.htmlbody,
            Incoming = true,
            MessageDate = System.now(),    
            ParentId = rwCase[0].Id,
            ActivityId = newTask[0].Id));
        if ( email.ccAddresses!=null && email.ccAddresses.size() > 0){
            cText = '';
            for (i = 0 ; i < email.ccAddresses.size() ; i++) {
                cText = cText + email.ccAddresses[i]+';';
            }
            newEmail[0].CcAddress = cText;
        }
        if ( email.headers!=null && email.headers.size() > 0){
            cText = '';
            for (i = 0 ; i < email.headers.size() ; i++) {
                cText = cText + email.headers[i].name+' = '+email.headers[i].value+crlf;
            }
            newEmail[0].headers = cText;
        }
        insert newEmail;
        System.debug('New Email Object: ' + newEmail );
    }       
    catch (Exception e) {
    System.debug('\n\nError:: ' + e + '\n\n');
    string body = 'Error adding new email: ' + e.getMessage() + crlf + e.getCause() + crlf + crlf + 'To: ' + email.toaddresses[0] + crlf + 'From: ' + email.fromaddress + crlf + email.fromname + crlf + 'Subject: ' + email.subject + crlf + 'CaseId:' + caseThreadId;
    sendDebugEmail(body, emailtype);
    }

Good luck.

NathanSNathanS

Thanks a Million!

 

Do you know how to grab the attachments and sassociate them to the case?

 

Nathan

CMcCaulCMcCaul

We use the following to add the attachments to the case rather than the email message: (i is an integer)

// Add any binary attachments to the case
    if (email.binaryAttachments!=null && email.binaryAttachments.size() > 0) {
        for (i = 0 ; i < email.binaryAttachments.size() ; i++) {
            try {
                if (email.binaryAttachments[i].filename != null) {
                    Attachment newAttachment = new Attachment(ParentId = rwCase[0].Id,
                    Name = email.binaryAttachments[i].filename,
                    Body = email.binaryAttachments[i].body);
                    insert newAttachment;
                    System.debug('New Binary Attachment: ' + newAttachment );
                }
            }
            catch (Exception e) {
            System.debug('\n\nError:: ' + e + '\n\n');
            string body = 'Error adding binary attachment: ' + e.getMessage() + crlf + e.getCause() + crlf + crlf + 'To: ' + email.toaddresses[0] + crlf + 'From: ' + email.fromaddress + crlf + email.fromname + crlf + 'Subject: ' + email.subject + crlf + 'CaseId:' + caseThreadId ;
            sendDebugEmail(body, emailtype);
            }
        }
    }

// Add any text attachments to the case
    if (email.textAttachments!=null && email.textAttachments.size() > 0) {
        for (i = 0 ; i < email.textAttachments.size() ; i++) {
            try {
                if (email.textAttachments[i].filename != null) {
                    Attachment newAttachment = new Attachment(ParentId = rwCase[0].Id,
                    Name = email.textAttachments[i].filename,
                    Body = Blob.valueOf(email.textAttachments[i].body) );
                    insert newAttachment;
                    System.debug('New Text Attachment: ' + newAttachment );
                }
            }
            catch (Exception e) {
            System.debug('\n\nError:: ' + e + '\n\n');
            string body = 'Error adding text attachment: ' + e.getMessage() + crlf + e.getCause() + crlf + crlf + 'To: ' + email.toaddresses[0] + crlf + 'From: ' + email.fromaddress + crlf + email.fromname + crlf + 'Subject: ' + email.subject + crlf + 'CaseId:' + caseThreadId;
            sendDebugEmail(body, emailtype);
            }
        }
    }

NathanSNathanS
This has been a huge help.
 
Thanks Again!
 
Nathan
NathanSNathanS
Thanks again for the code snip.
 
I can't seem to envoke the code. Never passes the criteria in the If statement :
if (email.binaryAttachments!=null && email.binaryAttachments.size() > 0)
or
if (email.textAttachments!=null && email.textAttachments.size() > 0)
 
Error: Attempt to de-reference a null object -- email.binaryAttachments.size()
 
are email attachments stripped out on developer accounts?
 
NathanSNathanS

Had to turn on attachments for that email service..... just a flag in the GUI.

 

Thanks again.

FreeFree
Regarding the thread issue, I don't know whether this is the best way to do, but I wrote a piece of code which identifies and recreates the Id of the Case sent by Salesforce as reference in any part of the email.

    public static String RefId (String text)
    {
       
        Integer beg;
        Integer end;
        String ref ;
        String Zref = '';
        Integer refLenght ;
           
            beg = text.IndexOf('ref:');
            end = text.IndexOf(':ref');
            ref = text.substring(beg+ 13, end);
           
           
            refLenght = ref.length();
           
            for (integer i = 0 ; i < (15-refLenght) ; i++)
            {
                Zref = '0' + Zref;
            }

            ref = ref.substring(0, 4) + Zref + ref.substring(4);
       
        return ref;
    }

I then identify in the code if a such a 'ref:' and ':ref' string exists in the subject and/or email and call the static method sending the email subject and/or body.

I am not an expert developer so any improvement on this piece of code would be more than welcome (but for now it works fine)
GoForceGoGoForceGo
When the  EmailMessage object is created, it's status defaults to 'Sent'. The system won't let you set the status as 'New', since it is a Read Only pick list (as per the API documentation). One gets the message.

Message: Insert failed. First exception on row 0; first error: INVALID_OR_NULL_FOR_RESTRICTED_PICKLIST, Status: bad value for restricted picklist field: New: [Status] null.  If I don't attempt to set the status, it defaults to "Sent".

This is the same issue documented in this thread. Does anyone know how set the status as 'New'?



GoForceGoGoForceGo
Okay, I figured it out...Too bad it's not documented. Spent a whole day trying to figure this stuff out.

You can pass a Status = '0'.

'0' = New
'1' = Read
'2' = Replied
'3' = Sent
'4' = Forwarded...