Apex

リストビューから取引先責任者へ一括メールを送るApex実装

リストビューで選択した取引先責任者へ、まとめてメールを送りたいときの実装です。ループの中でメールを送るとsendEmailの呼び出し回数の上限にすぐ当たります。まとめて1回で送る形に組み直します。

2023.07.11

なぜ1回にまとめて送るのか

選択した取引先責任者へメールを送るとき、1件ずつループの中で Messaging.sendEmail() を呼び出すと、送信件数がすぐに頭打ちになります。Messaging.sendEmail() の呼び出し回数そのものが、1トランザクションあたり10回までに制限されているからです。選択件数ではなく「呼び出した回数」が数えられる点が、ここでのつまずき所です。

メールをまとめて1回の呼び出しで送れば、選択件数が増えても呼び出し回数は1回のままです。

Visualforceページ

一括メール送信の画面を、標準コントローラの拡張として作成します。

BulkSendEmailContact.page

<apex:page standardController="Contact" recordSetVar="records" lightningStyleSheets="true" extensions="BulkSendEmailContactController">
    <apex:pageMessages id="pageMessages" />
    <apex:form>
        <apex:pageBlock title="一括メールを送信する">
            <apex:pageBlockButtons location="top" rendered="{!isNotEmpty}">
                <apex:commandButton value="送信" action="{!sendEmail}" reRender="pageMessages" />
            </apex:pageBlockButtons>
            <apex:pageBlockTable value="{!contacts}" var="contact" rendered="{!isNotEmpty}">
                <apex:column headerValue="姓名" value="{!contact.Name}" />
                <apex:column headerValue="TO" value="{!contact.Email}" />
            </apex:pageBlockTable>
        </apex:pageBlock>
    </apex:form>
</apex:page>

Contactのリストビューから複数選択したレコードを受け取る画面です

Apexクラス

メールはループの中では送りません。送るメッセージをいったんリストに集め、ループを抜けてから1回で送ります。

BulkSendEmailContactController.cls

public with sharing class BulkSendEmailContactController {
    private List<Contact> contacts;

    public BulkSendEmailContactController(ApexPages.StandardSetController controller) {
        Set<Id> contactIds = (new Map<Id, sObject>(controller.getSelected())).keySet();
        if (contactIds.isEmpty()) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.INFO, 'メール送信対象を選択してください。'));
            return;
        }
        contacts = [
            SELECT Name, Email
            FROM Contact
            WHERE Id IN :contactIds
            AND Email != null
        ];
        if (contacts.isEmpty()) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.INFO, 'メールアドレスを持つ送信対象が存在しません。'));
        }
    }

    public List<Contact> getContacts() {
        return contacts;
    }

    public Boolean getIsNotEmpty() {
        return contacts != null && !contacts.isEmpty();
    }

    public PageReference sendEmail() {
        List<Messaging.SingleEmailMessage> emails = new List<Messaging.SingleEmailMessage>();
        for (Contact contact : contacts) {
            String body = 'テストメールを送信しています。';
            Messaging.SingleEmailMessage singleEmail = new Messaging.SingleEmailMessage();
            singleEmail.setToAddresses(new String[]{contact.Email});
            singleEmail.setSubject('テストメール送信');
            singleEmail.setHtmlBody(body.replace('\n', '<br/>'));
            singleEmail.setPlainTextBody(body.stripHtmlTags());
            singleEmail.setBccSender(true);
            emails.add(singleEmail);
        }
        if (!emails.isEmpty()) {
            Messaging.sendEmail(emails);
        }
        return null;
    }
}

選択件数にかかわらずMessaging.sendEmail()の呼び出しは1回だけです

Messaging.sendEmail() を1件ずつループの中で呼び出す形だと、選択件数が10件を超えた時点で、11件目以降が Limits.getLimitEmailInvocations()(1トランザクションあたり10回)の上限に当たって失敗します。選択件数を数えて上限と比べても、実際に効いているのは「呼び出した回数」なので意味がありません。送るメッセージをリストにまとめ、ループの外で1回だけ Messaging.sendEmail(emails) を呼びます。

リストビューからボタンで開く

リストビューで選択したレコードは、標準セットコントローラ(recordSetVar)を使うVisualforceページへそのまま渡ります。コントローラ拡張ではgetSelected()で受け取ります。

  1. カスタムボタンを作成する

    オブジェクトマネージャの「取引先責任者」で「ボタン、リンク、アクション」を開き、新規ボタンを作成します。表示の種類は「リストボタン」、コンテンツソースは「Visualforceページ」を選びます。

  2. ボタンの内容を設定する

    Visualforceページの一覧からBulkSendEmailContactを選んで保存します。選択した取引先責任者が、まとめてページへ渡ります。

  3. リストビューの検索レイアウトに追加する

    「検索レイアウト」の「取引先責任者リストビュー」を編集し、作成したボタンをリストビューのボタンとして追加します。

検索レイアウトの編集画面。選択したボタン側に一括メール送信が入っている
検索レイアウトの編集です。作成したボタンが右の「選択したボタン」に移っているかを見てください。

動作確認

  1. 取引先責任者を複数作成する

    メールアドレスを入力した取引先責任者を、テスト用に複数作成します。

  2. リストビューから選択する

    作成した取引先責任者をリストビューで選択し、追加したボタンを押します。

  3. 送信対象を確認して送る

    一覧に選択した取引先責任者が表示されることを確認し、「送信」ボタンを押します。

  4. 送信結果を確認する

    送信対象の受信箱にテストメールが届いていることを確認します。

取引先責任者のリストビュー。一括メール送信ボタンが並び、ContactAが赤枠で囲まれている
リストビューにボタンが追加された状態です。テスト用の取引先責任者が2件並んでいます。
リストビューで2件の取引先責任者にチェックを入れて選択した状態
送りたいレコードにチェックを入れてから、一括メール送信ボタンを押します。
一括メールを送信する画面。姓名の一覧にContactAとContactBが並び、送信ボタンがある
Visualforceページの送信対象一覧です。選んだレコードが並ぶことを確認してから送信します。
受信箱に「テストメール送信」という件名のメールが2通届いている一覧
受信箱の確認です。選んだ件数と同じ数のメールが届いていれば成功です。

ここで間違えやすい

間違い何が起きるか
ループの中でMessaging.sendEmail()を都度呼ぶ呼び出し回数が1トランザクションあたり10回を超えると失敗します
選択件数をLimits.getLimitEmailInvocations()と比べるこの値は呼び出し回数の上限であって、選択件数の上限ではありません
メールアドレスが空の取引先責任者を対象に含める送信時にエラーになります。SOQLでEmail != nullを条件に含めます
送信ユーザーのメールアドレスが未検証Apexからのメール送信にはドメインとユーザーのメール確認が必要です。送信できない場合はまずここを確認します

確認した環境

  • 2026年9月 / Salesforce Summer '26(API バージョン 67.0)時点の公式ドキュメントで、Messaging.sendEmail()の呼び出し回数の上限と、組織の日次メール送信上限を確認しています
  • 組織の日次のメール送信上限は、標準では外部アドレス宛て5,000件/日です。この一括送信もこの上限を消費します

まとめ

  • Messaging.sendEmail()は1トランザクションあたり10回までしか呼び出せません。選択件数ではなく呼び出し回数が制限されます
  • 送信するメッセージはいったんリストに集め、ループの外で1回だけMessaging.sendEmail()を呼びます
  • リストビューのカスタムボタンは、コンテンツソースをVisualforceページにし、選択したレコードをgetSelected()で受け取ります
  • 一括送信も組織の日次メール送信上限(標準5,000件/日)を消費します
  • Apexからのメール送信には、送信ユーザーのメールアドレス確認が必要です

参考:当時の画面

記事を最初に書いた当時の画面です。いまの手順と違うところは、各画像の説明に書いています。

取引先責任者のカスタムボタン詳細画面。表示ラベルとVisualforceページの欄が赤枠で囲まれている
作成したリストボタンの詳細です。赤枠の表示ラベルと、呼び出すVisualforceページを確認します。

Salesforceの導入・運用についてご相談ください

導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。