Service and Support Blog

Service and Support Blog

Service and Support Blog - Best Practices

  • Email-to-Case Best Practices - Part 2: Things to Consider

    Marco Casalaina Nov 26, 2008

    Guest blogger and Customer Service & Support expert Peter Aubrey continues his thread on Email To Case.  Today we feature part 2 of the 2-part series Email-to-Case Best Practices (see part 1 here):

    In order to identify the best way to setup email-to-case, it’s best to get a good understanding of exactly how the agent works.  You will also need to make a decision on the communication strategy with your customers.  Some customer service departments allow agents to give out direct email addresses or direct dial numbers.  Others only allow customers to contact the agents via central support email or telephone numbers.  Some companies have a mixture of both.

    Email-to-case will of course only take email from specific mailboxes that are dedicated to receiving email related to customer support issues.  If a customer sends an email directly to one of your agents then the agent can manually add those emails into Salesforce using Force.com Connect for Microsoft Outlook.  Salesforce can be configured so that agents can send emails with a variety of From addresses, thus ensuring, for example, that all responses from customers arrive at the central mailbox which email-to-case is polling.

    E2c1 If the majority of the time you want your agents to send emails from a central support address, then each agent needs to modify his Email Settings by going to Setup: Personal Setup: Email: My Email Settings and setting his Email Name and Email Address to the required central support address.

    When an agent sends an email from a Case, he gets the choice of sending the email from a number of email addresses.  Salesforce will default to the settings specified by the agent in his email preferences, however he also gets the choice of setting the from email address on the email to either E2c2_4 their user record’s email address or any Routing Addresses that have been defined in your email-to-case settings.

    You can create a custom button that defaults the From address on the email page; for more on that, see this earlier blog entry: The Quick Email Button.

    How are emails linked together to form a conversation?
    When an agent sends an email from the Case page a special ID is created and added to the subject line of the mail so that a chain of mails can be uniquely identified as a conversation.

    However, this conversation ID is not created until an agent manually replies to the original email, an Auto-Response email does not include this unique ID.  Therefore if a customer responds to the Auto-Response mail then a new Case will be created since the unique ID is not included in the subject line of the email.

    This can be overcome in one of two ways.

    1.  Do Not Reply
    Some of our customers set the sender’s name and email address to “Do Not Reply” addresses, along with a warning in the response saying not to reply to this automated email.

    Note you could set up another email-to-case agent that monitors this email inbox to handle any emails are sent to the do not reply address.

    2.  Create the ID using a Formula Field
    The following formula field will calculate the thread ID, which is unique to each case.  This thread ID can then be added in to the Subject line or the body in your email template to make sure the ID is set even when an agent does not manually respond to the customer.

    Click here to see the formula that synthesizes the thread ID.  In the screenshot below, we've stored this formula in a custom field called "Case Communication ID" and added it to the subject line of our autoresponse template.

    E2c3 Note: if you use web-to-case then this technique can also be used to catch any immediate responses that customers may send to your support department, before an agent can respond.

    So in summary:

    1.    Customer sends an email to support@yourcompany.com

    2.    Email-to-Case agent picks up the email and:

    a.    Searches Salesforce to see if a Contact exists with an email that matches the from address of the inbound email.

    b.    If a match cannot be found, or if more than one Contact is found, then the name and address contained in the email are stored in the Web Name and Web Email fields on the Case when it is created.  If a match is found then a Case is created and linked to the Contact and its corresponding Account.

    3.    The appropriate Auto-Response Rule in Salesforce responds immediately with a message detailing Case Number, Subject, estimated response time, customer portal login link, suggested solutions, etc.

    4.    Assignment Rules evaluate the Case details and either places the Case into the correct queue or gives ownership to the correct individual.

    5.    The Case Queue members or the individual to which the Case was assigned are notified via email that they have a Case to work on.

    6.    Agent takes ownership of the Case and replies to the original email explaining that they will be working on their Case going forward.  Note that when the agent replies to the original email, the Open Task that was created is automatically closed.

  • Email-To-Case Best Practices -- Part 1: The Basics

    Marco Casalaina Nov 13, 2008

    People sometimes ask me whether Salesforce.com has an automated solution for turning emails into cases.  The answer is yes!  We've had one for years, and I'm not talking about Outlook Edition.

    Guest blogger and Salesforce.com Service & Support expert Peter Aubrey has written up a primer on Email To Case and what it can do for you.  Here we have it:

    Well, what a few days. Dreamforce was a fantastic experience where customers, partners and some of our most experienced employees shared ideas, best practices and generally showed how you can squeeze every dollar of ROI from your Salesforce implementation.

    One question that came up during the “Roundtable with the Service and Support Pros” session and one that comes up frequently in conversations I have with customers is what is email to case and how do I use it?  Well lets see if I can shed some light on this subject.

    What is email-to-case?

    Email-to-case is a small Java application that can run on any server, desktop or laptop PC.  It is used to connect to an email mailbox on a periodic basis, process any email messages that are currently there by taking the details of the email and creating a Case in Salesforce.  Think of it as like any email client application such as Outlook, Lotus Notes or Thunderbird, it just doesn’t need anyone to operate it!

    If a Contact in Salesforce has the same email address as that in the from address in the email, then the email-to-case agent will link the Case to the Contact automatically.

    If a Contact cannot be found then the details of the sending email are stored in Web Name and Web Email fields on the Case.

    What email servers and mail protocols does email-to-case work with?
    Email-to-case uses IMAP as the email protocol to communicate with your email server so it’s not a question of does email-to-case work with Microsoft Exchange, but does your mail server support the IMAP protocol.

    Exchange, Lotus Notes (Domino) and many other email servers are able to be configured to use the IMAP protocol, however this may not be enabled by default.  Also many web based email services such as Google support IMAP, which is what we’ll be using in our examples below.

    Can email-to-case use IMAPS?
    Yes. Simply state that the mail protocol as IMAPS and the port as 993 in the email2case.txt configuration file.

    How do I stop Spam?
    Email-to-case does not provide any SPAM checking.  It is assumed that customers will provide SPAM filtering capabilities before they reach the email-to-case agent.

    Where do I download and setup email-to-case?

    To download click here: http://wiki.apexdevnet.com/index.php/Members:Email_To_Case

    For instructions on how to setup see the Help files in Salesforce.com.

    Where do I get help and support?

    1. Search for "Setting Up Email-to-Case" in Salesforce.com's help files.
    2. Search for "email-to-case" in the Community discussion forums found here http://www.salesforce.com/community/community/
    3. http://wiki.apexdevnet.com/index.php/Members:Email_To_Case
    4.  If all else fails, you can contact Salesforce.com Support.


    Can I have more than one email-to-case agent running at the same time?

    Yes.  You can have as many agents running at once as you'd like, monitoring at most 50 email inboxes.

    How about some sample settings?
    Below are the settings I use for my email-to-case demonstrations.  There are two configuration files, email2case.txt and sfdcConfig.txt.

    email2case.txt

    <configFile>
        <server1>
            <url>imap.gmail.com</url>
            <protocol>imaps</protocol>
                <port>993</port>
            <userName>******@googlemail.com</userName>
            <password>******</password>
            <interval>1</interval>
            <inbox>inbox</inbox>
            <readbox>ReadMail</readbox>
            <errorbox>ErrorMail</errorbox>
        </server1>
        <server2>
            <url>imap.gmail.com</url>
            <protocol>imaps</protocol>
                <port>993</port>
            <userName>******donotreply@gmail.com</userName>
            <password>******</password>
            <interval>1</interval>
            <inbox>inbox</inbox>
            <readbox>ReadMail</readbox>
            <errorbox>ErrorMail</errorbox>
        </server2>
    </configFile>

    sfdcConfig.txt

    <configFile>
        <sfdcLogin>
            <url>https://www.salesforce.com/services/Soap/u/9.0</url>
            <userName>******</userName>
            <password>******</password>
            <loginRefresh>30</loginRefresh>
            <timeout>6000</timeout>
        </sfdcLogin>
        <notify>
              <notifyEmail>******@gmail.com</notifyEmail>
            <from>******@gmail.com</from>
            <host>smtp.gmail.com</host>
                <port>465</port>
            <user>******@gmail.com</user>
            <password>******</password>
            <service>com.sforce.mail.SMTPNotification</service>
        </notify>
        <attachments>
            <largeAttachmentDirectory>C:\\largeattach\</largeAttachmentDirectory>
            <largeAttachmentURLPrefix>file:\\YOUR_COMPUTER_NAME\largeattach\</largeAttachmentURLPrefix>
            <largeAttachmentSize>5</largeAttachmentSize>
        </attachments>
        <services>
            <com.sforce.mail.EmailService>C:\\EmailAgent\\email2case.txt</com.sforce.mail.EmailService>
        </services>
    </configFile>

    Thank you Peter!  Next week: Part 2: Things To Consider.

    -Marco Casalaina

  • How To Stop People From Making Comments On Closed Cases

    Marco Casalaina Oct 29, 2008

    Sometimes customers ask me how they can make closed cases "uncommentable" – once those cases are closed, they don't want their reps to be able to make new comments on them.

    Traditionally the way to accomplish this would be to change the record type of closed cases and use a layout for that record type which hides the Case Comments related list.  This is not ideal, though, because it means that you can't read the comments either.

    Fortunately, with Workflow From Case Comments, there is now a better way.

    As I mentioned in my previous blog entry, Workflow From Case Comments is a "chaining" workflow, which means that when it edits the parent case, it kicks off the workflow anew on that case object.  We can use this to our advantage in solving this problem.  Here's how.

    1.  Define a checkbox-type custom field on Case called "No more comments."  Do not add it to any of the Case page layouts.
    Validation1

    2.  Make a validation rule which shows the error "You cannot add comments to a closed case" at the top of the page.  In the formula section put simply "No_more_comments__c =true" (see first screenshot, left).

    3.  Make a Workflow on the Case Comments object which fires when Case: Closed equals True (see second screenshot).

    Validation2 4.  Make a Field Update in that workflow which sets the No More Comments field to true.

    5.  Activate the workflow rule!

    Now, when you try to submit a comment on a closed case, the workflow will fire on the case comment.  This will make the modification to the No More Comments field on the Case to set it to true, which will violate the validation rule and disallow the save, showing the error "You cannot add comments to a closed case." (see triumphant third screenshot)

    Validation3 Workflow From Case Comments to the rescue!

    -Marco Casalaina

  • Workflow From Case Comments!

    Marco Casalaina Oct 16, 2008

    I am pleased to report that the Winter '09 release includes a feature that customers have been asking about for years: Workflow From Case Comments!

    Shot1_3 Workflow From Case Comments gives you the ability to create a workflow rule on the Case Comments object.  From that rule, you can create a field update that takes effect on either the comment itself or on the parent case.

    This means that you can finally create those workflows you've been waiting for!

    For example, let's say I have a process whereby I set my case to a status of "Awaiting Customer Shot2 Response" when I'm waiting for the customer to get back to me.  Now my customer logs into the Customer Portal and adds a comment to the case.  That constitutes a response, so the system should automatically set my case back to a "Working" status.

    The screenshots shown here to the right document how I wrote a workflow that does just that.  All I have to do is write a workflow rule on Case Comment with the requisite criteria (and the criteria can refer to items from the Case as well).  Once that's done, I can make a field update that updates the status field on the case.

    As people begin creating workflows from Case Comments, they inevitably notice that only Field Update and Outbound Message are available from these workflow rules.  Then they ask me, what if I want to send an email?Shot3_3

    As with workflow from Email Messages, Workflow From Case Comments is a "chaining" workflow – it can trigger other workflow. Right now you can't put an email alert on the comment for the same reason you can't put a task on it: because the Comments object does not support Activities as children. However, you can put an email alert on a workflow on the parent case, and that workflow will get triggered and send out the email (and the email will be associated to the parent case, as you might expect).

    So, although you can't send an email directly from a Workflow From Case Comments, you can put an email alert on the parent case.  So, for example, you might make a Workflow From Case Comments that updates a "Last Comment" field on the parent case with the text of the most recent comment, and then make a workflow on Case which emails out that last comment to certain members of the Case Team.

    Case Teams!  A little foreshadowing there – I'll cover that new Winter '09 feature in my next dispatch.

    Don't forget, post any questions about this blog entry on the General board on developer.force.com so that the whole community can benefit from it.

    -Marco Casalaina

  • Adding A Link Button To Your CTI Adapter

    Marco Casalaina Oct 7, 2008

    Here's another question that CTI customers often ask me, particularly in the new age of Visualforce: "Can we add a button to the adapter that navigates to a link and passes it some information about the call?"

    Yes you can!  You can do it with a minor modification to your CTI adapter code.  Here's how.

    In my example, I've made an Apex page called "QuickCreate" that takes in as parameters the ANI (or caller ID) and the DNIS (the number that was dialed).  Now I'm going to make a button in the CTI adapter that links to this page, passing it the appropriate information for the active call on the line.

    In your .h file, add something like this:

    #define BUTTON_QUICK_CREATE 21

    In your Initialize() method, add this:

          
          CCTIButton pQCButton;
          pQCButton.SetLabel(L"QuickCreate");
          pQCButton.SetLongStyle(true);
          pQCButton.SetColor(COLOR_BEIGE);
          pQCButton.SetLinkURL(L"/001/e");
          UIAddButtonToAllLines(BUTTON_QUICK_CREATE,&pQCButton);

    Just after you call OnCallRinging, add this code (I left in my OnCallRinging code for context –  add everything from the CCTILine down):

          int nReturn = CCTIUserInterface::OnCallRinging(sCallObjectId, nCallType, true, bLogCall, mapInfoFields, mapAttachedData, nLineNumber);
          
          CCTILine* pLine = GetLine(nReturn);

          if (pLine) {
                CCTIButton* pQCButton = pLine->GetButton(BUTTON_QUICK_CREATE);
                if (pQCButton) {
                      pQCButton->SetLinkURL(L"/apex/QuickCreate?ANI="+mapInfoFields[KEY_ANI]+L"&DNIS="+mapInfoFields[KEY_DNIS]);
                      
                }
          }

    When the call is established, add the button to the line like this:

          listEnabledButtons.push_back(BUTTON_QUICK_CREATE);

    And that's all there is to it!  Now you'll have a button that shows up whenever a line is active which, when clicked, will navigate to your Visualforce page.

  • Kiva and the Quick Mass Close Button

    Marco Casalaina Oct 2, 2008

    From time to time in my job as product manager of Service & Support I come into contact with people who are using Salesforce.com to make a difference in the world.  One such company is Kiva

    Kiva is a microfinance non-profit that allows you to lend as little as $25 online to an entrepreneur in the developing world that needs a loan. On Kiva, you act as a banker to the poor by lending money to entrepreneurs in developing countries and then you get repaid.  Amazingly, in just 3 years, they've lent $44 million to 92,000 entrepreneurs, with an astonishing 98.5% repayment rate.

    Kiva is currently competing in the American Express Members Project, which will grant a grand prize of $1.5 million to the non-profit which garners the most votes from Amex cardmembers.  This money at Kiva could enable $30 million more loans to fund 60,000 more entrepreneurs so that they can lift themselves out of poverty.

    If you are an Amex cardmember, please click here to vote for Kiva – it really is a good cause.

    And now on to the meat of today's post:

    Kiva runs all of its customer service operations on Salesforce.com.  A few weeks ago they came to me with a request which I've heard before: they wanted a Mass Case Close button that skips the Case Close screen and just closes the cases.  This is just a twist on the Quick Case Close button that I have written about previously on this blog, so I was happy to oblige.

    The code is quite simple:

    {!REQUIRESCRIPT("/soap/ajax/13.0/connection.js")}

    var records = {!GETRECORDIDS($ObjectType.Case)};
    if (records[0] == null)
    {
    alert("Please select at least one case to close.")
    } else {
    //Get more info on the cases that were selected and generate a query out of it
    var updateRecords = [];

    //Iterate through the returned results
    for (var recordIndex=0;recordIndex<records.length;recordIndex++) {
    var caseUpdate = new sforce.SObject("Case");
    caseUpdate.Id = records[recordIndex];
    caseUpdate.Status = 'Closed';
    updateRecords.push(caseUpdate);
    }

    var result = sforce.connection.update(updateRecords);

    //handle errors here
    if (result.error) {
    alert('There was an error processing one or more cases');
    }

    //Reload the list view to show what he now owns
    parent.window.location.reload();
    }

    To add this code, go to Setup->Cases->Buttons and Links and make a new custom button called Mass Close.  Its Display Type should be set to List Button, its Behavior to Execute JavaScript, and its Content Source to OnClick JavaScript.  Paste the above code into the OnClick JavaScript field – but be sure to replace the caseUpdate.Status = 'Closed'; line with a status that is present in your org.

    Now you'll have to add this button to the Case list view.  To do
    this, go to Setup->Cases->Search Layouts.  Click Edit next to the
    Cases List View entry, and add your new button from the Available
    Buttons section to the Selected Buttons section.

    As with the Contention-Proof Accept Button, the trick here is that GETRECORDIDS call – it gets us the list of Case IDs that you selected from the list.  The rest here is simple, just put all those Case IDs into an array and set the case status fields to Closed, and call update.  Easy!

    Just imagine what other sorts of mass actions you could do with this method.  It's hard to fathom the creativity of the readers of this blog.

    And just in case you missed it: Vote for Kiva! :)

  • The Contention-Proof Case Accept Button

    Marco Casalaina Aug 21, 2008

    The case accept button is a handy thing.  It shows up on queue views to allow members of that queue to mass-accept cases (actually, in the Agent Console it shows up in the Mass Action dropdown rather than as a button, but it works the same).

    If you have a large queue, though, with lots of agents accepting cases at the same time, they may sometimes step on each others' toes.  If two agents accept the same cases at almost the same time, then the last one will win, and the first agent will not actually own the cases he thinks he just accepted.

    Here's some custom button code I wrote which addresses this issue.  It only accepts cases that are still assigned to a queue; if the cases have been accepted by a user already, those cases will remain owned by the users who accepted them.

    To add this code, go to Setup->Cases->Buttons and Links and make a new custom button called Accept Cases (or whatever label you'd like to use for this).  Its Display Type should be set to List Button, its Behavior to Execute JavaScript, and its Content Source to OnClick JavaScript.  Paste the code from this link into the OnClick JavaScript field.

    Now you'll have to add this button to the Case list view.  To do this, go to Setup->Cases->Search Layouts.  Click Edit next to the Cases List View entry, and add your new button from the Available Buttons section to the Selected Buttons section.

    So how did I do this?

    Quite easily really.  Perhaps the most important part is the line that invokes {!GETRECORDIDS($ObjectType.Case)}. This little-known merge field allows you to get a list of all the IDs that are selected in a list view or related list – really handy when you're trying to make buttons that act upon a large number of records.

    Once I have the IDs of the selected records, I run them through the also little-known retrieve function to get more information about them.  The key to that retrieve call is getting Owner.Type – that will return a string for each case containing either "Queue" or "User" depending on what the type of each case's owner is.  A good lesson here is that GETRECORDIDS plus retrieve makes for a quick and powerful combination.

    Now I divide the cases into two piles: the "accept" pile for cases that are currently owned by a queue that we're going to allow this user to accept, and the "reject" pile of cases that have already been accepted by other users.  If there are any cases in the "accept" pile I call update on it to update those cases, and then I give some feedback to the user of the changes that were made (and the changes that were rejected).

    And there we have it: the contention-proof case accept button.  Note that with just a couple of minor changes you can adapt this same code for use with leads and custom objects – any object that is ownable by a queue.

     

  • An Even Better Way To Pop Visualforce Pages From CTI

    Marco Casalaina Aug 14, 2008

    In a previous blog entry I posted a means of popping a Visualforce page from a CTI adapter.  That method was somewhat complicated and required an adapter writer to modify some of the core CTI Toolkit library code.

    Digging into it a little further, I recently came up with a much simpler way to pop a Visualforce page from CTI.  There is one caveat here:  using this method, your CTI adapter will always pop this Visualforce page – it won't try to do any other kind of search.  As long as you're OK with having your Visualforce page do any searching required, then this method is for you.  Here's how to do it.

    1. Make a Visualforce page that takes in ANI and a DNIS as parameters on the URL.  You may also want to make it take in other parameters that might be attached to the call.
    2. Whenever you call CCTIUserInterface::OnCallRinging to set the adapter to its "call ringing" mode, set the third parameter to false. This disables the native adapter search mechanism.
    3. After every call to OnCallRinging add this code (replacing the "YourPageHere" with the actual name of your page):
    CCTIParty* pParty = pLine->GetPartyByANI(mapInfoFields[KEY_ANI]);
    if (pParty) {
    RelObjSetList relObjs;
    CCTIRelatedObjectSet* pSet =
    new CCTIRelatedObjectSet(L"Visualforce",L"Caller Info",L"Caller Info");
    pSet->SetVisible(true);
    relObjs.push_back(pSet);

    std::wstring apexUrl = L"apex/YourPageHere?";
    apexUrl += L"ANI=" + mapInfoFields[KEY_ANI];
    apexUrl += L"&DNIS=" + mapInfoFields[KEY_DNIS];

    CCTIRelatedObject* pObject = new CCTIRelatedObject(apexUrl,L"Click To View");
    pObject->SetVisible(true);
    pSet->AddRelatedObject(pObject);

    pParty->AddRelatedObjectSets(relObjs);
    }
    UIRefresh();

    This code verbatim added to your adapter just after you call OnCallRinging will cause it to pop to the Visualforce page.  The Visualforce page can figure out what it should search on based on the ANI and DNIS that's passed in via the URL.

    To take this a step further, you can also append all the members of mapAttachedData to the URL, which will enable you to pass any attached data (say, caller entered digits in the IVR) to your Visualforce page.

    One more thing, though: usually, when the CTI adapter pops an object, it tries to add that object to the call log.  However, a Visualforce page cannot be added to the call log, and if you try to add one, it will cause the call log to fail upon saving.  So you'll want to override the function that adds objects to the call log to ignore Visualforce pages.  You can do so by overriding UIAddLogObject in your subclass of CCTIUserInterface, like this (replacing the CDemoUserInterface with the name of your subclass, of course):

    void CDemoUserInterface::UIAddLogObject(PARAM_MAP& parameters) {
          std::wstring sId = parameters[KEY_ID];
          if (sId.find(L"apex") == std::wstring::npos) {
                CCTIUserInterface::UIAddLogObject(parameters);
          }
    }

    Obviously you'll have to add a corresponding method header to your .h file also.

    To genericize this, you might consider adding a parameter to your Call Center Definition File (that XML file you distribute to your customers) that specifies the Visualforce page to pop to.  If the customer specifies this page in the setup, then always pop to it; otherwise take the normal route of searching for a Salesforce.com object and popping what you find.

    And by the way:

    Let's say you want your Visualforce page to add an object to the log.  As I mentioned earlier you can't put the Visualforce page itself in the log, but you can send a message to the CTI adapter to have it add an object to the log.

    All you have to do is have your Visualforce page call the following Javascript function:

    sendCTIMessage('ADD_LOG_OBJECT?ID='+entityId+'&amp;OBJECT_LABEL=' + escape(entityLabel)+'&amp;ENTITY_NAME='+entityDevName+'&amp;OBJECT_NAME=' + escape(entityName));

    where:
    entityId = the Salesforce.com ID of the object you want to add to the log
    entityLabel = the name of the object you want to add to the log (like "Lead" or "My Custom Object")
    entityDevName = the API name of the object you're adding (like "Lead" or "My_Custom_Object__c")

     

  • Case Age In Business Hours

    Marco Casalaina Aug 7, 2008

    I am pleased to announce that we have released an AppExchange application called Case Age In Business Hours (click link for the AppExchange listing) under the Salesforce Labs banner.  In this post I'll explain what it does, how it does it, and how you can modify the code if you so desire to make it do more.

    So first, what does it do?  More than its name suggests, actually.  First, and most obviously, it calculates the Case Age In Business Hours for your closed cases.  Fancy that.  It also has another useful function, though: it includes two more fields called Time With Support and Time With Customer that distinguish between time spent by your support team and time spent awaiting the customer.  Note that these new fields will only be accurate for closed cases -- I'll explain why in a bit.

    It sounds like it might be complex, doesn't it?  A few years ago, back in my full-time developer days, I wrote some code that calculated time differences in business hours, taking into account time zone differences, daylight savings, and all that jazz.  It was hundreds and hundreds of lines of code, and it took me days to get it right.  My pinkies still twitch at the thought of it.

    Thankfully, some of the capabilities we recently added to the Force.com platform make the Case Age In Business Hours package a cinch.  It does everything my old hunk of code did and more, but if you look at the trigger which forms the basis of this package, there are only 55 lines of code, and half the lines are comments or whitespace.

    What made it all possible are the BusinessHours methods in Apex, particularly BusinessHours.diff.  You pass BusinessHours.diff two times and a set of Business Hours (and you can define as many sets of Business Hours as you want at Setup->Company Info->Business Hours) -- and it spits back at you the time difference in business hours!  It does all the time zone translation and daylight savings nastiness for you, and all you have to do is call the method.  That's it!

    Now for the interesting part, though: how about those Time With Support and Time With Customer fields?  How do we figure that out?

    Well, if you install the package, you'll notice that it also installs a custom object and tab called "Stop Statuses."  To set it up, you access this tab and add the names of the case statuses you'd use when you're waiting for your customer.  Some people have "Awaiting Customer Response" or "Awaiting Customer Validation" -- some just have "On Hold."  Whatever your statuses may be, add them all in the Stop Statuses section.  Then, whatever time your case spends in one of those statuses will be tallied to the Time With Customer field -- and any time your case spends not in a stop status (and not in a closed status, of course) will be added to to the Time With Support field.

    Now what if you want to take it to the next level -- let's say you want to add a bucket and measure Time With Tier 1, Time With Tier 2, and Time With Customer?  Well, that wouldn't be so hard -- you'd just have to factor in the owner as well as the status when deciding what times go in what buckets.

    Before we begin, remember that because the Business Hours functionality is new, this Apex will only compile against the 13.0 API and higher (13.0 being the latest as of the Summer '08 release).  If you haven't got the latest Force.com IDE plugin, now would be a wonderful time to download it.  It makes Apex development much easier.

    First, we'll have to add some fields to Case to store this bucket. Let's call them Time With Tier 1 and Time With Tier 2 (with corresponding API names Time_With_Tier_1__c and Time_With_Tier_2__c respectively).

    Now we'll modify the trigger a little to accommodate these new buckets.  First, we'll need to figure out if the current user is a Tier 1 or a Tier 2 agent.  Let's assume (for the sake of simplicity) that we have two queues, Tier 1 and Tier 2, and that the current case owner will either be in one or the other.  It is admittedly a simplistic example, but this is a blog post, not a book.

    All you really need to do is add some code after this line:

    Double timeSinceLastStatus = BusinessHours.diff(hoursToUse, updatedCase.Last_Status_Change__c, System.now())/3600000.0;

    That's the code that actually figures out how many business hours have passed since the last status change.  At this point, you have the time you need to add to a bucket -- now all you have to do is find the right bucket to add it to.

    In the package, the bucketing code is pretty simple -- either the case spent the time since the last status change in a stop status, or it didn't:

    if (stopStatusSet.contains(oldCase.Status)) {
        updatedCase.Time_With_Customer__c += timeSinceLastStatus;
    } else {
        updatedCase.Time_With_Support__c += timeSinceLastStatus;
    }

    We'll have to modify this statement to figure out whether the case owner is in tier 1, and then assign the time to the proper bucket.  First, let's find out if the owner in tier 1:

    Boolean ownerIsTier1 = false;
    //Search the Tier 1 queue for this user
    GroupMember groupMember = [Select Id from GroupMember g where g.Group.Name='Tier 1' and
                               g.UserOrGroupId=:updatedCase.OwnerId];
    if (groupMember != null || updatedCase.Owner.Name == 'Tier 1') {
        //We found the user in the Tier 1 queue!
        ownerIsTier1 = true;
    }

    The variable ownerIsTier1 will now be true if either the owner was found in the Tier 1 queue or if the owner is the Tier 1 queue itself.  Now that we've figured that out, we can put the time in the right bucket:

    //We decide which bucket to add it to based on whether it was in a stop status before
    if (stopStatusSet.contains(oldCase.Status)) {
        //This still goes in the customer bucket!
        updatedCase.Time_With_Customer__c += timeSinceLastStatus;
    } else {
        //This is time with support -- let's see which tier's bucket we put it in
        if (ownerIsTier1) {
            updatedCase.Time_With_Tier_1__c += timeSinceLastStatus;
        } else {
            updatedCase.Time_With_Tier_2__c += timeSinceLastStatus;
        }
    }

    Finally, we change the Case Age In Business Hours total code a little:

    if (closedStatusSet.contains(updatedCase.Status)) {
        updatedCase.Case_Age_In_Business_Hours__c =
        updatedCase.Time_With_Customer__c +
        updatedCase.Time_With_Tier_1__c +
        updatedCase.Time_With_Tier_2__c;
    }

    And there we have it.  Extending this example, you can calculate business hours times for all kinds of buckets.  Just imagine the reporting possibilities?  I can hear call center managers around the globe rubbing their hands together in glee.

    Here's the finished trigger modified per the example given here.  I'll leave it to you to write the test cases for it.

    A little earlier I noted that these fields are only accurate for closed cases.  Why do you suppose that is?

    Back in high school, my creative writing teacher used to say that if you want to hold your reader's attention, you have to leave an unresolved mystery until the end.  Well, if you're still reading this, I guess it worked -- thanks Mrs. T! 

    The answer to today's unresolved mystery is that these fields don't measure the difference between the time the case was opened and now -- they measure the difference between the time the case was opened and the last time the case status changed.  That's because only a change to the case can trigger the Apex which updates these fields, and I deemed a status change the most appropriate change to perform this sort of recalculation.  Once the case is closed, these fields are frozen -- even if you change it from one closed state to another, they won't budge (if you reopen the case, on the other hand, they'll start tallying again).  The fact of the matter is, then, that only once the case is closed will you have a final tally of Time With Customer, Time With Support, and Case Age In Business Hours.

  • The Quick View Tab

    Marco Casalaina Jul 22, 2008

    Lots of customers would love to have a tab that just opens directly to a view of their choice.  Unfortunately, standard object tabs can only open the relevant standard overview page, which shows a list of recently viewed cases -- that page can't be preset to open a certain view as soon as you hit the tab.  However, you can create a custom tab which will instantly redirect itself to the view you'd like.  Here's how.

    The most common request I get is to create a tab for views on Case, so that's the example I'll give here.  Of course, this technique can be used to create a tab for any kind of view for any object.

    First, navigate to the view you'd like to tab-ify by going to the overview page and selecting that view.  In my case, I'll go to the Cases tab and click on the Open Cases view, which is a view I created that shows all the open cases in my org.

    Save the URL of this view; you'll need it later.  It will look something like

    https://na1.salesforce.com/500?fcf=00B300000031l4H

    Now go to Setup->Customize->Create->Tabs.  Click the New button under Web Tabs.  Set it to Full Page Width.  Set the Tab Type to URL, and give it a label that makes sense (I called mine "Open Cases" but you can call yours "Cases" if you intend to hide the normal case tab -- just make sure you don't end up with 2 tabs labelled "Cases").  Pick any Tab Style -- it's going to redirect to the Case tab anyway so this doesn't matter.

    On the next page of the wizard, enter the following in the Button or Link URL:

    javascript:parent.navigateToUrl('<your url here>');

    Don't forget those single-quotes around the URL.  So in my example it looks like this:

    javascript:parent.navigateToUrl('https://na1.salesforce.com/500?fcf=00B300000031l4H');

    And voila!  You now have a tab that navigates directly to your view.

    Bear in mind that a quick JavaScript navigation of this sort breaks the Back button minorly -- if you click this tab and then hit the back button, you'll get bounced right back to your view.  You have to click the Back button twice in rapid succession to get back where you started.  Consider yourself warned!

  • The Quick Email Button

    Marco Casalaina Jul 16, 2008

    …or, mastering the black art of URL hacking the email page.

    Sending an email from Salesforce.com can be a clicky operation.  You dig around for the Send An Email button, click it, click Select Template, find your template and click it, maybe type some more stuff in the email, and finally click Send.  That's a lot of clicks, especially for organizations where they just use the same template over and over.

    Well, the good news is that you can make this a much less clicky operation by making a custom button which changes the URL parameters.  Technically URL-hacking is not supported by Salesforce.com, and it's technically not a supported API, so be forewarned -- but when you're on a quest to reduce clicks, you gotta do what you gotta do.

    So let's say you have a template that you usually use for Cases which contains some pleasantries and then a merge field that adds in the text of any related Solutions.  If you've not used Email Templates much before, by the way, have a look at the sample template entitled "SUPPORT: Case Response with Solution (SAMPLE)" --  it does just what I've described here.

    Anyway, what you'll want to do is make a custom button that preselects this template.  It's quite simple really.

    The first thing you'll need to do is get the ID of the template you want to use.  This can be found by finding the template via Setup->Communication Templates->Email Templates.  Click on it and look in the URL -- you'll see an ID up there that looks like 00X30000000rNM0 (which may be followed by a ? with some other parameters -- ignore that stuff, just take the 15-character ID that starts with 00X).  Copy that ID into Notepad or someplace where you can get to it later.

    Now go to Case and create a Custom Button -- let's call it Email Solution.  Set its Behavior to Execute Javascript and its Display Type to Detail Page Button.

    In the Javascript area add this line of code, replacing the <your template here> with the ID of the template you saved earlier:

    location.replace('/email/author/emailauthor.jsp?retURL=/{!Case.Id}&p3_lkid={!Case.Id}&rtype=003&p2_lkid={!Case.ContactId}&template_id=<your template here>');

    Now add this custom button to your Case page layout, and voila, you have a one-click button that will bring up your email complete with template.

    But wait, there's more!

    To this we can actually add a few more bells and whistles.

    By default the Send An Email page likes to BCC the sender.  Many call center agents I talk to find this rather annoying.  With the addition of another URL parameter to our custom button, we can stop it from BCCing anyone.  The parameter is p5, so our new code would be:

    location.replace('/email/author/emailauthor.jsp?retURL=/{!Case.Id}&p3_lkid={!Case.Id}&rtype=003&p2_lkid={!Case.ContactId}&template_id=<your template here>&p5=');

    Notice at the end there the "&p5=" -- that sets the BCC area to empty.

    But what if you just want to send the email in one click with no edits?

    Well, you can do that too, with -- you guessed it -- one more URL parameter: the save parameter.

    If I take the above code and add a save=1 to it, then when I click the button, the email will just be sent, and the Case page will refresh itself to indicate that an email was sent:

    location.replace('/email/author/emailauthor.jsp?retURL=/{!Case.Id}&p3_lkid={!Case.Id}&rtype=003&p2_lkid={!Case.ContactId}&template_id=<your template here>&p5=&save=1');

    And what if you want to change the From address?

    You may have noticed that if you have Email To Case enabled with some routing addresses, or if you've modified the email address in the My Email Settings page in the Personal Information area of Setup, that when you go to send an email you have a choice of From addresses.  You can default this From address too!

    The From address is just another parameter in the URL -- it's p26.  So taking our earlier example, let's add a From address default:

    location.replace('/email/author/emailauthor.jsp?retURL=/{!Case.Id}&p3_lkid={!Case.Id}&rtype=003&p2_lkid={!Case.ContactId}&template_id=<your template here>&p26=me@mydomain.com');

    Note that the p26 default will only work for items that are actually in that From address list.

    And that ends today's lesson on email buttons.  Happy emailing!

  • The Quick Case Close Button

    Marco Casalaina Jul 9, 2008

    People often ask me how they can get around the Close Case page.  One option to do this is to replace the standard Close Case button with a custom button of your own.  It's remarkably easy to do!  Here's how.

    First, go to Setup->Cases->Buttons and Links and make a new custom button called Close Case.  Its Display Type should be set to Detail Page Button, its Behavior to Execute JavaScript, and its Content Source to OnClick JavaScript.

    Now you need only paste the following JavaScript in there:

    {!REQUIRESCRIPT("/soap/ajax/13.0/connection.js")}

    var caseObj = new sforce.SObject("Case");
    caseObj.Id = '{!Case.Id}';
    caseObj.Status = 'Closed';
    var result = sforce.connection.update([caseObj]);

    if (result[0].success=='false') {
         alert(result[0].errors.message);
    } else {
         location.reload(true);
    }

    This is assuming, of course, that you actually have a status called "Closed" -- but if you don't, don't despair -- just change the caseObj.Status = 'Closed'; line to a closed status that actually exists in your case status list.

    Now edit your Case page layout.  Click on the Detail Page Buttons box and it will be highlighted, then click the Edit Properties button.  Here you'll be able to hide the standard Close Case button and show your new custom Close Case button.

    Finally test your new button by making a test case and closing it.  Voila!  The button sets the case to a closed status and reloads the page to give you feedback that in fact it was closed!

    (please note that because this trick uses the API, it will only work for Enterprise Edition and up, or for Professional Edition with the API add-on)

  • Proving you have A+ Customer Service

    Ainsley Tai May 1, 2007

    Everybody claims that they have outstanding customer service. But how do you convince your customer of this? This all boils down to a question of credibility: you can say you care about your customer, but will they believe it?

    We at salesforce ran into a similar scenario before. We tell people that our on-demand service is extremely reliable, and our customers said "prove it". And so we did. We put up trust.salesforce.com, where the entire world can keep track of our uptime status. We are so confident of our reliability that we opened up this data to the public.

    What's the customer service equivalent of this? Well, I was shopping on this site called joggingstrollers.com when I saw this at the bottom of the page:

    Cust_stat_2








    Imagine that, publishing your contact center stats! That is instant credibility.

  • Customer loyalty through the blogging community

    Ainsley Tai Mar 24, 2007

    The blogging community is a key constituent of any company. Dissatisfaction from one blogger creates ripples throughout your online community... similarly, praise on  great customer service is a wonderful proofpoint that can be used to further build your community of loyal customers. As a Service Executive, how do you monitor this effectively?

    John Ragsdale of the SSPA blogs about Visible Technologies, a company that offers blog monitoring technology that according to him:

    • Identifies who the bloggers/posters are
    • Shows relationships between bloggers so you know their sphere of influence
    • Rates postings as positive or negative to help pinpoint where action is required

    I haven't seen their product first hand myself, but this sounds interesting enough that I may start looking more into it.

  • Beyond Cost per Minute

    Ainsley Tai Mar 9, 2007

    A lot of companies are moving beyond viewing their contact centers as cost centers, and are renewing their commitment to customer loyalty. But how do you measure this? Cents per minute is easy to do, but as we all know, that is the wrong indicator for great customer service. In a previous life, I worked with a company that had their agents ask "Are you satisfied with this call" and measure this percentage. While this is an improvement over obsessing on pennies, it does limit your measurement to individual calls.

    Robert Cichon offers three great metrics to keep track of in a recent post:

    How many times a day do you get customer testimonials about how good your support staff, billing group, customer service or sales teams did?

    How many of your customers are referred to you just simply because of Customer Service? Are you asking this question of your new customers?

    If you have a forum up, how many times do your customers back you there from a quality and customer service perspective?

    Anybody else have great ideas on how to measure customer satisfaction or customer loyalty?

  • A Happy Agent is a Happy Customer

    Ainsley Tai Mar 7, 2007

    SearchCRM.com highlighted the Strativity Group's recent Customer Experience Management Survey, which talks about the link between employee loyalty and customer loyalty. This quote caught my eye:

    "In today's world of the customer experience, we are depending more and more on employees' attitude as a form of differentiation -- products and services cannot carry the weight of differentiation anymore... Our dependence on employees is growing, at the same time we are seeing a decline in employees' commitment to employers."

    All the money spent on marketing and brand-building can go down the drain with one bad phone call. Your contact center is the front line in winning over your customer's loyalty. What are you doing in your organization to empower your employees? Are you doing everything you can to make your agents successful? Are you providing them with the tools they need to perform their job well?

    How are you turning your agents into brand ambassadors?

  • Motivating Call Center Agents

    Ainsley Tai Mar 5, 2007

    Maria Palma from the blog CustomersAreAlways flagged a Harvard Business School article that gives advice on how managers can motivate their employees.

    I find the opening sentence provocative, and quite true:

    Most companies have it all wrong. They don't have to motivate their employees. They have to stop demotivating them.

    Studies after studies have shown that a happy agent is a happy customer. How are you ensuring that your agents remain highly motivated? What's your key takeaway from this HBS article?

  • The Gmail customer service rep

    Ainsley Tai Mar 1, 2007

    Who's the Gmail customer service rep?

    I found out when I recently started to hit my storage limits in Gmail (incredibly enough). I went to their Help page to figure out what I could do about it.

    When I get to their main help page, my eye immediately goes to the upper right hand corner of the screen, the "Search Help Center". I type in "exceeding storage":

    Gmail1

    The search results are divided into two portions, the upper portion is based on Google's knowledge base, and the lower portion are the search results from their customer forum:

    Gmail2_1

    I tend to prefer customer forums myself, so I click on the first thread there, which leads me to this page giving advice on how to free up storage space. I follow the instructions, and voila! I have more space.

    So notice that I:

    1) solved my issue
    2) within 1 session
    3) in less than 3 minutes

    Does this make me a bigger fan of Gmail? You betcha. My issue was solved almost instantaneously... I didn't have to wait on the phone or get transferred multiple times to different people who sometimes might not be equipped to solve my problem.

    So who is the Gmail customer service rep? Well, no one. Or, all of us. The Gmail community. That's the power of community-based customer service.

  • Creating fanatical customers

    Ainsley Tai Mar 1, 2007

    In a recent post, Joel Spolsky lists "Seven steps to remarkable customer service", based on his experience in providing customer service at his company. This particular line caught my eye:

    But when someone does call, look at it as a great opportunity to create fanatically devoted customer, one who will prattle on and on about what a great job you did.

    Think about it: how many times have you praised a company because of the great customer service you received? And what's the likelihood that you'll keep on going back to that company?

  • Implement the Self Service Portal using Single Sign On

    Eric Baird Aug 18, 2006

    Ssp_with_sso_4This is the high level approach for implementing single sign on using the customer self service portal embedded within an existing customer portal.  For more information on the general set up of the Self Service Portal, reference the Self-Service Implementation Guide. There are also tip sheets and online help topics to explain how to use Self-Service. Click Help & Training in the Salesforce.com user interface to access these resources.

    Download self_service_portal_with_sso.doc

  • New Service and Support Process Map

    Jamie Grenney Jul 14, 2006

    Ssmap_2 Today we published a new process map for Salesforce Service and Support.


    The map illustrates how support requests flow through the app from capture through to case resolution.


    This is a brand new document, so let us know what you think by adding comments below.