1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

kintone MCPサーバーとblastengine MCPで、顧客への案内メールを送って配信結果をkintoneに書き戻す

1
Posted at

kintoneの標準の通知は、kintoneのユーザーにしか届きません。顧客リストをkintoneで管理していても、そこに載っている社外の担当者へメールを送るには、別の仕組みが必要になります。

サイボウズが公開しているkintoneの公式MCPサーバーと、blastengineのMCPサーバーを1つのエージェントにつなぐと、「kintoneから配信対象の顧客を取り出し、blastengineで1件ずつ案内メールを送り、配信結果をkintoneのレコードに書き戻す」までを会話で回せます。本記事ではこの構成をClaude Codeで組み、実際に送信して書き戻すところまで確認します。

書き戻しで一番大事なのは、何をもって「送れた」と判断するかです。送信APIが受け付けた時点では、まだ届いたとは限りません。実際に動かしてみると、配信情報のステータスがSENTなのに1通も届いていない、というケースがありました。この判定の部分に紙幅を割きます。

※記事中の送信者名と送信元メールアドレスは、掲載用に置き換えています。

blastengine MCPの導入手順は導入記事、各ツールの入出力は全15ツールの検証記事を参照してください。

全体構成

                 ┌──────────────────────────────┐
                 │        Claude Code           │
                 └───────┬──────────────┬───────┘
              stdio      │              │ stdio
          (Docker)     │              │
        ┌────────────────▼───┐      ┌───▼──────────────────┐
        │  kintone MCP       │      │  blastengine MCP     │
        │  (公式Docker)    │      │                      │
        └────────┬───────────┘      └───────────┬──────────┘
                 │ REST API                     │
        ┌────────▼───────────┐      ┌───────────▼──────────┐
        │      kintone       │      │     blastengine      │
        └────────────────────┘      └──────────────────────┘

どちらも手元で動くstdioサーバーです。kintone側は公式のDockerイメージ(ghcr.io/kintone/mcp-server)を使います。npmパッケージ(@kintone/mcp-server)やClaude Desktop用のMCPBファイルも配布されています。

準備1:kintoneの環境と認証方式を決める

検証用のkintone環境は、kintone開発者ライセンスで無料で用意できます。ユーザー5人・ディスク25GBの固定で、開発やテストの目的に限って使える環境です。本番運用には使えません。

kintone MCPサーバーの認証は、パスワード認証かAPIトークン認証のどちらかを選びます。

方式 環境変数 向いている使い方
パスワード認証 KINTONE_USERNAME / KINTONE_PASSWORD アプリの作成など、アプリの設定まで触る場合(アプリを作成するAPIはAPIトークンに対応していない)
APIトークン認証 KINTONE_API_TOKEN(カンマ区切りで最大9個) 既存アプリのレコードを読み書きするだけの場合

両方を指定すると、パスワード認証が優先されます。

パスワード認証では、そのユーザーが触れるものはすべてエージェントからも触れます。本記事では手順1でアプリ自体をClaudeに作らせるのでパスワード認証を使いますが、運用に載せる段階では、対象アプリだけに権限を付けたAPIトークンへ切り替えるのが安全です。APIトークンはアプリごとに発行し、「レコード閲覧」「レコード編集」のように操作単位で権限を付けられます。

準備2:Claude Codeに2つのMCPを登録する

プロジェクト直下の.mcp.jsonに2つのサーバーを書きます。認証情報は直接書かず、${VAR}で環境変数から展開させます。

{
  "mcpServers": {
    "kintone": {
      "type": "stdio",
      "command": "docker",
      "args": ["run", "-i", "--rm",
        "-e", "KINTONE_BASE_URL", "-e", "KINTONE_USERNAME", "-e", "KINTONE_PASSWORD",
        "ghcr.io/kintone/mcp-server:latest"],
      "env": {
        "KINTONE_BASE_URL": "${KINTONE_BASE_URL}",
        "KINTONE_USERNAME": "${KINTONE_USERNAME}",
        "KINTONE_PASSWORD": "${KINTONE_PASSWORD}"
      }
    },
    "blastengine": {
      "type": "stdio",
      "command": "node",
      "args": ["/absolute/path/to/blastengine-mcp/dist/index.js"],
      "env": {
        "BLASTENGINE_LOGIN_ID": "${BLASTENGINE_LOGIN_ID}",
        "BLASTENGINE_API_KEY": "${BLASTENGINE_API_KEY}",
        "BLASTENGINE_ENABLE_SEND": "true",
        "BLASTENGINE_ENABLE_BULK": "false",
        "BLASTENGINE_ENABLE_CSV_IMPORT": "false"
      }
    }
  }
}

KINTONE_BASE_URLはhttps://(サブドメイン).cybozu.comの形です。kintone側はdocker runの-e KINTONE_BASE_URLのように値を書かずに変数名だけを渡し、envで設定した値をコンテナに引き継がせています。

blastengine側は、今回トランザクション配信しか使わないので、BLASTENGINE_ENABLE_SENDだけを有効にしています。一斉配信とCSVインポートは切っておきます。

Claude Codeを起動して/mcpを開き、両方が接続済みになっていれば準備は完了です。

kintone側のツールを把握する

手元のkintone MCPサーバー1.11.2では、28個のツールが提供されていました。今回使うのは次のものです。

用途 ツール
アプリを作る kintone-add-app / kintone-add-form-fields / kintone-deploy-app / kintone-get-app-deploy-status
フィールドを調べる kintone-get-form-fields
レコードを読み書きする kintone-get-records / kintone-add-records / kintone-update-records

このほかに、レコードやフィールド、スペースを削除するツール(kintone-delete-records / kintone-delete-form-fields / kintone-delete-space)もあります。今回の作業では削除は不要なので、後述の「エージェントに渡すツールを絞る」で使えないようにします。

手順1:配信先のアプリをClaudeに作らせる

kintone MCPサーバーはアプリの設定まで扱えるので、配信先を管理するアプリそのものを会話から作れます。

kintoneに「配信先顧客」というアプリを新規作成してください。フィールドは次のとおり。
会社名(文字列1行, 必須, フィールドコード company)、担当者名(文字列1行, contact_name)、
メールアドレス(文字列1行, 必須, email)、配信対象(チェックボックス、選択肢「対象」1つ, target)、
配信ステータス(ドロップダウン 選択肢: 未送信/送信済み/エラー, 初期値 未送信, status)、
配信ID(数値, delivery_id)、送信日時(日時, sent_at)、配信結果メモ(文字列複数行, result_note)。
作成後、運用環境へ反映し、反映完了を確認してアプリIDを報告してください。

エージェントは次の順でツールを呼びました。

  1. kintone-add-appでアプリを作る(返り値は{"app":"1","revision":"2"})
  2. kintone-add-form-fieldsで8つのフィールドをまとめて追加する
  3. kintone-deploy-appで運用環境へ反映する
  4. kintone-get-app-deploy-statusで反映状況を確認する。1回目はPROCESSING、2回目でSUCCESS

kintone-add-appで作ったアプリは、まだ動作テスト環境にあるだけです。ツールの説明にも、運用環境に反映するにはkintone-deploy-appを実行するよう書かれています。指示に「反映完了を確認して」と入れておくと、エージェントがSUCCESSになるまで状況を確認してくれます。

フィールドコードは指示で明示しておくのがおすすめです。後の手順の指示や書き戻しの引数にそのまま出てくるので、短い英数字に決めておくと読みやすく、取り違えも起きにくくなります。

ここまでで6ターン、約0.32ドル(Sonnet)でした。

動作確認用に、ダミーの顧客を5件登録しておきます。配信対象にチェックしたレコードが4件、チェックしていないレコードが1件です。登録はkintone-add-recordsの1回で済みました。

手順2:配信対象のレコードを取り出す

「配信対象にチェックが入っていて、まだ送っていない」レコードを取り出します。

kintoneのアプリID 1(配信先顧客)から、配信対象に「対象」がチェックされていて
配信ステータスが「未送信」のレコードを取得してください。

エージェントはまずkintone-get-form-fieldsでフィールドの型を確認し、そのうえでkintone-get-recordsを次の引数で呼びました。

{
  "app": "1",
  "filters": {
    "inValues": [
      { "field": "target", "values": ["対象"] },
      { "field": "status", "values": ["未送信"] }
    ]
  }
}

kintoneのREST APIを直接使ったことがある人は、target in ("対象") and status in ("未送信")のようなクエリ文字列を思い浮かべるはずです。kintone MCPサーバーのkintone-get-recordsは、クエリ文字列ではなく、構造化されたfiltersで条件を受け取ります。

条件の種類 対応する演算子 主な対象フィールド
textContains like 文字列(1行・複数行)、リッチエディター、リンク
equals = レコード番号、文字列1行、数値、日付・日時、ステータス
dateRange >= / <= 日付・時刻・日時、作成日時・更新日時
numberRange >= / <= レコード番号、数値、計算
inValues / notInValues in / not in チェックボックス、ドロップダウン、ラジオボタン、ユーザー選択など

条件はすべてAND結合で、OR条件は書けません。ツールの説明にも「OR conditions are not supported」と明記されています。「配信対象」か「VIP」のどちらかにチェックがある、のような条件ならinValuesのvaluesに並べれば済みます。一方、異なるフィールドにまたがるOR条件は、2回に分けて取得してまとめる必要があります。

クエリ記法の細かい書き方をエージェントに覚えさせなくていいので、条件の組み立てで失敗しにくいのは利点です。チェックボックスのフィールドにはinValuesを使う、という型の対応も、kintone-get-form-fieldsの結果を見てエージェントが自分で選んでいました。

1回に取得できるのは最大500件(limitは1〜500)で、それ以上はoffsetで続きを取ります。

手順3:blastengineで1件ずつ送る

取り出したレコードに、トランザクション配信で1件ずつ送ります。

取得した各レコードに、blastengineのトランザクション配信で1件ずつ案内メールを送ってください。
- 送信元: info@example.com(送信者名: 株式会社example)
- 件名: 【ご案内】11月のサービス説明会のお知らせ
- 本文(テキスト): 「{会社名}\n{担当者名} 様\n\nいつもお世話になっております。株式会社exampleです。\n11月のサービス説明会のご案内をお送りします。\n」({}はレコードの値で置き換える)

エージェントはblastengine_send_transactionをレコードの数だけ呼びます。会社名と担当者名は、エージェントが本文に埋め込んで送っていました。

{
  "from_email": "info@example.com",
  "from_name": "株式会社example",
  "to": "yamada@example.jp",
  "subject": "【ご案内】11月のサービス説明会のお知らせ",
  "text_part": "株式会社サンプル商事\n山田 太郎 様\n\nいつもお世話になっております。株式会社exampleです。\n11月のサービス説明会のご案内をお送りします。\n"
}

送信が受け付けられると{"delivery_id":5}のように配信IDが返ります。

BLASTENGINE_ENABLE_BULKを切ってあるので、件数が多くてもエージェントが一斉配信に切り替えることはありません。宛先ごとにトランザクション配信が1件ずつ作られ、配信IDも1件ずつ付きます。レコードと配信IDを1対1で対応させたい今回の用途には、このほうが都合がいいです。

手順4:配信結果を確かめてから書き戻す

ここがこの構成の要です。

配信IDが返っても、届いたとは限らない

最初、指示を「送信が受け付けられたらstatusを『送信済み』にする」と書いて実行しました。このときは、宛先をblastengineの配信可能アドレスに登録し忘れていました。結果は、4件とも配信IDが返ったにもかかわらず、1通も届いていませんでした。

このときエージェントは、受け付けた後に自分で配信情報を確認し、指示どおり「送信済み」にせず「エラー」で書き戻しました。報告にはこうありました。

ご指示では、API が受け付けたら「送信済み」にするとなっていました。
ただ、配信されていないレコードを「送信済み」にすると、再送の対象から外れてしまうため、
「エラー」にしました。

今回はエージェントが気を利かせてくれました。ただ、毎回こう判断してくれる保証はありません。何をもって送信済みとするかは、指示の中で決めておくべきです。

配信情報のSENTは「配信処理が終わった」という意味

判定に使うツールの選び方にも注意が要ります。blastengine_delivery_getで配信情報を引くと、届かなかった配信でも次のように返ってきました。

{
  "delivery_id": 1,
  "delivery_type": "TRANSACTION",
  "status": "SENT",
  "total_count": 1,
  "sent_count": 0,
  "drop_count": 1,
  "hard_error_count": 0,
  "soft_error_count": 0
}

statusはSENTですが、sent_countは0で、drop_countが1です。配信情報のstatusは配信処理の段階を表す値なので、宛先に届いたかどうかの判定には使えません。

宛先ごとの結果は、blastengine_mail_results_listで配信IDを指定して引きます。

{
  "delivery_id": 1,
  "email": "yamada@example.jp",
  "status": "DROP",
  "last_response_code": "554(rejection)",
  "last_response_message": "宛先が配信可能なアドレスではありません。配信可能アドレスの登録状況をご確認ください。..."
}

届いた場合はstatusがSENT、last_response_codeが250、last_response_messageが「配信に成功しました」になります。判定はこちらのstatusとlast_response_codeで行います。

配信ログに載るまで数秒かかることがある

宛先を配信可能アドレスに登録し、もう一度送り直しました。3件とも届きましたが、このとき配信ID 6だけ、送った直後にblastengine_mail_results_listを引くと{"data":[]}が返りました。同じ時点のblastengine_delivery_getはstatus: SENTで、sent_countもdrop_countも0です。

何度か引き直すと、配信ログにSENT / 250が現れました。ログの作成時刻が11:51:09、配信時刻が11:51:15、更新時刻が11:51:18で、送信から結果が確定するまで10秒ほどかかっています。

送った直後に1回だけ確認して、ログが空なら「エラー」、という判定にすると、届いているメールを失敗扱いにしてしまいます。空のときは少し待って引き直し、それでも出なければ「未確定」として残す、という扱いが必要です。

書き戻しまでを含めた指示

以上を踏まえて、指示を次のように書き直しました。

送信APIが受け付けたら、すぐに「送信済み」にはせず、配信ログで実際の配信結果を確認してください。
- 配信に成功していたら: delivery_id に配信ID、sent_at に送信日時を入れ、status を「送信済み」
- DROPやエラーだったら: delivery_id と sent_at を入れたうえで status を「エラー」、result_note に結果コードとエラー内容
- 送信API自体がエラーを返したら: status を「エラー」、result_note にエラー内容
最後に、更新後のレコード一覧(レコード番号・会社名・status・delivery_id・sent_at・result_note)を報告してください。

エージェントは3件を送り、配信ログが空だった配信ID 6について引き直しを繰り返し、全件のSENT / 250を確認してからkintone-update-recordsを1回呼びました。

{
  "app": "1",
  "records": [
    { "id": "1", "record": { "status": { "value": "送信済み" }, "delivery_id": { "value": "5" }, "sent_at": { "value": "2026-10-29T02:51:08Z" } } },
    { "id": "2", "record": { "status": { "value": "送信済み" }, "delivery_id": { "value": "6" }, "sent_at": { "value": "2026-10-29T02:51:15Z" } } },
    { "id": "3", "record": { "status": { "value": "送信済み" }, "delivery_id": { "value": "7" }, "sent_at": { "value": "2026-10-29T02:51:10Z" } } }
  ]
}

sent_atには、配信ログのdelivery_time(日本時間)をUTCに直した値が入っています。kintoneの日時フィールドはYYYY-MM-DDTHH:mm:ssZのUTCで受け取り、画面ではユーザーのタイムゾーンで表示されます。

書き戻した後に読み直すと、sent_atは2026-10-29T02:51:00Zのように、秒が切り捨てられて分単位になっていました。kintoneの日時フィールドは秒を保持しないためです。秒まで残したい場合は、配信ログのdelivery_timeを文字列フィールドに入れるか、result_noteに書いておきます。

ここまでで19ターン、約0.16ドルでした。引き直しの分だけターン数は増えますが、取り違えて書き戻すよりは安く済みます。

書き戻した後のレコードは次のとおりです。

レコード 配信対象 status delivery_id 備考
1 対象 送信済み 5
2 対象 送信済み 6 配信ログの反映待ちあり
3 対象 送信済み 7
4 対象 エラー 4 1回目の送信でDROP。result_noteに554の内容
5 なし 未送信 配信対象外のため送っていない

同じ指示をもう一度実行しても二重に送らない

取り出す条件を「statusが『未送信』」にしているので、送信済みとエラーのレコードは次回の対象から外れます。同じ指示を何度実行しても、同じ相手に二重には送りません。エラーになったレコードは、宛先を直してからstatusを「未送信」に戻せば、次回の実行で送り直されます。

この形にしておくと、手順2〜4をまとめた指示をclaude -pで定期実行に載せることもできます。非対話での実行方法は週次レポートの記事を参照してください。

エージェントに渡すツールを絞る

kintone MCPサーバーには、アプリの設定変更や削除のツールも含まれています。配信と書き戻しだけを行う運用であれば、使うツールを明示的に許可し、それ以外は使わせない形にします。

claude -p "(手順2〜4をまとめた指示)" \
  --mcp-config .mcp.json --strict-mcp-config --tools "" \
  --allowedTools "mcp__kintone__kintone-get-form-fields,mcp__kintone__kintone-get-records,mcp__kintone__kintone-update-records,mcp__blastengine__blastengine_send_transaction,mcp__blastengine__blastengine_delivery_get,mcp__blastengine__blastengine_mail_results_list"

対話で使う場合は、.claude/settings.jsonのpermissions.denyに削除系のツールを入れておくと安心です。

{
  "permissions": {
    "deny": [
      "mcp__kintone__kintone-delete-records",
      "mcp__kintone__kintone-delete-form-fields",
      "mcp__kintone__kintone-delete-space"
    ]
  }
}

送信の直前に承認を挟む方法は、誤送信を防ぐ記事で詳しく扱っています。

kintoneの通知とblastengineの使い分け

観点 kintoneの通知 blastengine経由
宛先 kintoneのユーザー 社外を含む任意のメールアドレス
送信元 kintoneの通知用アドレス SPF・DKIM・DMARCを設定した自社ドメイン
文面 通知の定型 件名・本文を自由に組める
届いたかどうか 宛先ごとの配信結果は残らない 宛先ごとの配信ログ(SENT / DROP / HARDERRORなど)を取れる
レコードとの紐付け レコードに直接紐づく 配信IDを自分で書き戻す

社内のメンバーに「レコードが更新された」と知らせるだけならkintoneの通知で足ります。顧客や取引先にメールを届けたいとき、そして届いたかどうかをkintone側で管理したいときに、blastengineを挟む意味があります。

つまずきやすいポイント

症状 原因と対処
アプリを作ったのにレコードを登録できない kintone-add-appは動作テスト環境に作るだけ。kintone-deploy-appで反映し、kintone-get-app-deploy-statusがSUCCESSになるのを待つ
クエリ文字列で条件を書かせると失敗する kintone-get-recordsはクエリ文字列ではなく構造化されたfiltersを受け取る。条件はAND結合のみでOR不可
500件を超えると取りこぼす limitは1〜500。offsetで続きを取る
MCP Inspectorから直接呼ぶと引数エラーになる アプリIDやレコードIDは文字列で渡す。数値で渡すとInput validation error: expected string, received number at app
配信IDが返ったのに届いていない 受け付けと配信は別。blastengine_mail_results_listで宛先ごとのstatusとlast_response_codeを確認する
配信情報がSENTなのに届いていない blastengine_delivery_getのstatusは配信処理の段階。sent_countとdrop_count、または配信ログで判定する
送った直後に配信ログが空 反映まで10秒ほどかかることがある。少し待って引き直す
554(rejection)でDROPになる 宛先が配信可能アドレスに登録されていない。トライアルでは登録した宛先にしか送れない
送信日時の秒が消える kintoneの日時フィールドは分単位。秒が必要なら文字列フィールドに残す
アプリの作成がAPIトークンでできない アプリを作成するAPIはAPIトークン認証に対応していない。アプリ自体を作るならパスワード認証を使う
MCP Inspectorで認証が通らない InspectorのCLIモードは親シェルの環境変数をサーバーに渡さない。-e KEY=valueをサーバーのコマンドより前に書き、--で区切る
blastengine MCPが起動しない Node.js 22.15以上が必要(package.jsonのengines)

まとめ

kintoneとblastengineを1つのエージェントにつなぐ構成のポイントは3つです。

  • kintoneの公式MCPサーバーは、Dockerイメージを.mcp.jsonに書くだけで使える。アプリの作成から反映まで会話で済むが、運用ではレコード操作だけに絞ったAPIトークンに切り替える
  • レコードの抽出は、クエリ文字列ではなく構造化されたfiltersで行う。条件はAND結合のみで、statusが「未送信」のものだけを取り出せば、何度実行しても二重には送らない
  • 書き戻しの判定は、配信IDが返ったかどうかでも、配信情報のstatusでもなく、blastengine_mail_results_listで引いた宛先ごとの配信結果で行う。ログが空なら少し待って引き直す

「送れた」の基準をはっきりさせておけば、kintoneのレコードを見るだけで、誰に送って誰に届かなかったかが分かる状態にできます。


blastengine MCPサーバーへの要望や感想は、以下のアンケートフォームから送ると開発チームに届くみたいです。

https://docs.google.com/forms/d/e/1FAIpQLSdNZ9TswUT3JEv3pMHkJGqOvgmsXuKP1smkJiNEsqbTVDwRyg/viewform

※本記事の手順は、kintone開発者ライセンスの環境に、kintone MCPサーバー1.11.2(Dockerイメージ)とblastengine MCPサーバー v0.2.2を接続し、Claude Code(Sonnet)からアプリの作成、レコードの抽出、実送信、配信結果の確認、書き戻しまで通しで確認しました。提供ツールや引数はバージョンによって変わるため、手元のtools/listを確認してください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?