アップロード済みファイルを添付してメールを送る実装
取引先にアップロードしたファイルを、メール送信時に取り直してファイル名を変え、SingleEmailMessageに添付して送ります。添付サイズと送信件数の上限を説明します。
シナリオ
取引先にファイルをアップロードする
あらかじめ取引先の「ファイル」関連リストへ、メールに添付したいファイルをアップロードしておきます。
メール送信時にファイルを取得する
アップロード済みのContentVersionを取得し、ファイル名を変更したうえでメールに添付する準備をします。
メールを送信する
取引先のリストビューから対象レコードを選び、ボタンからメールを送信します。
プログラム
SendEmailAccountFile.page
<apex:page standardController="Account" recordSetVar="records" lightningStyleSheets="true" extensions="SendEmailAccountFile"> <apex:pageMessages id="pageMessages" /> <apex:form > <apex:pageBlock title="send test email" rendered="{!isNotEmpty}"> <apex:pageBlockButtons location="top" rendered="{!isNotEmpty}"> <apex:commandButton value="send" action="{!sendEmail}" reRender="pageMessages" disabled="{!NOT(isSendable)}" /> <apex:commandButton value="cancel" action="{!cancel}"/> </apex:pageBlockButtons> <apex:pageBlockTable value="{!accounts}" var="account" rendered="{!isNotEmpty}"> <apex:column headerValue="Name" value="{!account.Name}" /> </apex:pageBlockTable> </apex:pageBlock> </apex:form> </apex:page>
取引先のリストビューから複数選択して呼び出すVisualforceページです。エラーはpageMessagesにまとめて表示します。
SendEmailAccountFile.cls
public with sharing class SendEmailAccountFile { private final Pagereference returnRef; private final List<Account> accounts; public SendEmailAccountFile(ApexPages.StandardSetController controller) { Set<Id> accountIds = (new Map<Id, sObject>(controller.getSelected())).keySet(); if (accountIds.size() >= 1 && accountIds.size() <= Limits.getLimitEmailInvocations()) { accounts = [SELECT Name, Email__c FROM Account WHERE Id IN :accountIds AND Email__c != null]; } if (accounts?.size() < 1) { ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.INFO, 'Email destination does not exist')); } returnRef = null; } public List<Account> getaccounts() { return accounts; } public Boolean getIsNotEmpty() { return accounts?.size() > 0; } public Boolean getIsSendable() { return !ApexPages.hasMessages(); } public class Email { public final String fromAddress; public final String toAddress; public final String subject; public final String bodyHtml; public final String bodyText; public final List<Id> contentVersionIds; public final ContentVersion cv; public Email(Id accountId) { Account account = [SELECT Name, Email__c FROM Account WHERE Id = :accountId AND Email__c != null LIMIT 1 ]; User user = [SELECT Name FROM User WHERE Id = :UserInfo.getUserId() ]; this.fromAddress = UserInfo.getUserEmail(); this.toAddress = account.Email__c; this.subject = 'test'; String[] bodyParts = new List<String> { '', 'test', '' }; String body = String.join(bodyParts, '\n'); this.bodyHtml = body.replace('\n', '<br/>'); this.bodyText = body.stripHtmlTags(); Set<Id> linkedEntityIds = new Set<Id>{ account.Id }; List<ContentDocumentLink> links = [SELECT Id, LinkedEntityId, ContentDocumentId, ContentDocument.LatestPublishedVersionId, ContentDocument.title FROM ContentDocumentLink WHERE LinkedEntityId IN :LinkedEntityIds ]; List<Id> documentIds = new List<Id>(); for (ContentDocumentLink link : links) {documentIds.add(link.ContentDocumentId);} List<ContentVersion> targetCVs = new List<ContentVersion>(); String fileName = 'test1'; String fileExtension = 'pdf'; targetCVs = [SELECT Id, Title, FileType, FileExtension, VersionNumber, VersionData, ContentDocumentId FROM ContentVersion WHERE IsLatest = true AND ContentDocumentId IN :documentIds AND Title = :fileName AND FileExtension = :fileExtension ORDER BY CreatedDate Desc ]; this.contentVersionIds = new List<Id>(); if(targetCVs.size() > 0){ this.cv = new ContentVersion(Title = 'test', PathOnClient = 'test.pdf', VersionData = targetCVs.get(0).VersionData, OwnerId = UserInfo.getUserId()); } } } public Pagereference SendEmail() { try { for (Account account : accounts) { Email email = new Email(account.Id); if(email.cv != null){ insert email.cv; email.contentVersionIds.add(email.cv.Id); } Messaging.SingleEmailMessage singleEmail = new Messaging.SingleEmailMessage(); singleEmail.setToAddresses(new List<String>{ email.toAddress }); if(email.contentVersionIds.size() > 0){ singleEmail.setSubject(email.subject); singleEmail.setHtmlBody(email.bodyHtml); singleEmail.setPlainTextBody(email.bodyHtml.stripHtmlTags()); singleEmail.setEntityAttachments(email.contentVersionIds); singleEmail.setBccSender(true); Messaging.sendEmail(new List<Messaging.SingleEmailMessage>{ singleEmail }); } } } catch(Exception e) { ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL, e.getStackTraceString())); return null; } return null; } }
選択した取引先ごとに、アップロード済みのファイルを取得してメールへ添付し送信します。添付が見つからない取引先へはメールを送りません。
内部のEmailクラスが、取引先に紐づくContentDocumentLinkからContentVersionを検索し、TitleとFileExtensionが一致する最新版を取得しています。そのうえで、VersionData(ファイルの中身)だけを取り出し、TitleとPathOnClientをtest.pdfに変えた新しいContentVersionを作っています。setEntityAttachmentsでファイル名を変えて送るには、こうして中身だけを引き継いだコピーを作ります。作ったコピーは、組織にファイルとして残ります。レコードを増やさずに名前だけ変えたいときは、Messaging.EmailFileAttachmentのsetFileNameで添付ファイル名を指定する方法もあります。
Messaging.SingleEmailMessageへの添付はsetEntityAttachmentsを使っています。Document・ContentVersion・AttachmentのIdをまとめて渡せるメソッドで、Messaging.EmailFileAttachmentのようにファイルの中身を改めてセットし直す必要がありません。
setEntityAttachmentsとsetFileAttachmentsは、どちらも渡した添付ファイルの合計サイズが10MBを超えないようにする必要があります。1件あたりの上限ではなく、1通のメールに含める添付ファイルすべての合計です。添付サイズとメール送信数の上限
添付ファイルの合計サイズの上限に加えて、送信数そのものにも上限があります。ApexトリガーやApexクラス、APIからSingleEmailMessageで送るメールは、1日あたり5,000件の受信者数までという上限が組織単位でかかります。To・Cc・Bccに入れたアドレスはすべて合算されます。Trial組織はこれより少なく、既定は15件です。
このコードは、選択した取引先の数だけメールを1通ずつ送ります。リストビューで一度に数百件を選んで実行すると、その件数がそのまま受信者数としてカウントに積み上がります。他の自動化(ワークフローのメールアラートやFlowの送信メール)と合わせて、その日の上限に近づいていないかを意識してください。
ボタン作成
取引先のボタン設定を開く
オブジェクトマネージャから取引先を選び、「ボタン、リンク、およびアクション」を開きます。
新規ボタンを作成する
「新規ボタンまたはリンク」を押し、ラベルに「取引先添付ファイルメール送信」のような名前を入力します。表示の種類は「リストボタン」、コンテンツソースは「Visualforceページ」を選びます。
対象のページを選んで保存する
Visualforceページの一覧からSendEmailAccountFileを選び、保存します。
リストビュー
作成したボタンは、そのままでは画面に表示されません。検索レイアウトへ追加します。
検索レイアウトを開く
オブジェクトマネージャの取引先から「検索レイアウト」を開き、「取引先リストビュー」の編集を選びます。
作成したボタンを追加する
「使用可能なボタン」の一覧から作成したボタンを選び、「選択済みのボタン」へ移動します。
保存する
保存すると、取引先のリストビュー上部にボタンが表示されるようになります。
動作確認
ファイルをアップロードする
取引先タブを開き、メール送信対象のレコードで「test1.pdf」というファイルをアップロードします。
対象レコードを選ぶ
取引先のリストビューから、ファイルをアップロードした取引先にチェックを入れます。
ボタンを押す
作成した「取引先添付ファイルメール送信」ボタンを押します。
送信する
対象の取引先名が一覧に表示されるので、内容を確認して「send」ボタンを押します。
受信を確認する
取引先のEmail__c項目に設定したメールアドレスで受信を確認します。添付ファイル名が「test.pdf」に変わっていることを確認します。
ここで間違えやすい
| 間違い | 何が起きるか |
|---|---|
| 添付ファイルの合計サイズが10MBを超える | setEntityAttachmentsを含めたメール送信が失敗します |
| 対象の取引先にファイルが1件もない | contentVersionIdsが空のままなので、メール自体が送信されません(エラーにもなりません) |
| リストビューで大量の取引先を選ぶ | 選択件数がそのままその日の送信数にカウントされ、5,000件の上限に近づきます |
Email__cが空の取引先を選ぶ | コンストラクタの絞り込みで対象から外れ、メールは送られません |
| ファイル名や拡張子の条件を変えずに流用する | TitleとFileExtensionの一致でファイルを探しているため、条件に合うファイルがないと添付されません |
確認した環境
- 2026年9月 / Salesforce Summer '26(APIバージョン67.0)時点の公式ドキュメントで、ContentVersion・ContentDocumentLinkの仕様、setEntityAttachmentsの添付サイズ上限、SingleEmailMessageの1日あたりの送信数上限を確認しています
まとめ
- 添付ファイルの保存と取得にはContentVersionとContentDocumentLinkを使います。Attachmentは使いません
setEntityAttachmentsでファイル名を変えて送る場合は、元のVersionDataだけを引き継いだ新しいContentVersionを作ります。EmailFileAttachmentのsetFileNameでも名前を変えられますsetEntityAttachmentsはContentVersionなどのIdを直接渡せます。添付ファイルの合計サイズは10MBまでですSingleEmailMessageによるメール送信は、1日あたり5,000件の受信者数までです。To・Cc・Bccの合計でカウントされます- 添付ファイルが見つからないとメールは送信されません。エラーにはならないため気づきにくい挙動です
Salesforceの導入・運用についてご相談ください
導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。