Since it turns out System.AsyncException is uncatchable (just tested it) there isn't much you can do to either predict when this is going to happen or resolve it outside of editing the conflicting code.
In short: there's not really a solution other than requiring customers to fix their code to not use @future form non-async safe contexts. And yes, I realize how well that response goes over, I've had to give it more than once.
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!
Best Answer
The Limits class as you have started to discover is only for the current request and does not provide any org wide limit information. Salesforce have partially addressed this with emails, but as far as I can see there is nothing for Batch Apex or @future. Though I have included some thoughts below on both.
Emails. There is a couple of methods on the Messaging class called reserveSingleEmailCapacity and reserveBulkEmailCapacity. They don't tell you how much is left, but will stop your app if your about to exceed it. The downside is that it throws uncatchable exceptions, if that would be an issue to you check out this answer.
Batch Apex / @Future. There is no equivalent to the reserve methods above here, nor any way to query the current value. So here are some general thoughts to consider, both in terms of your answer and also avoiding the limit...
Summary. My personal view is try to consider which jobs need to be 'system / app level' and which are handling 'end user requests'. The former can be scheduled to throttle job usage through aggregation of work into one job. The later, is somewhat harder, but often is negotiable with end users into a more scheduleable approach (say every 15 mins), particularly as they are already used to not getting an immediate response anyway. What remains after taking these two considerations are the jobs that really do need to be invoked more aggressively on user demand.
Ok, I feel like this has turned into a bit of best practice answer, rather than a direct one, which unfortunately is a no. Anyway I hope this helps in some way!