EC-CUBEには公式のメルマガ管理プラグインがあり、会員を条件で絞って一斉配信できます。ただし送信するのはEC-CUBEのサーバー自身です。件数が増えるとプロセスを長く占有しますし、到達率やバウンスの追跡はEC-CUBEの外側の話になります。
そこで本記事では、宛先の抽出はEC-CUBEに任せ、配信そのものはblastengineに寄せる構成を、AIエージェント(Claude Code)から扱います。使うのはblastengine MCPサーバーです。
blastengine MCPの導入手順は導入記事、ツールの一覧と入出力は全15ツールの検証記事にまとめてあります。本記事はその続きとして、実際のECサイト運用に組み込むところを扱います。手順はEC-CUBE 4.3.1とblastengine MCP v0.2.1で実際に配信するところまで確認しました。
全体構成
┌──────────────────────────────┐
│ Claude Code │
└──────────────┬───────────────┘
│ stdio
┌──────────────▼───────────────┐
│ blastengine MCP │
└──────────────┬───────────────┘
│ blastengine API
┌──────────────▼───────────────┐
│ blastengine │
└──────────────────────────────┘
▲
│ 宛先CSV(パスだけを渡す)
┌──────────────┴───────────────┐
│ EC-CUBE 4(会員管理) │
└──────────────────────────────┘
EC-CUBEは顧客データの正、blastengineは配信基盤、AIはその間の段取りと文章づくり、という並びです。
何をAIに見せて、何を見せないか
先に設計方針を決めておきます。ここを曖昧にしたまま「AIにメルマガを送らせる」と、会員のメールアドレスがまるごとLLMのコンテキストに乗ります。
| データ | 置き場所 | AIに渡すか |
|---|---|---|
| 会員のメールアドレス一覧 | ローカルのCSV | 渡さない(ファイルパスだけ渡す) |
| メルマガの件名・本文 | AIが作る | 渡す |
| 配信結果・エラーログ | blastengine | 渡す(宛先アドレスを含む点に注意) |
blastengine MCPのblastengine_bulk_import_recipients_csvは、ローカルのCSVパス(csv_path)を受け取ってblastengineへアップロードするツールです。CSVの中身はMCPサーバーのプロセス内で読まれるだけで、LLMのコンテキストには入りません。宛先の実体をAIに見せずに一斉配信できるのは、この設計のおかげです。
一方、配信後に使うblastengine_mail_results_listは宛先ごとのログを返すので、こちらは結果としてメールアドレスがAIに渡ります。バウンス分析をさせるなら、そこは織り込んでおきます。
準備:blastengine MCPを一斉配信モードで登録する
一斉配信とCSVインポートを使うので、BLASTENGINE_ENABLE_BULKとBLASTENGINE_ENABLE_CSV_IMPORTを有効にします。トランザクション送信は今回使わないのでfalseのままにしておきます。
claude mcp add blastengine \
--scope local \
--env BLASTENGINE_LOGIN_ID="$BLASTENGINE_LOGIN_ID" \
--env BLASTENGINE_API_KEY="$BLASTENGINE_API_KEY" \
--env BLASTENGINE_ENABLE_SEND=false \
--env BLASTENGINE_ENABLE_BULK=true \
--env BLASTENGINE_ENABLE_CSV_IMPORT=true \
-- node /absolute/path/to/blastengine-mcp/dist/index.js
有効化フラグは「ツール一覧に出るかどうか」ではなく実行時のゲートとして効きます。BLASTENGINE_ENABLE_BULK=falseのままblastengine_bulk_beginを呼ぶと、下書きは作られずにこう返ります。
{
"code": "bulk_disabled",
"message": "BLASTENGINE_ENABLE_BULK=true is required for bulk delivery tools",
"retryable": false
}
不要なフラグを切っておくほど事故が減るので、使う機能だけを開けます。なお、サーバーはNode.js 22.15以上が必要です(package.jsonのengines)。
手順1:会員CSVを出す
メルマガ管理プラグイン(MailMagazine42)を入れると、会員エンティティにplg_mailmagazine_flg列が追加され、管理画面の会員検索にメールマガジン受信設定の条件が増えます。フロント側では会員登録時とマイページの登録内容変更で、会員自身が受け取る/希望しないを選べます。
値は1が「受け取る」、0が「希望しない」です(プラグインのフォーム定義で確認できます)。列のデフォルトは0なので、プラグインを後から入れた場合、既存会員は全員0から始まります。
配信対象は、この受信設定で絞り込みます。
- 管理画面 → 会員管理 で、メールマガジン受信設定が「受け取る」の会員を検索します。必要なら購入回数や最終購入日でさらに絞ります。
- 検索結果の画面からCSVをダウンロードします。出力される項目は 設定 → 店舗設定 → CSV出力項目設定 で調整できます。メールアドレスの列名は「メールアドレス」です。
手順2:blastengine用のCSVに変換する
blastengineの一括登録用CSVは、ヘッダーの1項目めがemailである必要があります。文字コードはUTF-8、差し込みコードの上限は50件、ファイルサイズは256MBまでです。
ここで引っかかるのが文字コードです。EC-CUBEのCSV出力は既定でSJIS-win(eccube_csv_export_encoding)なので、そのまま渡すと文字化けするか、取り込みに失敗します。列の並べ替えとあわせて変換します。
import csv
src = "customer.csv" # EC-CUBEからダウンロードしたCSV(既定は SJIS-win)
dst = "recipients.csv" # blastengineに渡すCSV(UTF-8)
email_col = "メールアドレス"
with open(src, encoding="cp932", newline="") as f, \
open(dst, "w", encoding="utf-8", newline="") as g:
reader = csv.DictReader(f)
writer = csv.writer(g)
writer.writerow(["email"])
seen = set()
for row in reader:
addr = (row.get(email_col) or "").strip()
if addr and addr not in seen:
seen.add(addr)
writer.writerow([addr])
eccube_csv_export_encodingをUTF-8に変更している環境なら、読み込み側をutf-8-sigにしてください。どちらか分からないときはfileコマンドか、先頭数バイトを見て判断します。
出力は次のようになります。改行コードはCRLFのままで問題なく取り込めました。
email
member1@example.com
member2@example.com
差し込みを使う場合は、2列目以降に差し込みコードの列を足します。本文側では__prop1__のように記述した箇所が置き換わります。キーに使えるのはASCIIの英数字だけです。CSVのヘッダー名の書式は、blastengineの管理画面からダウンロードできるサンプルCSVで実際の形を確認してから決めてください。
手順3:一斉配信を組み立てる
blastengineの一斉配信は、下書きの作成 → 宛先の登録 → 内容の確認 → 確定、という多段構成です。ツールもその順に対応しています。
| 順序 | ツール | 内容 |
|---|---|---|
| 1 | blastengine_bulk_begin |
送信元・件名・本文を指定して下書き(EDIT状態)を作る |
| 2 | blastengine_bulk_import_recipients_csv |
ローカルCSVから宛先を一括登録する |
| 3 | blastengine_bulk_import_status |
インポートジョブの状況と件数を確認する |
| 4 | blastengine_bulk_preview |
件名・送信元・宛先数・本文サマリを人が確認する |
| 5 |
blastengine_bulk_commit_immediate / blastengine_bulk_commit_scheduled
|
即時または予約で確定する |
Claude Codeへの指示はこうなります。
blastengineで一斉配信の下書きを作って。
送信元は news@example.com(表示名は「EXAMPLE STORE」)、
件名は「【EXAMPLE STORE】今週の新着商品のご案内」。
本文はこの内容で(略)。
宛先は ./recipients.csv から取り込んで。即時配信はしないで。
bulk_beginは下書きのdelivery_idを返します。続くCSVインポートはジョブとして走り、job_idが返ります。
{ "job_id": 42, "immediate": false, "warning": "CSV import will not trigger immediate delivery." }
immediateは必ずfalseのままにします。trueにするとインポート完了と同時に配信が走るため、内容を確認する前に送信が終わります。
インポートの完了はbulk_import_statusで確認します。
{ "total_count": 1, "percentage": 100, "success_count": 1, "failed_count": 0, "status": "FINISHED" }
failed_countが0でない場合は、blastengine_bulk_import_error_downloadでエラー内容のzipをローカルに保存して中身を確認します。
宛先が50件以下ならblastengine_bulk_update_recipientsでも登録できますが、既定の上限は50件(BLASTENGINE_BULK_MAX_RECIPIENTS)です。メルマガの規模ならCSVインポート一択になります。
続いてプレビューします。
{
"delivery_id": 1258,
"subject": "【EXAMPLE STORE】今週の新着商品のご案内",
"from": { "email": "news@example.com", "name": "EXAMPLE STORE" },
"recipient_count": 1,
"body_summary": "EXAMPLE STORE をご利用いただきありがとうございます。 今週の新着商品から...",
"note": "This preview is informational only. It does not authorize or block commit. Bulk commit tools execute directly when BLASTENGINE_ENABLE_BULK=true."
}
noteにあるとおり、プレビューは情報を見せるだけで、確定を承認したり止めたりする効果はありません。ENABLE_BULKが有効なら、確定ツールはプレビューを経由しなくても実行できます。送信の可否を判断するのは人間側だと理解しておいてください。
内容に問題がなければ確定します。予約配信にする場合、時刻はタイムゾーンオフセット付きのISO 8601で指定します。「明日の10時」のような相対表現は受け付けないので、具体的な時刻に解決してから渡します。
配信ID 1258 を 2026-09-01T10:00:00+09:00 に予約配信で確定して。
手順4:配信結果をEC-CUBE側に戻す
配信後は、宛先ごとのログを確認します。
{
"delivery_id": 1258,
"maillog_id": 1275,
"delivery_type": "BULK",
"email": "member1@example.com",
"status": "SENT",
"last_response_code": "250",
"last_response_message": "配信に成功しました",
"open_time": null
}
ハードバウンス(HARDERROR)したアドレスは、EC-CUBE側でメールマガジン受信設定を「希望しない」(0)に変更します。会員データの更新なので、管理画面から直すか、bin/consoleで自作コマンドを書くことになります。件数が多いなら後者です。
配信情報と配信ログは、配信開始から62日で削除されます。長期の到達率推移を残したいなら、この期間内に自分側へ吸い出しておく必要があります。
エージェントに任せたときに起きること
ここまでの手順をClaude Codeに指示して実際に回すと、ツールを直接叩くのとは違う挙動がいくつか出ます。実際に確認できたものを挙げておきます。
まず、送信のような取り消せない操作の手前で、エージェントは自分から止まって確認を求めてきます。対話で使っている限りはこれが承認ゲートとして働くので都合がいいのですが、非対話(claude -p)で回すときは、止まったまま何も起きずに終わります。バッチに載せるなら、プロンプトの中で「確認は取れている、追加の確認は不要」と明示的に承認を与える必要があります。
次に、許可していないツールを呼ぼうとすることがあります。今回は件名の丸括弧を半角から全角に直そうとして、許可リストに入れていないblastengine_bulk_update_recipientsを呼び、拒否されて止まりました。許可リストを絞るのは正しいのですが、絞った結果どこで詰まるかは事前に読み切れません。実行結果のpermission_denialsを必ず見てください。
そして、指示していない細部をエージェントが整えようとすることがあります。上の件名の例がまさにそれです。件名や本文の表記をこちらで確定させたい場合は、「表記はこのままで、修正しないでください」と明示するほうが確実です。
宛先を登録していない下書きに対してblastengine_bulk_previewを呼ぶと、件名も宛先件数もnullで返ります。プレビューが空に見えたら、CSVインポートがまだ終わっていないと考えてください。
そして最も注意したいのが、送信ツールが成功を返しても、それは「blastengineが受け付けた」という意味でしかない点です。配信可能アドレスに登録していない宛先の場合、ツールはdelivery_idを返して成功したように見えますが、実際には配信ログ側でDROP、レスポンスコード554(rejection)になります。エージェントは戻り値だけを見て「送信しました」と報告するので、到達の確認まで指示に含めておいてください。
送信したら、配信ログでstatusとレスポンスコードまで確認して報告して。
つまずきやすいポイント
| 症状 | 原因と対処 |
|---|---|
| CSVを取り込むと文字化けする | EC-CUBEのCSV出力は既定でSJIS-win。UTF-8に変換してから渡す |
| CSVインポートが弾かれる | ヘッダー1項目めがemailか、拡張子が.csvかを確認する。URL指定や空ファイルは不可 |
| 宛先が50件で頭打ちになる |
blastengine_bulk_update_recipientsの既定上限。CSVインポートを使う |
| 予約時刻が受け付けられない | オフセット付きのISO 8601が必須。2026-09-01のような日付のみは不可 |
| 送信は成功したのにメールが届かない | 配信可能アドレスに未登録の宛先は、送信ツール側ではエラーにならず配信ログでDROP / 554(rejection)になる。成否は必ず配信ログで確認する(トライアルの登録上限は5件) |
| 送信済みの配信をもう一度確定してしまった |
timeoutが返ることがある。実体は404だが、APIの応答が既定のタイムアウト(30秒)より遅い。BLASTENGINE_TIMEOUT_MSを伸ばすと本来のエラーを確認できる |
| 送信直後に使用量が増えていない |
usage_latest_getの集計には反映のラグがある。送信の成否は配信ログで確認する |
| 62日より前の配信が検索できない | 保管期限による削除。期限内にエクスポートしておく |
| MCPサーバーが起動しない | Node.js 22.15以上が必要。バージョンを確認する |
| 非対話実行でAIが確認を求めて止まる | 送信前にエージェントが承認を求める。プロンプトで明示的に承認を与える |
プレビューがnullばかり返る |
宛先が未登録の下書き。CSVインポートの完了を先に確認する |
発展:EC-CUBE側もMCPでつなぐには
宛先の抽出まで会話で完結させたいなら、EC-CUBE側にもMCPを用意する手があります。EC-CUBEには公式のWeb APIプラグイン(Api42)があり、GraphQLで商品・受注・顧客を取得できます。エンドポイントはhttps://<ホスト名>/api、認可はAuthorization Code Flowのみ、スコープはreadとwriteの2つです。
GraphQLをそのままMCPとして喋らせるなら、汎用のmcp-graphqlが使えます。Mutationは既定で無効なので、参照だけならこの設定で足ります。
claude mcp add eccube \
--env ENDPOINT=https://<ホスト名>/api \
--env 'HEADERS={"Authorization":"Bearer <アクセストークン>"}' \
--env ALLOW_MUTATIONS=false \
-- npx -y mcp-graphql
ただし、この構成には先に知っておいたほうがいい制約が2つあります。
1つめは権限の粒度です。EC-CUBEのスコープはリソース単位に分かれていないため、readを渡した時点でAIはcustomersクエリを投げて会員のメールアドレスを取得できます。mcp-graphqlのREADME自身も、絞り込みたいなら自前のMCPを書いてロックダウンするよう勧めています。本番サイトに常時つなぐなら、クエリを固定した自作MCP(独自ツールを追加する記事と同じ要領で書けます)に置き換えるのが安全です。
2つめはアクセストークンの寿命です。Authorization Code Flowで発行されるトークンの有効期限は3600秒なので、HEADERSに静的に書くとすぐ切れます。リフレッシュトークンで更新する仕組みが別に要ります。
なお、筆者の検証環境(Docker上のEC-CUBE 4.3.1にtarballからプラグインを導入)では、このGraphQLエンドポイントを動かすところまで到達できませんでした。プラグインのcomposer依存が自動では入らない、webonyx/graphql-phpの要求バージョンがセキュリティアドバイザリでブロックされる、composerのレシピがOAuthバンドルを二重登録する、といった手当てが続き、最後はOAuthの鍵設定で止まっています。オーナーズストア経由の正規導入なら結果は変わる可能性がありますが、少なくとも「軽く試す」構成ではありません。
宛先の抽出は管理画面のCSVで足りるので、まずは本記事の主線で運用を回し、必要になった段階でEC-CUBE側のMCP化を検討するのが現実的だと思います。
EC-CUBE標準のメルマガ機能との使い分け
すべてをblastengineに寄せる必要はありません。判断の目安を挙げておきます。
- 会員数が少なく、テンプレート管理と配信履歴がEC-CUBE内で完結していれば十分な場合は、メルマガ管理プラグインのままで問題ありません。
- 配信件数が増えてEC-CUBEのプロセスを長く占有するようになった、あるいはバウンスの内訳やレスポンスコードまで追いたくなったら、配信基盤を外に出す判断になります。
- 注文確認メールなどのシステムメールは今回の構成とは別の話で、そちらはEC-CUBEの
MAILER_DSNをblastengineのSMTPに向けるほうが素直です。blastengine側の配信一覧にはSMTP種別として結果が入るので、ログの確認方法は共通化できます。
まとめ
EC-CUBEのメルマガをblastengine MCPに寄せる構成のポイントは3つです。
- 宛先の実体はCSVのままにして、blastengine MCPにはパスだけを渡す。会員のメールアドレスをLLMのコンテキストに載せずに一斉配信できる
- EC-CUBEのCSV出力は既定でSJIS-win、blastengineの一括登録CSVはUTF-8で1列目が
email。ここの変換が実務上の最初の関門になる - 一斉配信は下書き → 宛先登録 → プレビュー → 確定の多段。
immediateはfalseを厳守し、プレビューは判断材料であって承認ではないと理解しておく
EC-CUBE側もMCPでつなぐ発展形はありますが、Web APIプラグインの導入コストは小さくありません。まずはCSVで橋渡しする構成から始めるのがおすすめです。
blastengine MCPサーバーへの要望や感想は、以下のアンケートフォームから送ると開発チームに届くみたいです。
https://docs.google.com/forms/d/e/1FAIpQLSdNZ9TswUT3JEv3pMHkJGqOvgmsXuKP1smkJiNEsqbTVDwRyg/viewform
※本記事の手順はEC-CUBE 4.3.1-p1、メルマガ管理プラグイン4.3.0、blastengine MCPサーバー v0.2.1で確認しました。仕様はバージョンにより変わり得ます。