37

In Salseforce governor limits, it states that every org is allowed to send single emails to 1000 external addresses every 24 hours. On very rare occasions, an org I work with has exceeded this limit through Apex emails on a particular busy day. Exceeding the limit prevents them from sending emails until more became available.

I appreciate that Salesforce is not a mass emailing program. However on the rare occasions when the limit is exceeded in this way, I would like there to be a way to capture exceeding this limit and redirect the emails back to the current user with an internal note instructing them to direct the messages manually (there is no limit on internal email).

Salesforce themselves suggest using the Messaging.reserveSingleEmailCapacity(count) method to determine to see if the org has the capacity to send the emails. However, this method is next to useless for this because if the emails are not available, an uncatchable system.limit exception is fired. So instead of allowing the email process to run which would cause an un-catchable exception, we can simply have this happen earlier, doesn't really help!

Does anyone have an idea about how to capture this limit break? I have heard one potential solution that involves a scheduled job updating a global setting with the latest time the job successfully ran, then using another job to read this value and react accordingly. I could do this, but given the rare occurrence of the limit exception, I would prefer not to use regular scheduled jobs in this manner.

23

Update 26th Oct: See below for a second version following comments below

I have just promoted this idea as well, I would never have imagined it to be a good design for this method to have it throw an unhandled exception, IMHO that is!

Anyway, since we are being inventive here, the following also seems to do the trick. But be aware much like the custom setting counting trick, it does not actually reserve anything for the subsequent sending of emails, as the method call is made in another VF context.

<apex:page controller="TestMessagingLimitController" action="{!check}"/>

public with sharing class TestMessagingLimitController 
{
    public PageReference check()
    {
        Integer amount = Integer.valueOf(ApexPages.currentPage().getParameters().get('amount'));
        Messaging.reserveSingleEmailCapacity(amount);       
        return null;
    }
}

You can then do this...

try
{
    PageReference checkMessages = Page.testmessaginglimit;
    checkMessages.getParameters().put('amount', '1000');
    checkMessages.getContent();
    // Success (note messages will not be reserved however)
}
catch (Exception e)
{
    // Failure
    System.debug(e.getMessage());
}

Implementation Note: If you wrap the above in your own helper method, e.g.

public static boolean checkSingleEmailCapacity(Integer amount)

You can then easily switch out this implementation with a try/catch once Salesforce allows us to catch these exceptions or provides an alternative as per the idea exchange posting.

Hope this helps!

Update: Apex REST Approach

Here is a further approach that is using a Http callout. I've left the above approach in my answer, as it has the benefit of not needing a remote site enabled, global class etc. So please make your choice! In the end if you follow the abstraction I recommended above and only call the helper method you can change your mind swiftly in the future.

@RestResource(urlMapping='/MessageLimit')
global with sharing class MessageLimit 
{
    @HttpGet
    global static void doGet() 
    {
        RestRequest req = RestContext.request;
        Integer amount = Integer.valueOf(req.params.get('amount'));
        Messaging.reserveSingleEmailCapacity(amount);               
    }

    public static boolean checkSingleEmailCapacity(Integer amount)
    {   
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        req.setEndpoint(URL.getSalesforceBaseUrl().toExternalForm() + '/services/apexrest/MessageLimit?amount=' + amount);
        req.setMethod('GET');
        req.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId());
        HttpResponse res = h.send(req);
        if(res.getStatusCode() == 500) // May want to actually check the body message to be 100% sure
            return false;
        return true;
    }
}    

Thus you can now do this!

if(MessageLimit.checkSingleEmailCapacity(1001))
    System.debug('Good to go!');
else 
    System.debug('No go for launch!');              

Enjoy!

  • Nice one Andrew, some good out of the box thinking there, like it. However, to further complicate matters, some of the messages are generated via scheduled jobs, which cannot use the getContent() method, any thoughts? – Christopher Alun Lewis Oct 26 '12 at 10:11
  • I think for that you could take a similar approach, but use a custom Apex REST method and make a Http callout. I looked into making Http callouts to VF pages here, gist.github.com/3686866. But it tripped my stability radar! If I have more time later this evening I'll update the answer with an Apex REST approach unless someone contributes one sooner. – Andrew Fawcett Oct 26 '12 at 10:46
  • Here you go Christopher hope this hits the spot and answers your question. Enjoy! – Andrew Fawcett Oct 26 '12 at 22:48
  • Great stuff Andrew on the REST call! I believe this is the most suitable solution for the problem while Salesforce come up with a way to actually capture this limit exception. Thanks again for taking the time to put this together. – Christopher Alun Lewis Oct 31 '12 at 9:55
  • Your very welcome! :-) – Andrew Fawcett Oct 31 '12 at 10:05
2

I agree, it's a huge inconvenience.

My colleague has decided to use custom setting with count of emails sent so far from Apex (there's a scheduled job to reset it overnight). Definitely not an elegant solution, you need to hunt all Messaging-related code and wrap it in your own class or add a line that would increment the counter...

Feel free to upvote his idea ;) https://sites.secure.force.com/success/ideaView?id=08730000000aSLLAA2

  • Thanks for the feedback and putting the idea link on there, I forgot about that. I have already voted it up, you can see my rant of a comment on there :D – Christopher Alun Lewis Oct 26 '12 at 9:31
  • Heh, I'm blind :D We need to occasionally contact LOTS of our customers when some important cable is cut and they lose service. Combine it with periodic mailing activities and around beginning of the month management is nervous when anything bad happens. Ridiculous situation really. Heh, I don't know. Http callout to your own webservice that tries to reserve the capacity and if it fails you'll get catchable SOAP exception back? Delegation of mail sending to something on AppExchange? – eyescream Oct 26 '12 at 9:46
  • The only problem with your current solution is that it is a 24 hour rolling limit. if you send 600 at 11:00pm , reset the limit, and then send 600 at 08:00am the next morning, you will break the limit, but not by your control method. – Christopher Alun Lewis Oct 26 '12 at 10:57
  • Actually, the limit resets at midnight GMT. This also means you have to schedule your job for midnight GMT otherwise, you won't know where you actually stand in relation to the limit. You could send 999 emails at 11:59 pm GMT and another 999 at 12:01 am GMT technically. – DerekLansing Oct 27 '12 at 0:02
0

Now that we can catch the exception thrown by Messaging.reserveSingleEmailCapacity() so to know if the email limit has exceeded we can do this in the class..

public boolean limitErr;
public void sendMail()
{
   limitErr = false;
    try
    {
        Messaging.reserveSingleEmailCapacity(1);
    }
    catch(Exception e)
    {
        mssg = 'Mail limit for today exceeded!';
        limitErr = true;
    }

    If(!limitErr)
    {
    //send mail
    }
    else
        System.debug(mssg);
}

This is just a sample and you can modify this according to your needs and please upvote this answer if it helps.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.