Email send result is neither checked nor guarded
ID |
apex.correctness.email_error_handling |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Error Handling |
Language |
Apex |
Description
Reports a Messaging.sendEmail call whose result is discarded and which is not inside a try block.
Rationale
An email send fails for reasons that have nothing to do with the code: deliverability turned off in a sandbox, a deactivated recipient, a missing org-wide address, a bounced address on the block list. In the all-or-none form the call throws EmailException and takes the whole transaction with it, including the DML that the notification was about; in the partial form it does not throw at all, and the per-message failures sit in a SendEmailResult list that nobody read. Either way the org’s own record says the work succeeded and the person who was supposed to be told never hears anything.
The following code illustrates the pattern detected by this rule:
public class Alerts {
public void fireAndForget(List<Messaging.SingleEmailMessage> messages) {
// FLAGGED: Email send result is neither checked nor guarded
Messaging.sendEmail(messages);
}
public void fireAndForgetPartial(List<Messaging.SingleEmailMessage> messages) {
// FLAGGED: Email send result is neither checked nor guarded
Messaging.sendEmail(messages, false);
}
public void checked(List<Messaging.SingleEmailMessage> messages) {
List<Messaging.SendEmailResult> results = Messaging.sendEmail(messages, false);
for (Messaging.SendEmailResult r : results) {
if (!r.isSuccess()) {
System.debug(LoggingLevel.ERROR, r.getErrors());
}
}
}
public void guarded(List<Messaging.SingleEmailMessage> messages) {
try {
Messaging.sendEmail(messages);
} catch (EmailException e) {
System.debug(LoggingLevel.ERROR, e.getMessage());
}
}
public void guardedWithFinally(List<Messaging.SingleEmailMessage> messages) {
try {
Messaging.sendEmail(messages);
} catch (EmailException e) {
System.debug(LoggingLevel.ERROR, e.getMessage());
} finally {
System.debug('done');
}
}
}
Remediation
Assign the result and inspect it - isSuccess() and getErrors() per message - or wrap the call in a try that catches EmailException and records the failure where an operator will see it. In a trigger context, prefer the partial form plus an explicit check, so a bad address cannot roll back the user’s save.