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!
Edit: Having been questioned about the behavior, I just tested this and found that you can send as many emails as you can handle within governor limits. As a proof of concept, I wrote the following code:
Messaging.SingleEmailMessage[] messages = new Messaging.SingleEmailMessage[0];
for(integer i = 0; i < 10000; i++) {
messaging.SingleEmailMessage m = new messaging.SingleEmailMessage();
m.settargetobjectid(userinfo.getUserId());
m.setsubject('hello'+i);
m.setplaintextbody('world'+i);
m.setsaveasactivity(false);
messages.add(m);
}
messaging.sendemail(messages);
system.assert(false);
This code did not fail with any errors about too many emails in the list, heap limits, CPU limits, etc. So, as long as you don't break any other governor limits, you can theoretically send 25,000 in a single call (but, you'd likely run in to governor limits without going asynchronous).
You can send 100 SingleEmailMessage per sendEmail call, and 10 sendEmail calls per transaction. So, you can send 1000 per transaction. There's no limit for sending to portal and internal users using setTargetObjectId.
All of this information is found in the Governor Limits document in the Email Limits section (the link goes there directly).
Best Answer
For a virtually unlimited number of email sends, you could relay the messages through your own service via call outs. Salesforce has to limit the messages sent to reduce the chance of spam filters blocking one customer's emails and consequently black listing many customers (because filters are usually by IP address).
Basically, this would simply require an endpoint hosted in a server you control to accept REST or SOAP messages, and in turn relay that to the local SMTP. You could also find a proper email hosting company that has a REST API.
I realize that the original question was about sending "through" salesforce, but from a practical standpoint, there's no alternative than to relay. It's easy to implement and gives far more flexibility.