Messaging.sendEmail called inside a loop

ID

apex.correctness.email_messages_in_loop

Severity

high

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Performance

Language

Apex

Description

Reports a Messaging.sendEmail call inside a for, while or do loop.

Rationale

A transaction may call sendEmail ten times, not ten thousand: the eleventh call throws System.LimitException: Too many Email Invocations. Sending one message per iteration therefore works for the first ten records and aborts the transaction on the eleventh, after ten emails have already left the org and any DML in the same transaction has been rolled back - so the recipients received a notification about a change that was then undone. The limit is per transaction, so the same loop passes every test that uses three records.

The following code illustrates the pattern detected by this rule:

public class Notifier {

    public void notifyEach(List<Contact> contacts) {
        for (Contact c : contacts) {
            // FLAGGED: Messaging.sendEmail called inside a loop
            Messaging.sendEmail(new List<Messaging.SingleEmailMessage>{ build(c) });
        }
    }

    public void notifyIndexed(List<Contact> contacts) {
        for (Integer i = 0; i < contacts.size(); i++) {
            // FLAGGED: Messaging.sendEmail called inside a loop
            Messaging.sendEmail(new List<Messaging.SingleEmailMessage>{ build(contacts[i]) });
        }
    }

    public void notifyAll(List<Contact> contacts) {
        List<Messaging.SingleEmailMessage> messages = new List<Messaging.SingleEmailMessage>();
        for (Contact c : contacts) {
            messages.add(build(c));
        }
        Messaging.sendEmail(messages);
    }

    public void notifyOne(Contact c) {
        Messaging.sendEmail(new List<Messaging.SingleEmailMessage>{ build(c) });
    }

    private Messaging.SingleEmailMessage build(Contact c) {
        Messaging.SingleEmailMessage m = new Messaging.SingleEmailMessage();
        m.setTargetObjectId(c.Id);
        return m;
    }
}

Remediation

Build the messages in the loop and send them once after it: Messaging.sendEmail(messages); accepts up to 100 in a single call, which also counts as one invocation. Where the volume is larger than one transaction can hold, move the send into a batch and let each chunk send its own collection.

Configuration

This detector does not need any configuration.

© 2026 Xygeni. All rights reserved