画面フローとApexで送るテンプレート不要のメール送信
JavaScriptカスタムボタンからApexを呼ぶ方法は、Lightning Experienceでは動きません。画面フローのアクションからApexを呼び出し、テンプレートを使わずメールを送信する現行の実装を、送信上限と動作確認まで含めて説明します。
なぜテンプレートを使わずに送るのか
宛先も文面も決まっている定型の通知メールでは、メールテンプレートを用意するほどではありません。Apexの中で件名と本文を組み立てて、そのまま送ってしまうほうが早い場面があります。レコード詳細のボタンを押したら固定文面の通知が飛ぶ、という作りです。
この記事では、その送信処理をレコード詳細のアクションから呼び出す実装を、いまのLightning Experienceで動く形でまとめます。
JavaScriptカスタムボタンは動かない
以前はこの手のボタンを、カスタムボタンの「OnClick JavaScript」で作るのが定番でした。{!REQUIRESCRIPT("/soap/ajax/38.0/connection.js")}でAJAX Toolkitを読み込み、sforce.apex.execute()でApexのWebServiceメソッドを直接呼び出す書き方です。
この作り方は、いまは使えません。公式ヘルプの「カスタムボタンとカスタムリンクの制限事項」は「Custom buttons that call JavaScript aren't supported in Lightning Experience(JavaScriptを呼び出すカスタムボタンはLightning Experienceでサポートされません)」と明記しています。ページレイアウトにボタンを置いても、押しても何も起きません。
⚠️ WebService修飾子とAJAX Toolkit(sforce.apex.execute)はSOAPベースの古い仕組みです。新しく作る処理では使いません。Salesforce Developersのブログも、この手のボタンの置き換え先としてクイックアクションとApex、Lightningコンポーネント、フローを挙げています。
現行のやり方はアクションからApexを呼ぶ
いまは、レコード詳細に置くボタンをカスタムボタンではなくクイックアクションにして、その中からApexを呼び出します。作り方は主に2通りです。
| 方法 | 向いている場面 |
|---|---|
| 画面フロー+Apexアクション | 画面はほぼ不要で、ボタン1つで送信まで完結させたいとき |
LWC+@AuraEnabledのApexメソッド | 送信前に内容を確認させたいなど、画面を作り込みたいとき |
この記事では画面フローの形で実装します。送信処理そのものはApex側にまとめておくと、どちらの入口からでも同じロジックを再利用できます。
テンプレートなしで本文を組み立てる
Messaging.SingleEmailMessageは、テンプレートを指定しなくても送信できます。押さえておくのは次の4つです。
| メソッド | 役割 | 押さえておくこと |
|---|---|---|
setSubject | 件名 | テンプレートを使う場合は、テンプレート側の件名が優先されます |
setPlainTextBody | テキスト版の本文 | setTemplateId・setHtmlBody・setPlainTextBodyのいずれかは必ず指定します |
setHtmlBody | HTML版の本文 | 同上。テンプレートを使わないときは、この2つのどちらかで本文を渡します |
setToAddresses | 宛先 | メールアドレスの文字列のほか、取引先責任者・リード・ユーザーのIDも渡せます |
⚠️ 宛先は最低1件必要です。toAddresses・ccAddresses・bccAddresses・targetObjectIdのどこにも入っていないと送信できません。
setTargetObjectIdは、宛先にする取引先責任者・リード・ユーザーのIDを渡すメソッドです。公式リファレンスは「Required if using a template, optional otherwise(テンプレートを使う場合は必須、それ以外は任意)」としています。テンプレートを使わない今回の作りでは、setToAddressesだけで足ります。
ただしsetTargetObjectIdには、あとで効いてくる差があります。社内ユーザーのIDをsetTargetObjectIdに渡して送ったメールは、1日あたりの送信上限に数えられません。社内向けの通知をたくさん送るなら、宛先の渡し方を変えるだけで上限の消費が変わります。なお、メールを活動として記録するsetSaveAsActivityも、宛先がtargetObjectIdのときだけ効きます。
送信処理をApexで書く
画面フローのアクション要素から呼び出せるように、@InvocableMethodを付けたクラスにします。入力はリストで受け取ります。複数レコードから一括で呼ばれても1回で処理できる形です。
public with sharing class NotifyByEmailAction { @InvocableMethod(label='固定文面の通知メールを送信' category='Email') public static void send(List<Id> recordIds) { Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage(); mail.setToAddresses(new List<String>{ 'xxxxxxxx@example.com' }); mail.setReplyTo('xxxxxxxx@example.com'); mail.setSubject('【メール配信】'); mail.setPlainTextBody('各位\n\nテスト用メールをお送りいたします。\n宜しくお願い致します。\n'); List<Messaging.SendEmailResult> results = Messaging.sendEmail(new List<Messaging.SingleEmailMessage>{ mail }, false); for (Messaging.SendEmailResult result : results) { if (!result.isSuccess()) { Messaging.SendEmailError err = result.getErrors()[0]; throw new NotifyByEmailException(err.getStatusCode() + ':' + err.getMessage()); } } } public class NotifyByEmailException extends Exception {} }
画面フローのアクションから呼び出す送信処理です。宛先はダミーのメールアドレスです。
globalとWebServiceは付けていません。アクションから呼ぶだけならpublicで十分です。引数のrecordIdsは固定文面のこの版では使っていませんが、あとで「レコードの値を本文に差し込む」に変えるときの入口として残してあります。
⚠️ @InvocableMethodを付けられるメソッドは、1クラスに1つだけです。メソッドはstaticでpublicまたはglobal、クラスは外部クラスである必要があります。引数にはプリミティブ型のリストのほか、List<sObject>や@InvocableVariableを付けた独自クラスのリストも使えます。使えないのは汎用のObject型です。
送信結果を受け取ってエラーを見つける
Messaging.sendEmailはMessaging.SendEmailResultの配列を返します。戻り値を見ないと、送れていなくても処理は先へ進みます。
| 受け取り方 | 分かること |
|---|---|
isSuccess() | 配信のために受け付けられたかどうか |
getErrors() | 失敗したときのMessaging.SendEmailErrorの配列 |
getStatusCode() | エラーの種類を示すコード |
getMessage() | エラーの本文 |
第2引数のallOrNothingは、省略するとtrueです。公式リファレンスは「1通でもエラーになったとき、他のメールの配信も行わない(true)か、エラーのないメールは配信する(false)か」と説明しています。上のコードでfalseを渡しているのは、どのメールがどう失敗したかをSendEmailResultから読み取るためです。
⚠️ isSuccess()がtrueでも、届いたとは限りません。公式リファレンスも「アドレスに問題があったり、バウンスしたり、スパムフィルタにはじかれたりした可能性がある」と断っています。届いたかどうかは、受信側で確かめるしかありません。
送信の失敗はSystem.EmailException(公式リファレンスの説明は「メールに関する何らかの問題。配信の失敗など」)として投げられることもあります。どちらの経路でも呼び出し元にエラーが伝わるよう、上のコードでは失敗を独自の例外に変えて投げ直しています。画面フローから呼んでいれば、この例外はフローのエラーとして表示されます。
差出人は指定しなければ実行ユーザーになる
setOrgWideEmailAddressIdは必須ではありません。指定しなければ、差出人は処理を実行したユーザーのメールアドレスになります。「〇〇システムからの通知」のように差出人をそろえたいときだけ、組織のメールアドレスを登録して指定します。
⚠️ ゲストユーザーから送る場合は話が別です。公式リファレンスは「If you're using Apex to send emails from the guest user, set the sender to the verified org-wide email address or the emails are blocked(ゲストユーザーからApexでメールを送る場合は、検証済みの組織のメールアドレスを差出人に設定しないとブロックされます)」としています。
なお、組織のメールアドレスの表示名とsetSenderDisplayNameは併用できません。公式リファレンスは「The object's DisplayName field cannot be set if the setSenderDisplayName field is already set」と書いています。
画面フローとクイックアクションを作る
新規のフローを作成する
設定の「フロー」から「新規フロー」を選び、「画面フロー」を選択します。
アクション要素を配置する
要素の一覧から「アクション」を選び、先ほどの
NotifyByEmailActionを検索して配置します。入力にはレコードIDを渡します。フローを保存してアクティブにする
フロー名を付けて保存し、アクティブ化します。
クイックアクションを作成する
対象オブジェクトの「ボタン、リンク、アクション」から新規アクションを作成し、アクションの種類に「フロー」、フローに先ほど作ったフローを指定します。
ページレイアウトに配置する
Lightningレコードページの「アクション」領域に、作成したクイックアクションを追加します。
送信数の上限を確認する
Apexからのメール送信には、公式ドキュメントに書かれている上限があります。ボタンを押せば何通でも飛ぶわけではありません。
| 上限 | 数値 | 何にかかるか |
|---|---|---|
1トランザクションあたりのsendEmailの実行回数 | 10回 | 同期・非同期のApex共通 |
| 1通あたりのTo・Cc・Bccの合計宛先数 | 150件 | SingleEmailMessage1件ごと |
| 1日あたりの外部メールアドレス数 | 5,000件 | ライセンスを持つ組織ごと・GMT基準 |
| Developer Edition・試用組織の1日あたりの宛先数 | 50件 | 1通あたりは15件まで |
⚠️ 1日あたりの上限は、組織全体で共有されます。このアクション専用の枠ではないので、他の自動化からのメール送信と合算されます。Spring '19以降に作成された組織では、メールアラート・シンプルなメールアクション・フローの「メール送信」アクション・REST APIからの送信も、同じ日次の枠で数えられます。
⭐ 社内ユーザー宛は数え方が違います。公式ドキュメントは「If you use SingleEmailMessage to email your org's internal users, specifying the user's ID in setTargetObjectId means the email doesn't count toward the daily limit(社内ユーザー宛にSingleEmailMessageを使う場合、ユーザーのIDをsetTargetObjectIdに指定すれば、そのメールは日次上限に数えられません)」としています。社内通知が多い組織では、ここで枠の消費が大きく変わります。
上限に達すると、送信は拒否されます。SOAP APIのリファレンスは「When this limit is reached, sendEmail() calls using SingleEmailMessage are rejected, and the user receives a SINGLE_EMAIL_LIMIT_EXCEEDED error code(上限に達するとSingleEmailMessageを使ったsendEmail()の呼び出しは拒否され、SINGLE_EMAIL_LIMIT_EXCEEDEDのエラーコードが返ります)」と書いています。
動作確認
確認は、レコードを1件作るところからはじめます。対象オブジェクトの新規作成画面で、名称だけ入れて保存します。
保存すると詳細画面に切り替わり、置いたボタンがレコード名の右に並びます。ここを押すと、Apexの送信処理が走ります。
押して少し待つと、Apexで組み立てた文面がそのままメールとして届きます。件名・本文・改行が、コードに書いたとおりになっているかを見ます。
送信に失敗すると、エラーが返ります。SINGLE_EMAIL_LIMIT_EXCEEDEDは、その日の送信枠を使い切ったという意味です。枠は翌日GMTで戻るので、時間を置いてから試し直します。
画面フローで作った場合、このエラーはフローのエラー画面として出ます。⚠️ エラーの文面を読まずに「押しても何も起きない」で止めないでください。上限・宛先の書式・差出人のどれが原因かは、getStatusCode()の値で切り分けられます。
ここで間違えやすい
| 間違い | 何が起きるか |
|---|---|
OnClick JavaScriptのカスタムボタンをそのまま使う | Lightning Experienceでは押しても何も起きません |
WebServiceメソッドをそのままアクションから呼ぼうとする | @InvocableMethodを付けた別のメソッドとして作り直しが必要です |
@InvocableMethodの引数を単一の値にする | 入力も出力もリストで受け渡します。単一の値では登録できません |
1つのクラスに@InvocableMethodを2つ書く | @InvocableMethodを付けられるメソッドは1クラスに1つだけです |
| 件名だけ設定して本文を入れない | setTemplateId・setHtmlBody・setPlainTextBodyのいずれも無いと送信できません |
Messaging.sendEmailの戻り値を見ない | 失敗が握りつぶされ、送信できていないのに成功したように見えます |
確認した環境
- 2026年9月 / Salesforce Summer '26(APIバージョン67.0)時点で、JavaScriptカスタムボタンの制限・
SingleEmailMessageの仕様・SendEmailResultの戻り値・メール送信の上限を公式ドキュメントで確認しています。なお Apex 開発者ガイドとリファレンスガイドは、次期リリースの Winter '27(APIバージョン68.0)の表示に切り替わっていました。上記の項目について、この2つの版で記述の差はありません - 掲載している画面は2021年当時のSalesforce Classicのものです。現在のLightning Experienceの画面とは異なります
まとめ
OnClick JavaScriptのカスタムボタンは、Lightning Experienceでは動きません- 現行のやり方は、画面フローかLWCのクイックアクションからApexを呼び出す形です
- テンプレートを使わないときは、
setPlainTextBodyかsetHtmlBodyのどちらかで本文を渡します - アクションから呼ぶApexは
@InvocableMethodを付け、入力も出力もリストで受け渡します Messaging.sendEmailの戻り値SendEmailResultを見て、失敗を握りつぶさないようにします- 送信数には、1トランザクション10回・1通150宛先・1日5,000件(外部アドレス)という上限があります
- 社内ユーザー宛に
setTargetObjectIdでIDを指定したメールは、1日あたりの上限に数えられません
参考:当時の画面
記事を最初に書いた当時の画面です。いまの手順と違うところは、各画像の説明に書いています。
Salesforceの導入・運用についてご相談ください
導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。