Salesforceのフローで呼び出し可能な添付メール送信Apexクラスを作成する

今回の記事では、フローで呼び出し可能な添付メール送信Apexクラスの作成手順をご紹介します。ここでは単一メール送信のApexクラスを作成します。フローでメール送信は標準機能で存在するのですが、添付ファイル付きのメールを送信することはできません。

 

Apexクラスを作成することで、例えば、商談レコードページのボタンクリックでフローを呼び出し、フロー内で請求書作成(OFC呼び出し)+請求書添付メール送信(Apex呼び出し)といったことが可能になります。フロー+OFC+Apexでの請求書添付メール送信は、以下の記事を参考にしてください。

Salesforceのフローで請求書作成+添付メール送信する – Office File Creator応用編 –

 

Apexクラス作成のポイント

    • フローで呼び出し可能なメソッドは、InvocableMethodアノテーションを付与します。
    • フローで入出力に使用する変数には、InvocableVariableアノテーションを付与します。
    • メール送信は、Emailクラスと拡張クラスのSingleEmailMessageクラスを使用します。

     

    InvocableMethodの使用時、以下の留意事項があります。

    • 呼び出し可能なメソッドは、staticで、publicまたはglobalである必要があり、そのクラスは外部クラスである必要があります。
    • クラスの1つのメソッドにのみInvocableMethodアノテーションを付加できます。
    • 他のアノテーションとInvocableMethodアノテーションを併用することはできません。

     

    詳細は、以下のApex開発者ガイドを参考にしてください。

    InvocableMethod アノテーション

    InvocableVariable アノテーション

     

    メール送信時、ここでは一部のパラメータをセットするメソッドをサンプルとして記載しています。すべてのメソッドは以下のApex開発者ガイドを参考にしてください。

    Email クラス (基本メールメソッド)

    SingleEmailMessage のメソッド

     

    メール送信 Apexクラスのサンプル

    SendEmailWithAttachment.cls

    /**
     * 添付ファイル付きメール送信
     */
    public class SendEmailWithAttachment{
        /**
         * Request
         */
        public class Request {
            @InvocableVariable(label='Toメールアドレス')
            public String[] toAddresses;
            @InvocableVariable(label='Ccメールアドレス')
            public String[] ccAddresses;
            @InvocableVariable(label='Bccメールアドレス')
            public String[] bccAddresses;
            @InvocableVariable(label='Bccにメール送信者をセット *デフォルトはfalse')
            public Boolean bccSender;
            @InvocableVariable(label='組織のアドレスId')
            public String orgWideEmailAddressId;
            @InvocableVariable(label='送信者名 *組織のアドレスIdがセットされている場合、設定不可')
            public String senderDisplayName;
            @InvocableVariable(label='テンプレートId')
            public String templateId;
            @InvocableVariable(label='件名')
            public String subject;
            @InvocableVariable(label='メールのテキスト本文')
            public String plainTextBody;
            @InvocableVariable(label='メールのhtml本文')
            public String htmlBody;
            @InvocableVariable(label='送信ユーザのメールの署名をセット *デフォルトはtrue')
            public Boolean useSignature;
            @InvocableVariable(label='添付ファイルId *ContentVersionId, AttchmentId or DocumentId')
            public String[] fileIds;
            @InvocableVariable(label='関連レコードId')
            public String whatId;
            @InvocableVariable(label='対象オブジェクトId(取引先責任者、リード、ユーザ) *テンプレートIdがセットされている場合、必須')
            public String targetObjectId;
            @InvocableVariable(label='対象オブジェクトId(取引先責任者、リード、ユーザ)を受信者に設定 *デフォルトはtrue')
            public Boolean treatTargetObjectAsRecipient;
            @InvocableVariable(label='メールを活動に保存 *targetObjectId、whatIdレコードに保存、デフォルトはtrue')
            public Boolean saveAsActivity;
        }
    
        /**
         * Result
         */
        public class Result {
            @InvocableVariable(label='実行結果 *true=成功、false=失敗')
            public Boolean isSuccess;
            @InvocableVariable(label='エラーメッセージ')
            public String errorMessage;
        }
    
        /**
         * メール送信
         * @description 添付ファイル付きの単一メール送信
         * @param requests メールのパラメータ
         * @return メール送信結果
         */
        @InvocableMethod(label='メール送信 (添付ファイル付きの単一メール送信)')
        public static List<Result> sendEmail(List<Request> requests) {
            Request req = requests[0];
            Result result = new Result();
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
    
            //受信者
            mail.setToAddresses(req.toAddresses);
            mail.setCcAddresses(req.ccAddresses);
            mail.setBccAddresses(req.bccAddresses);
            if(req.bccSender != null){
                mail.setBccSender(req.bccSender);
            }
    
            //送信者
            if(!String.isBlank(req.orgWideEmailAddressId)){
                mail.setOrgWideEmailAddressId(req.orgWideEmailAddressId);
            }
            else{
                mail.setSenderDisplayName(req.senderDisplayName);
            }
           
            //件名・本文
            if(!String.isBlank(req.templateId)){
                mail.setTemplateId(req.templateId);
            }
            else{
                mail.setSubject(req.subject);
                mail.setHtmlBody(req.htmlBody);
                mail.setPlainTextBody(req.plainTextBody);
            }
    
            //送信ユーザのメールの署名をセット
            if(req.useSignature != null){
                mail.setUseSignature(req.useSignature);
            }
    
            //添付ファイル
            mail.setEntityAttachments(req.fileIds);
    
            //関連レコードId
            mail.setWhatId(req.whatId);
    
            //対象オブジェクトレコードId(取引先責任者、リード、ユーザ)
            mail.setTargetObjectId(req.targetObjectId);
    
            //対象オブジェクトId(取引先責任者、リード、ユーザ)を受信者に設定
            if(req.treatTargetObjectAsRecipient != null){
                mail.setTreatTargetObjectAsRecipient(req.treatTargetObjectAsRecipient);
            }
    
            //メールを活動に保存
            if(req.saveAsActivity != null){
                mail.setSaveAsActivity(req.saveAsActivity);
            }
    
            //メール送信実行
            Messaging.SendEmailResult[] sendResults =
                Messaging.sendEmail(new Messaging.SingleEmailMessage[] {mail}, false);
            result.isSuccess = sendResults[0].success;
    
            if(!result.isSuccess) {
                result.errorMessage = '';
                for(Messaging.SendEmailError error : sendResults[0].getErrors()){
                    result.errorMessage += error.getMessage() + ';';
                }
            }
    
            return new List<Result> {result};
        }
    }

     

     

    Requestクラス

    Request一部抜粋

        /**
         * Request
         */
        public class Request {
            @InvocableVariable(label='Toメールアドレス')
            public String[] toAddresses;
            @InvocableVariable(label='Ccメールアドレス')
            public String[] ccAddresses;
            @InvocableVariable(label='Bccメールアドレス')
            public String[] bccAddresses;
            @InvocableVariable(label='Bccにメール送信者をセット *デフォルトはfalse')
            public Boolean bccSender;

    フロー内で呼び出す際の入力用変数をRequestクラス内に作成します。フロー内で呼び出し可能にするため、変数には@InvocableVariableを使用します。InvocableVariableは、以下の修飾子がサポートされており、いずれも省略可能です。

    label: 変数の表示ラベル。デフォルトは変数名。フロー画面に表示されます。

    description: 変数の説明。フロー画面に表示されません。

    required: 変数が必須かどうかを指定。デフォルトは false

     

    フローではApex呼び出し時、labelしか表示されずdescriptionは表示されません。例えば、以下のようにdescriptionに説明を記載してもフローではlabelのみが表示されます。

    @InvocableVariable(label='Bccにメール送信者をセット' description='デフォルトはfalse')

    フローでの設定画面

     

    ということで、変数の説明が必要な場合、説明も表示されるようlabelに含め、descriptionは使用しません。

    @InvocableVariable(label='Bccにメール送信者をセット *デフォルトはfalse')

    フローでの設定画面

     

    汎用的なメール送信Apexクラスとしますので、ここではどの変数もrequired=falseのデフォルトとし、requiredオプションは省略します。

     

    Resultクラス

        /**
         * Result
         */
        public class Result {
            @InvocableVariable(label='実行結果 *true=成功、false=失敗')
            public Boolean isSuccess;
            @InvocableVariable(label='エラーメッセージ')
            public String errorMessage;
        }

    結果をResultクラス内に作成します。実行結果(isSuccess)とエラーメッセージ(errorMessage)を返します。

     

    sendEmailメソッド

        /**
         * メール送信
         * @description 添付ファイル付きの単一メール送信
         * @param requests メールのパラメータ
         * @return メール送信結果
         */
        @InvocableMethod(label='メール送信 (添付ファイル付きの単一メール送信)')
        public static List<Result> sendEmail(List<Request> requests) {
            Request req = requests[0];
            Result result = new Result();
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();

    フローで呼び出し可能にするため、メソッドには@InvocableMethodを使用します。

    label: メソッドの表示ラベル。デフォルトはメソッド名。フロー画面に表示されます。

    その他、サポートされる修飾子は、descriptioncalloutcategoryconfigurationEditoriconNameがあります。詳細は、Apex開発者ガイドを参照してください。

     

    descriptionは、Apex開発者ガイドには、”Flow Builder にアクションの説明として表示されます。とあるのですが、実際はフローのどこにも表示されません。ここでは説明も表示されるようlabelに含めます。

    @InvocableMethod(label='メール送信 (添付ファイル付きの単一メール送信)')

     

    InvocableMethodは、入力、出力ともにリストである必要があります。リストではありますが、それぞれ単一のデータのため、入力パラメータのRequestは、requests[0]で受け取ります。

     

    受信者

            //受信者
            mail.setToAddresses(req.toAddresses);
            mail.setCcAddresses(req.ccAddresses);
            mail.setBccAddresses(req.bccAddresses);
            if(req.bccSender != null){
                mail.setBccSender(req.bccSender);
            }

    toAddressesccAddressesbccAddressesは、それぞれ複数のアドレスのセットが可能です。フローではパラメータとして渡す際に、テキスト型のコレクション変数を作成する必要があります。

    bccSender は、bccSenderがtrueの場合、Bccにメール送信者をセットします。デフォルトはfalseです。

     

    送信者

            //送信者
            if(!String.isBlank(req.orgWideEmailAddressId)){
                mail.setOrgWideEmailAddressId(req.orgWideEmailAddressId);
            }
            else{
                mail.setSenderDisplayName(req.senderDisplayName);
            }

    組織のアドレスId(orgWideEmailAddressId)がセットされている場合、送信者名(senderDisplayName)をセットすることは不可です。両方セットするとエラーになります。ここでは、条件分岐しているので両方セットしていてもエラーにはなりませんが、送信者名は無視されます。

     

    件名・本文

            //件名・本文
            if(!String.isBlank(req.templateId)){
                mail.setTemplateId(req.templateId);
            }
            else{
                mail.setSubject(req.subject);
                mail.setHtmlBody(req.htmlBody);
                mail.setPlainTextBody(req.plainTextBody);
            }

    templateIdがセットされている場合はテンプレートの内容、セットされていない場合はsubjecthtmlBodyplainTextBodyの内容が件名、本文に使用されます。すべて同時にセットしてもエラーにはなりませんが、templateIdをセットした状態でsubjecthtmlBodyplainTextBodyをセットした場合、件名、本文の内容はsubjecthtmlBodyplainTextBodyの内容で上書きされます。

     

    送信ユーザのメールの署名をセット

            //送信ユーザのメールの署名をセット
            if(req.useSignature != null){
                mail.setUseSignature(req.useSignature);
            }

    useSignatruretrueの時、メール本文の最後に送信ユーザの署名が付与されます。ユーザの署名は、画面右上の自身のアイコン>設定>私のメール設定より設定することができます。デフォルトはtrueです。

     

    署名は二重にならないよう、メール本文の署名、ユーザの署名のどちらを使用するか意識しておく必要があります。例えば、メール本文に署名が含まれていて、ユーザ自身の署名を設定している場合、デフォルトはtrueなのでテンプレート本文の署名の下にユーザの署名が表示され、署名が二重表示されてしまいます。

    また、テスト時、ユーザ自身の署名は設定しておらずuseSignatrure=falseにしなくとも二重にはならないけど、署名を設定している他のユーザが送信時に署名が二重で設定されてしまう、とならないよう注意が必要です。

    メール本文の署名を使用しユーザの署名を使用しない場合は、呼び出し元で明示的にuseSignatrure=falseをセットします。逆にユーザの署名を使用する場合は、メール本文に署名を含めないようにします。

     

    画面右上の自身のアイコン>設定>私のメール設定

     

    添付ファイル

            //添付ファイル
            mail.setEntityAttachments(req.fileIds);

    fileIdsは、複数のファイルIdのセットが可能です。フローではパラメータとして渡す際に、テキスト型のコレクション変数を作成する必要があります。

    ContentVersionIdAttchmentIdDocumentIdのいずれかのIdをセットします。

     

    関連レコードId

            //関連レコードId
            mail.setWhatId(req.whatId);

    テンプレート使用時、レコードの差し込み項目をメールテンプレートに使用している場合は、対象のレコードIdwhatIdにセットします。テンプレートを使用しない場合もwhatIdをセットすることで、メールを活動に保存(saveAsActivity)trueの時、メール送信後、whatIdレコードに活動が保存されます。

     

    対象オブジェクトレコードId(取引先責任者、リード、ユーザ)

            //対象オブジェクトレコードId(取引先責任者、リード、ユーザ)
            mail.setTargetObjectId(req.targetObjectId);

    targetObjectIdは、取引先責任者、リード、ユーザのいずれかのIdをセットします。templateIdをセットしている場合は必須です。templateIdをセットしてtargetObjectIdをセットしない場合は、エラーになります。今回のように単一メール送信時、targetObjectIdは単一のIdのみセット可能です。

    テンプレート使用時、targetObjectIdを差し込み項目に使用することができます。

    対象オブジェクトId(取引先責任者、リード、ユーザ)を受信者に設定(treatTargetObjectAsRecipient)trueの時、取引先責任者、リード、ユーザのメールアドレスをTo宛先としてメール送信します。

    また、メールを活動に保存(saveAsActivity)trueの時、メール送信後、targetObjectIdのレコードに活動が保存されます。

     

    対象オブジェクトレコードId(取引先責任者、リード、ユーザ) を受信者に設定

            //対象オブジェクトId(取引先責任者、リード、ユーザ)を受信者に設定
            if(req.treatTargetObjectAsRecipient != null){
                mail.setTreatTargetObjectAsRecipient(req.treatTargetObjectAsRecipient);
            }

    treatTargetObjectAsRecipienttrueの時、対象オブジェクトId(取引先責任者、リード、ユーザ)(targetObjectId)のメールアドレスをTo宛先としてメール送信します。デフォルトはtrueです。

     

    メールを活動に保存

            //メールを活動に保存
            if(req.saveAsActivity != null){
                mail.setSaveAsActivity(req.saveAsActivity);
            }

    saveAsActivitytrueの時、メール送信後、対象オブジェクトレコードId(取引先責任者、リード、ユーザ)(targetObjectId)、関連レコードId(whatId)レコードに活動が保存されます。デフォルトはtrueです。

     

    メール送信実行

            //メール送信実行
            Messaging.SendEmailResult[] sendResults =
                Messaging.sendEmail(new Messaging.SingleEmailMessage[] {mail}, false);
            result.isSuccess = sendResults[0].success;
    
    
            if(!result.isSuccess) {
                result.errorMessage = '';
                for(Messaging.SendEmailError error : sendResults[0].getErrors()){
                    result.errorMessage += error.getMessage() + ';';
                }
            }
    
    
            return new List<Result> {result};

    単一のメール送信を実行し、結果を返します。Messaging.sendEmailの引数に、emailsallOrNothingオプションを指定します。

    emails: 複数のメールメッセージをセットすることができます。ここでは、1通のメールのみを送信し、mailを指定します。

    allOrNothing: 複数のメールを送信時、任意のメッセージがエラーで失敗した場合、すべてのメッセージの配信を行わない (true) か、エラーのないメッセージの配信を行う (false) かを指定します。デフォルトは true です。ここではfalseに設定します。

     

    例えば、3通のメールを送信する場合、以下のようになります。

    allOrNothing=true: 3通のメールを送信時、3通目にエラーがある場合、3通すべてメール送信しない

    allOrNothing=false: 3通のメールを送信時、3通目にエラーがある場合、1通目と2通目はメール送信する

     

    ここでは単一メールの送信なので、他のメールを送信する、しないは関係ないオプションではあるのですが、allOrNothing trueで実行時に何らかのエラーが発生した場合、Messaging.sendEmailメソッドを実行した時点で処理が止まります。falseにした場合、エラーがあっても処理はとまらず、結果がエラーで返されるのでここではfalseに設定します。

     

     

    テストクラス

    SendEmailWithAttachmentTest.cls

    /**
     * SendEmailWithAttachment テストクラス
     */
    @isTest
    private class SendEmailWithAttachmentTest {
        /**
         * 通常
         */
        @isTest
        private static void test_normal01(){
            SendEmailWithAttachment.Request req = new SendEmailWithAttachment.Request();
            req.toAddresses = new String[]{'test01@testsfdc.com'};
            req.ccAddresses = new String[]{'test02@testsfdc.com'};
            req.subject = 'Invoice';
            req.plainTextBody = 'Please find the attached invoice.';
            req.bccSender = true;
            req.useSignature = true;
            req.saveAsActivity = true;
    
            Test.startTest();
            List<SendEmailWithAttachment.Result> results =
                SendEmailWithAttachment.sendEmail(new List<SendEmailWithAttachment.Request>{req});
            Test.stopTest();
    
            System.assertEquals(true, results[0].isSuccess);
        }
    
        /**
         * 通常
         * テンプレート使用
         */
        @isTest
        private static void test_normal02(){
            SendEmailWithAttachment.Request req = new SendEmailWithAttachment.Request();
            req.toAddresses = new String[]{'test01@testsfdc.com'};
            req.ccAddresses = new String[]{'test02@testsfdc.com'};
            req.templateId = createEmailTemplate().Id;
            req.targetObjectId = UserInfo.getUserId();
            req.saveAsActivity = false;
            req.treatTargetObjectAsRecipient = true;
    
            Test.startTest();
            List<SendEmailWithAttachment.Result> results =
                SendEmailWithAttachment.sendEmail(new List<SendEmailWithAttachment.Request>{req});
            Test.stopTest();
    
            System.assertEquals(true, results[0].isSuccess);
        }
    
        /**
         * 通常
         * 組織のアドレス使用
         */
        @isTest
        private static void test_normal03(){
            SendEmailWithAttachment.Request req = new SendEmailWithAttachment.Request();
            req.toAddresses = new String[]{'test01@testsfdc.com'};
            req.subject = 'Invoice';
            req.plainTextBody = 'Please find the attached invoice.';
            //get orgWideEmailAddress
            Boolean isSuccess;
            List<OrgWideEmailAddress> orgList = [SELECT Id FROM OrgWideEmailAddress LIMIT 1];
            if(orgList.size() == 1){
                req.orgWideEmailAddressId = orgList[0].Id;
                isSuccess = true;
            }
            else{
                req.orgWideEmailAddressId = '0D2000000000000000';
                isSuccess = false;
            }
    
            Test.startTest();
            List<SendEmailWithAttachment.Result> results =
                SendEmailWithAttachment.sendEmail(new List<SendEmailWithAttachment.Request>{req});
            Test.stopTest();
    
            System.assertEquals(isSuccess, results[0].isSuccess);
        }
    
        /**
         * エラー
         * メールアドレス形式不正
         */
        @isTest
        private static void test_error01(){
            SendEmailWithAttachment.Request req = new SendEmailWithAttachment.Request();
            req.toAddresses = new String[]{'test01testsfdccom'};
            req.subject = 'Invoice';
            req.plainTextBody = 'Please find the attached invoice.';
    
            Test.startTest();
            List<SendEmailWithAttachment.Result> results =
                SendEmailWithAttachment.sendEmail(new List<SendEmailWithAttachment.Request>{req});
            Test.stopTest();
    
            System.assertEquals(false, results[0].isSuccess);
        }
    
        /**
         * テンプレート作成
         */
        private static EmailTemplate createEmailTemplate(){
            EmailTemplate e = new EmailTemplate(
                Name = 'test',
                DeveloperName = 'test',
                FolderId = UserInfo.getUserId(),
                TemplateType = 'Text',
                Subject = 'Invoice',
                Body = 'Please find the attached invoice.',
                IsActive = true
            );
    
            insert e;
            return e;
        }
    }

     

    組織のアドレス

            //get orgWideEmailAddress
            Boolean isSuccess;
            List<OrgWideEmailAddress> orgList = [SELECT Id FROM OrgWideEmailAddress LIMIT 1];
            if(orgList.size() == 1){
                req.orgWideEmailAddressId = orgList[0].Id;
                isSuccess = true;
            }
            else{
                req.orgWideEmailAddressId = '0D2000000000000000';
                isSuccess = false;
            }

    組織のアドレスのOrgWideEmailAddressレコードはテストクラスで作成不可です。そのため、実行環境に組織のアドレスがあれば取得、なければ、”0D2000000000000000”をセットしています。”0D2000000000000000”は存在しないレコードのため、結果の期待値はエラーとなります。

     

    メモ

    送信したメール数はLimits.getEmailInvocations()で取得可能です。テストクラス内で以下のように送信数の結果を確認可能です。今回は、単一メールのため成功の場合は1、失敗の場合は0となります。

    System.assertEquals(1, Limits.getEmailInvocations());

    但し、メール送信時、Messaging.sendEmailの引数allOrNothingfalseに設定した場合、getEmailInvocationsメソッドは失敗時も送信数を返します。今回allOrNothingfalseにしており常に1が返されるため、Limits.getEmailInvocations()はテストクラスに含めていません。resultクラスの結果で成功、エラーを確認します。

     

    エラー例

    Messaging.sendEmail実行時にエラーになる例としては、以下のようなものがあります。

    エラー例

    エラーメッセージ エラー内容
    REQUIRED_FIELD_MISSING, メールを送信する受信者を追加してください。 受信者がいずれもセットされていない
    INVALID_EMAIL_ADDRESS, Email address is invalid: メールアドレスの形式が不正
    REQUIRED_FIELD_MISSING, Missing targetObjectId with template: [] templateIdがセットされていて、targetObjectIdがセットされていない
    INVALID_SAVE_AS_ACTIVITY_FLAG, saveAsActivity must be false when sending mail to users.: [saveAsActivity, true] targetObjectIdにユーザIdがセットされていて、saveAsActivitytrue
    INVALID_ID_FIELD, SaveAsActivity is not allowed with whatId that is not supported as a task whatId. saveAsActivitytrueで、whatIdのオブジェクトの「活動を許可」がオフ
    Single email is not enabled for your organization or profile. 設定>メール>送信>メールを送信するためのアクセス権 (すべてのメールサービス)のアクセス権が「すべてのメール」に設定されていない

     

    メモ

    メールアドレスの形式が正しく、存在しないメールアドレスの場合、エラーにはなりません。メール送信が完了で成功となり、宛先不明の結果は検証されません。

    設定>メール>送信の不達管理を設定することにより、宛先不明の確認が可能です。

    不達管理の有効化: チェックオンの時、EmailMessageレコードのIsBouncedtrueで更新されます。メールを活動に保存(saveAsActivity)trueの時、活動にも「不達」と表示されます。

    エラーが発生したメールを送信者に返す: チェックオンの時、Salesforceから不達メールが届きます。

    検証時は、以下のSalesforceアドレスから不達メールが届きました。

    送信者: Mail Delivery System <mailer-daemon@salesforce.com>

     

    不達エラーの結果はメール送信後、即座~数日かかります。検証時、存在しないドメインの場合は1日かかっていました。

     

    タイトルとURLをコピーしました