Apex

ApexでAmazon S3のファイルを削除するときの注意

S3のファイルは消えたのか、もともと無かったのか、レスポンスだけでは見分けがつきません。DELETEリクエストの組み立て方と、複数件まとめて消すときにコールアウト回数へどう当たるかを説明します。

2023.02.24

なぜ削除もコールアウトで完結させるのか

Salesforce側のレコードとS3側のファイルを紐づけて運用していると、レコードを削除したりファイルを差し替えたりするたびに、S3側にも不要になったファイルが残ります。コンソールから手作業で消すのは、件数が増えると追いつきません。Apexからのコールアウト1回で、対象のファイルだけを指定して消せます。

用意するもの

用意するもの内容
S3バケット削除対象のファイルが置かれている先。本記事では example-bucket を使います
Salesforce側の認証経路S3への発信を許可し署名を作る仕組み
削除対象を特定する情報Salesforce側のContentVersionに記録した、S3側のファイル名(キー)
S3のバケット作成画面。バケット名の入力欄が赤枠で示され、リージョンは東京
保存先を作る画面です。バケット名の入力欄と、リージョンの選択位置を見てください。
S3のバケット一覧画面。右上の作成ボタンが赤枠で示されている
バケットの一覧です。新しく作るときは、右上の赤枠のボタンから始めます。
バケット作成フォームの全体。名前の入力欄とリージョンの選択欄が並ぶ
作成フォームです。名前は全世界で重複できない点と、リージョンの指定を見てください。
バケット作成フォームの最下部。右下の作成ボタンが赤枠で示されている
入力し終えたら、画面の一番下にある赤枠のボタンで確定します。
作成されたバケットが一覧に1行追加された状態
作成後の一覧です。作った保存先が1行増えているところを見てください。

認証経路の考え方はアップロード・読み込みと同じです。アクセスキーとシークレットは、AWS Signature Version 4に対応した外部ログイン情報に保存し、Apexのコードへは書きません。以降のコード例は、DELETEリクエストの署名の仕組みを示すために自前でHMAC署名を組み立てていますが、実運用ではこの部分をSalesforceの外部ログイン情報に任せます。

削除対象のキーを特定する

S3上のファイル名(キー)を、アップロード時にContentVersionのどこかへ記録しておく必要があります。カスタム項目を1つ用意し、アップロードが成功した時点でキーを書き込んでおくと、削除のときにそのまま使えます。

SELECT Id, Title, S3_Object_Key__c
FROM ContentVersion
WHERE ContentDocumentId IN (
  SELECT ContentDocumentId FROM ContentDocumentLink WHERE LinkedEntityId = :accountId
)
ORDER BY CreatedDate DESC
LIMIT 1

取引先に紐づく最新のファイルから、S3上のキーを保持したカスタム項目を取得します

以前の書き方ではAttachmentにキーを持たせていましたが、Attachmentは新規のファイル添付に使われなくなったため、ContentVersion側のカスタム項目で持ちます。

Apexから削除する

DELETEメソッドには送るボディがありません。署名文字列にも空のボディぶんが入ります。

public with sharing class S3FileDeleter {
  public static Integer deleteFromS3(String objectKey, String accessKey, String secretKey) {
    String bucketName = 'example-bucket';
    String host = bucketName + '.s3.ap-northeast-1.amazonaws.com';
    String encodedKey = EncodingUtil.urlEncode(objectKey, 'UTF-8');
    String dateHeader = Datetime.now().formatGMT('EEE, dd MMM yyyy HH:mm:ss z');

    String stringToSign = 'DELETE\n\n\n' + dateHeader + '\n' + '/' + bucketName + '/' + encodedKey;
    Blob mac = Crypto.generateMac('HMACSHA1', Blob.valueOf(stringToSign), Blob.valueOf(secretKey));
    String authHeader = 'AWS' + ' ' + accessKey + ':' + EncodingUtil.base64Encode(mac);

    HttpRequest req = new HttpRequest();
    req.setMethod('DELETE');
    req.setEndpoint('https://' + host + '/' + encodedKey);
    req.setHeader('Host', host);
    req.setHeader('Date', dateHeader);
    req.setHeader('Authorization', authHeader);

    HttpResponse res = new Http().send(req);
    return res.getStatusCode();
  }
}

指定したキーのファイルをS3から削除します。対象が無くても例外にはならず、ステータスコードで結果を返します

削除結果の読み方

S3のファイル一覧。test.pngが1件あり、件数が1と表示されている
削除する前のS3です。対象のファイルが1件ある状態を見てください。
S3のファイル一覧。件数が0になりファイルが無い旨が赤枠で示されている
削除した後のS3です。件数が0になったことを、この画面で確かめます。

AWSの公式リファレンスによると、削除に成功すると204 No Contentが返ります。この204は、対象のキーがもともと存在しなかった場合にも同じく返ります。つまりS3の削除は、2回続けて同じキーを消そうとしても2回目もエラーにならない、べき等な作りになっています。「消えたこと」を確認したいときは、削除の前に一覧やヘッダー確認(HEAD)で存在を確かめておくか、削除後に別途一覧を取り直して確認します。ステータスコードが204だからといって、削除前にファイルが実在したとは限りません。

複数件まとめて消すときの注意

トリガーやフローの一括処理から、レコードごとに1回ずつ削除のコールアウトを送る形にすると、対象が増えたときに1トランザクションあたり最大100回というコールアウトの上限にすぐ当たります。トリガーの中からは同期のコールアウトを送れないため、削除処理はその場では行わず、キューアブルやバッチApexに渡して分割します。トリガーの中で直接コールアウトを送らないのは、削除に限らずコールアウト全般に共通する注意点です。

ここで間違えやすい

間違い何が起きるか
204が返ったことを「削除できた」の確認にする対象が最初から無かった場合も204が返るため、実在の確認にはなりません
トリガーから1レコードずつ削除のコールアウトを直接送るトリガーからは同期のコールアウトを送れず、実行時エラーになります。非同期処理に渡します
Attachment側にS3のキーを持たせたままにするAttachmentは新規のファイル添付に使われません。ContentVersion側へ移します
DELETEのボディに何か渡そうとするS3のDELETEはボディを使いません。署名文字列側の扱いを間違えると403になります
アクセスキーとシークレットをApexの文字列に直書きする外部ログイン情報に置けば、コードには一切残りません

確認した環境

  • 2026年9月/Salesforce Summer '26(APIバージョン67.0)時点の公式ドキュメントで、コールアウトの制限と指定ログイン情報の仕様を確認しています
  • S3のDeleteObjectが返すステータスコードの仕様は、AWSの公式リファレンスで確認しています

まとめ

  • S3のファイル削除は、対象のキーを指定したDELETEリクエスト1回で完結します
  • 削除成功は204で返りますが、対象が無かった場合も同じく204です。存在確認には使えません
  • 複数件をまとめて消すときは、トリガーから直接コールアウトを送らず、キューアブルやバッチApexに分けます
  • 削除対象のキーはAttachmentではなくContentVersion側のカスタム項目で管理します
  • アクセスキーとシークレットは外部ログイン情報に置き、Apexのコードには書きません

参考:当時の画面

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

設定のリモートサイト一覧画面。新規作成のリンクが赤枠で示されている
外部への発信先を登録する画面です。
リモートサイトの登録フォーム。名前とURLの入力欄が赤枠で示されている
登録フォームです。名前と発信先URLの2欄を埋めます。
登録後のリモートサイト詳細画面。登録した名前とURLが表示されている
登録後の確認画面です。
カスタムボタンの詳細画面。表示ラベルと動作、呼び出すページの設定が並ぶ
画面から実行するためのボタン定義です。
取引先の詳細画面。ボタンの並びの右端にファイル削除が赤枠で示されている
レコード画面のボタン列です。赤枠の削除ボタンから実行します。
処理後に表示される閉じるボタンだけの小さな画面
実行後の画面です。閉じるボタンで元のレコード画面に戻ります。
取引先のメモと添付ファイルの一覧。添付ファイルが1件登録されている
Salesforce側の添付です。どのファイルを消すかは、この行から特定します。

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

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