今回の記事では、フローで呼び出し可能な添付メール送信Apexクラスの作成手順をご紹介します。ここでは単一メール送信のApexクラスを作成します。フローでメール送信は標準機能で存在するのですが、添付ファイル付きのメールを送信することはできません。
本Apexクラスを作成することで、例えば、商談レコードページのボタンクリックでフローを呼び出し、フロー内で請求書作成(OFC呼び出し)+請求書添付メール送信(Apex呼び出し)といったことが可能になります。フロー+OFC+Apexでの請求書添付メール送信は、以下の記事を参考にしてください。
Salesforceのフローで請求書作成+添付メール送信する – Office File Creator応用編 –
Apexクラス作成のポイント
- フローで呼び出し可能なメソッドは、InvocableMethodアノテーションを付与します。
- フローで入出力に使用する変数には、InvocableVariableアノテーションを付与します。
- メール送信は、Emailクラスと拡張クラスのSingleEmailMessageクラスを使用します。
InvocableMethodの使用時、以下の留意事項があります。
- 呼び出し可能なメソッドは、staticで、publicまたはglobalである必要があり、そのクラスは外部クラスである必要があります。
- クラスの1つのメソッドにのみInvocableMethodアノテーションを付加できます。
- 他のアノテーションとInvocableMethodアノテーションを併用することはできません。
詳細は、以下のApex開発者ガイドを参考にしてください。
メール送信時、ここでは一部のパラメータをセットするメソッドをサンプルとして記載しています。すべてのメソッドは以下のApex開発者ガイドを参考にしてください。
メール送信 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: メソッドの表示ラベル。デフォルトはメソッド名。フロー画面に表示されます。
その他、サポートされる修飾子は、description、callout、category、configurationEditor、iconNameがあります。詳細は、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);
}
toAddresses、ccAddresses、bccAddressesは、それぞれ複数のアドレスのセットが可能です。フローではパラメータとして渡す際に、テキスト型のコレクション変数を作成する必要があります。
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がセットされている場合はテンプレートの内容、セットされていない場合はsubject、htmlBody、plainTextBodyの内容が件名、本文に使用されます。すべて同時にセットしてもエラーにはなりませんが、templateIdをセットした状態でsubject、htmlBody、plainTextBodyをセットした場合、件名、本文の内容はsubject、htmlBody、plainTextBodyの内容で上書きされます。
送信ユーザのメールの署名をセット
//送信ユーザのメールの署名をセット
if(req.useSignature != null){
mail.setUseSignature(req.useSignature);
}
useSignatrureがtrueの時、メール本文の最後に送信ユーザの署名が付与されます。ユーザの署名は、画面右上の自身のアイコン>設定>私のメール設定より設定することができます。デフォルトはtrueです。
署名は二重にならないよう、メール本文の署名、ユーザの署名のどちらを使用するか意識しておく必要があります。例えば、メール本文に署名が含まれていて、ユーザ自身の署名を設定している場合、デフォルトはtrueなのでテンプレート本文の署名の下にユーザの署名が表示され、署名が二重表示されてしまいます。
また、テスト時、ユーザ自身の署名は設定しておらずuseSignatrure=falseにしなくとも二重にはならないけど、署名を設定している他のユーザが送信時に署名が二重で設定されてしまう、とならないよう注意が必要です。
メール本文の署名を使用しユーザの署名を使用しない場合は、呼び出し元で明示的にuseSignatrure=falseをセットします。逆にユーザの署名を使用する場合は、メール本文に署名を含めないようにします。
画面右上の自身のアイコン>設定>私のメール設定
添付ファイル
//添付ファイル
mail.setEntityAttachments(req.fileIds);
fileIdsは、複数のファイルIdのセットが可能です。フローではパラメータとして渡す際に、テキスト型のコレクション変数を作成する必要があります。
ContentVersionId、AttchmentId、DocumentIdのいずれかのIdをセットします。
関連レコードId
//関連レコードId
mail.setWhatId(req.whatId);
テンプレート使用時、レコードの差し込み項目をメールテンプレートに使用している場合は、対象のレコードIdをwhatIdにセットします。テンプレートを使用しない場合も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);
}
treatTargetObjectAsRecipientがtrueの時、対象オブジェクトId(取引先責任者、リード、ユーザ)(targetObjectId)のメールアドレスをTo宛先としてメール送信します。デフォルトはtrueです。
メールを活動に保存
//メールを活動に保存
if(req.saveAsActivity != null){
mail.setSaveAsActivity(req.saveAsActivity);
}
saveAsActivityがtrueの時、メール送信後、対象オブジェクトレコード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の引数に、emailsとallOrNothingオプションを指定します。
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の引数allOrNothingをfalseに設定した場合、getEmailInvocationsメソッドは失敗時も送信数を返します。今回allOrNothingをfalseにしており常に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がセットされていて、saveAsActivityがtrue |
INVALID_ID_FIELD, SaveAsActivity is not allowed with whatId that is not supported as a task whatId. | saveAsActivityがtrueで、whatIdのオブジェクトの「活動を許可」がオフ |
Single email is not enabled for your organization or profile. | 設定>メール>送信>メールを送信するためのアクセス権 (すべてのメールサービス)のアクセス権が「すべてのメール」に設定されていない |
メモ
メールアドレスの形式が正しく、存在しないメールアドレスの場合、エラーにはなりません。メール送信が完了で成功となり、宛先不明の結果は検証されません。
設定>メール>送信の不達管理を設定することにより、宛先不明の確認が可能です。
不達管理の有効化: チェックオンの時、EmailMessageレコードのIsBouncedがtrueで更新されます。メールを活動に保存(saveAsActivity)がtrueの時、活動にも「不達」と表示されます。
エラーが発生したメールを送信者に返す: チェックオンの時、Salesforceから不達メールが届きます。
検証時は、以下のSalesforceアドレスから不達メールが届きました。
送信者: Mail Delivery System <mailer-daemon@salesforce.com>
不達エラーの結果はメール送信後、即座~数日かかります。検証時、存在しないドメインの場合は1日かかっていました。