mcpField
S
send-message

send-message

github.com/zavudev生産性1351 回インストール2 日前に更新
リポジトリ

send-message について

send-message は、Zavu API でメッセージを送る開発者向けのスキルです。チャネル選択、宛先形式、メッセージ種別に加え、返信やエラーの扱いを説明します。

主な機能

  1. 01宛先に応じたチャネル選択
  2. 02複数の宛先識別子形式に対応
  3. 03テキストと WhatsApp リッチメッセージの送信
  4. 04引用返信と送信元の設定
  5. 05冪等キーによるメッセージ送信

活用シーン

  1. 01アプリから SMS やメールを送信
  2. 02WhatsApp の連絡先にメディアや対話型メッセージを送信
  3. 03Telegram やソーシャルチャネルの会話に返信

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/zavudev/zavu-skills --skill send-message

インストール後、Claude Code で/send-messageを入力して読み込まれたか確認します。

詳細

メッセージの送信

使用する場面

Zavu API を使ってメッセージを送信するコードを作成するときに、このスキルを使用します。チャネルの選択、宛先(to)の形式、メッセージの種類、エラー処理について説明します。

チャネル

auto、sms、sms_oneway、whatsapp、telegram、email、instagram、messenger、voice。

送信者とアカウント(1 段落)

送信者は、Zavu-Sender として渡す API ハンドルです。アカウント(WhatsApp Business Account、Facebook ページ、Telegram ボット、電話番号)は、メッセージのルーティング先となる接続であり、料金が発生する対象です。送信者は無料です。ダッシュボードでアカウントを接続すると、その送信者が自動的に作成されます。GET /v1/senders で確認し、送信可能なチャネルについては channels 配列を参照してください。全体像については channel-setup スキルをご覧ください。

チャネル選択の判断ツリー

Is recipient an email address?
  -> YES: channel = "email" (the sender needs an email channel: a verified domain)
Is message type non-text (image, video, buttons, list, template, etc.)?
  -> YES: channel = "whatsapp" (auto-selected)
Need voice call / TTS?
  -> YES: channel = "voice"
Recipient is a numeric chat ID (Instagram / Messenger / Telegram)?
  -> YES: channel = "instagram" | "messenger" | "telegram"
Need one-way SMS (no inbound replies)?
  -> YES: channel = "sms_oneway"
Need guaranteed delivery to a specific channel?
  -> YES: channel = "sms" | "whatsapp" | "telegram" | "instagram" | "messenger"
Want Zavu to pick the channel?
  -> YES: channel = "auto" (first channel the sender has configured
     that the recipient's address can receive; not cost-based)
Default?
  -> channel = "sms" (or omit for default)

宛先(to)の形式

共通の to フィールドでは、複数の識別子形式を使用できます。ルーティングは channel に従います(channel が省略された場合は、識別子から自動選択されます)。

形式例備考
E.164 電話番号+14155551234SMS、WhatsApp、Voice、Telegram。
メールアドレス[email protected]デフォルトは email です。
WhatsApp BSUIDUS.13491208655302741918ビジネススコープのユーザー ID です。WhatsApp にルーティングされます。ユーザー名を設定して電話番号を非公開にした連絡先にメッセージを送る際に使用します。
数値チャット ID123456789Telegram、Instagram、Messenger のチャット/ユーザー ID。

基本的なメッセージ

SMS(デフォルト)

TypeScript:

const result = await zavu.messages.send({
  to: "+14155551234",
  text: "Your verification code is 123456",
});
console.log(result.message.id);

Python:

result = zavu.messages.send(
    to="+14155551234",
    text="Your verification code is 123456",
)
print(result.message.id)

Go:

result, err := client.Messages.Send(context.TODO(), zavudev.MessageSendParams{
    To:   zavudev.String("+14155551234"),
    Text: zavudev.String("Your verification code is 123456"),
})
fmt.Println(result.Message.ID)

Ruby:

result = client.messages.send(to: "+14155551234", text: "Your verification code is 123456")
puts result.message.id

PHP:

$result = $client->messages->send([
    'to' => '+14155551234',
    'text' => 'Your verification code is 123456',
]);
echo $result->message->id;
WhatsApp テキスト
const result = await zavu.messages.send({
  to: "+14155551234",
  channel: "whatsapp",
  text: "Hello from Zavu!",
});
メール
const result = await zavu.messages.send({
  to: "[email protected]",
  channel: "email",
  subject: "Your order has shipped",
  text: "Hi John, your order #12345 has shipped.",
  htmlBody: "<h1>Order Shipped</h1><p>Your order #12345 has shipped.</p>",
  replyTo: "[email protected]",
});
音声(テキスト読み上げ)
const result = await zavu.messages.send({
  to: "+14155551234",
  channel: "voice",
  text: "Your verification code is 1 2 3 4 5 6",
  voiceLanguage: "en-US", // optional, auto-detected from country code
});
一方向 SMS

返信が想定されない場合(受信経路がない場合)は、sms_oneway を使用します。

await zavu.messages.send({
  to: "+14155551234",
  channel: "sms_oneway",
  text: "Your appointment is confirmed for 3pm.",
});
Instagram/Messenger

(受信した会話から取得した)数値のチャット/ユーザー ID を指定します。

// Instagram Direct
await zavu.messages.send({
  to: "17841400000000000",
  channel: "instagram",
  text: "Hello from Zavu via Instagram!",
});

// Messenger (Facebook Page / Marketplace chat)
await zavu.messages.send({
  to: "24025631120151183",
  channel: "messenger",
  text: "Hello from Zavu via Messenger!",
});

WhatsApp リッチメッセージ

画像
await zavu.messages.send({
  to: "+14155551234",
  messageType: "image",
  text: "Check out this product!", // caption
  content: { mediaUrl: "https://example.com/image.jpg" },
});
ドキュメント
await zavu.messages.send({
  to: "+14155551234",
  messageType: "document",
  content: {
    mediaUrl: "https://example.com/invoice.pdf",
    filename: "invoice.pdf",
  },
});
動画/音声
// Video
await zavu.messages.send({
  to: "+14155551234",
  messageType: "video",
  text: "Watch this!",
  content: { mediaUrl: "https://example.com/video.mp4" },
});

// Audio
await zavu.messages.send({
  to: "+14155551234",
  messageType: "audio",
  content: { mediaUrl: "https://example.com/audio.mp3" },
});
スタンプ
await zavu.messages.send({
  to: "+14155551234",
  messageType: "sticker",
  content: { mediaUrl: "https://example.com/sticker.webp" },
});
位置情報
await zavu.messages.send({
  to: "+14155551234",
  messageType: "location",
  content: {
    latitude: 37.7749,
    longitude: -122.4194,
    locationName: "San Francisco",
    locationAddress: "123 Main St, San Francisco, CA",
  },
});
連絡先カード
await zavu.messages.send({
  to: "+14155551234",
  messageType: "contact",
  content: {
    contacts: [
      { name: "John Doe", phones: ["+14155551234", "+14155555678"] },
    ],
  },
});
インタラクティブボタン(最大 3 個)
await zavu.messages.send({
  to: "+14155551234",
  messageType: "buttons",
  text: "How would you rate your experience?",
  content: {
    buttons: [
      { id: "great", title: "Great!" },
      { id: "okay", title: "It was okay" },
      { id: "poor", title: "Not good" },
    ],
  },
});
リストメッセージ
await zavu.messages.send({
  to: "+14155551234",
  messageType: "list",
  text: "Select an option:",
  content: {
    listButton: "View Options",
    sections: [{
      title: "Products",
      rows: [
        { id: "prod_1", title: "Product A", description: "$10.00" },
        { id: "prod_2", title: "Product B", description: "$20.00" },
      ],
    }],
  },
});
テンプレートメッセージ

templateVariables は本文のプレースホルダーに値を設定します(位置指定テンプレートでは位置 1、2、…をキーにし、名前指定テンプレートでは名前をキーにします。両方を混在させないでください)。templateButtonVariables は URL/OTP ボタンの動的なプレースホルダーに値を設定します(ボタンインデックス 0、1、2 をキーにします)。templateHeaderVariables はテキストヘッダーの変数に値を設定します(1 をキーにします)。WhatsApp では、本文とヘッダーの値に改行、タブ、または 4 個を超える連続したスペースを含めることはできません。含まれている場合、送信は 400 invalid_request で拒否され、details.field に該当する値が示されます。

await zavu.messages.send({
  to: "+14155551234",
  messageType: "template",
  content: {
    templateId: "tpl_abc123",
    templateVariables: { "1": "John", "2": "ORD-12345" },
    templateButtonVariables: { "0": "abc-report-token" }, // optional: dynamic URL/OTP buttons
  },
});
CTA URL(コールトゥアクションボタン)
await zavu.messages.send({
  to: "+14155551234",
  messageType: "cta_url",
  text: "Check out our latest collection",
  content: {
    ctaDisplayText: "View Products",          // max 20 chars
    ctaUrl: "https://example.com/products",
    ctaHeaderType: "image",                    // optional: text | image | video | document
    ctaHeaderMediaUrl: "https://example.com/header.jpg",
    footerText: "Limited time offer",          // optional, max 60 chars
  },
});
位置情報のリクエスト(連絡先に位置情報の共有を依頼)

固定の「位置情報を送信」ボタンを含むメッセージを送信します。WhatsApp 専用です。content オブジェクトは不要です。プロンプトは text に指定します(最大 1024 文字)。

SDK ではまだ生成されていないため、REST を使用してください。

curl -X POST https://api.zavu.dev/v1/messages \
  -H "Authorization: Bearer $ZAVU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+14155551234",
    "channel": "whatsapp",
    "messageType": "location_request",
    "text": "To finish your order, share the delivery address."
  }'

返信は通常の受信 location メッセージとして届きます(新しい種類ではありません)。content.replyToMessageId にはリクエストの ID が設定されます。このフィールドを使って、座標と依頼した注文/チケットを関連付けます。

if (
  event.type === "message.inbound" &&
  event.data.messageType === "location" &&
  event.data.content?.replyToMessageId === pendingRequestId
) {
  const { latitude, longitude } = event.data.content;
}

content.name と content.address は任意項目です。連絡先がピンをドロップする代わりに保存済みの場所を選んだ場合にのみ含まれます。常に緯度/経度を使用してください。

連絡先情報のリクエスト(連絡先に電話番号の共有を依頼)

固定の「連絡先情報を共有」ボタン付きのメッセージを送信します。WhatsApp 専用です。content オブジェクトは不要です。プロンプトは text に指定します(最大1024文字)。WhatsApp のユーザー名を採用し、BSUID でしか識別できない連絡先に不可欠です。

SDK ではまだ生成されていないため、REST を使用してください。

curl -X POST https://api.zavu.dev/v1/messages \
  -H "Authorization: Bearer $ZAVU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "US.13491208655302741918",
    "channel": "whatsapp",
    "messageType": "request_contact_info",
    "text": "Share your phone number so our team can call you back."
  }'

回答は通常の受信 contact メッセージとして届き、共有された番号は content.contacts[0].phones に含まれます。送信者が BSUID でしか識別できなかった場合、Zavu は 共有された電話番号をその連絡先に自動的に関連付けます。手動で更新する必要はありません。

注:ワンタップ/ゼロタップ/コードコピーボタン付きの認証テンプレートは BSUID 宛てには送信できません(WhatsApp エラー 131062)。まず電話番号を取得してください。 ブロードキャストでも、BSUID/@username の宛先は拒否されます。

リアクション
await zavu.messages.react({
  messageId: "msg_abc123",
  emoji: "\ud83d\udc4d",
});

引用返信(WhatsApp と Telegram)

content.replyToMessageId に、返信対象のメッセージの Zavu ID を設定します。たとえば、message.inbound ウェブフックの data.messageId を指定します。受信者には、そのメッセージが返信の上に引用表示されます。

const telegram = event.data.channel === "telegram";
await zavu.messages.send({
  // Telegram's `data.from` is "telegram:<chatId>"; send to the bare chat id.
  to: telegram ? event.data.telegramChatId : event.data.from,
  channel: telegram ? "telegram" : "whatsapp",
  text: "Your order ships tomorrow.",
  content: { replyToMessageId: event.data.messageId },
  'Zavu-Sender': event.senderId, // the sender that received it
});
  • WhatsApp:reaction を除くすべてのメッセージタイプで使用できます。Telegram:すべてのメッセージタイプで使用できます。ボタンのタップ(受信時の data.isCallbackQuery: true)はメッセージではないため、引用できません。
  • 引用するメッセージは to と同じ会話に属し、このメッセージの送信元と同じ送信者によって送信または受信されている必要があります。その送信者を Zavu-Sender で指定してください。
  • channel を明示的に設定してください。auto では、電話番号宛てのメッセージが引用できない SMS にルーティングされる場合があります。
  • 配信できない引用は拒否され、引用なしで送信されることはありません。メッセージがこのプロジェクトに存在しない場合は 404、別のチャネル、別の会話、別の送信者、リアクション、Telegram のボタンタップ、失敗したメッセージ、または providerMessageId がまだない引用対象メッセージの場合は 400 が返されます。
  • リクエストから読み取られるのは replyToMessageId のみです。保存されたメッセージには、Zavu が replyToProviderMessageId、replyToFrom、replyToText、replyToMessageType を設定します。

入力中インジケーター

返信を準備している間、受信した WhatsApp メッセージを既読にし、入力中インジケーターを表示します(POST /v1/messages/{messageId}/typing)。返信を送信すると自動的に消えます。または25秒後に消えます。受信した WhatsApp メッセージにのみ有効です。返信に数秒以上かかる場合(LLM エージェント、ツール呼び出し、検索)に使用してください。

curl -X POST https://api.zavu.dev/v1/messages/MESSAGE_ID/typing \
  -H "Authorization: Bearer $ZAVU_API_KEY" \
  -H "Zavu-Sender: sender_12345"

送信者の上書き

await zavu.messages.send({
  to: "+14155551234",
  text: "Hello!",
  'Zavu-Sender': "snd_abc123",
});

冪等性

await zavu.messages.send({
  to: "+14155551234",
  text: "Payment confirmed",
  idempotencyKey: "payment_confirm_order_123",
});

プロジェクトですでに使用されたキーで再試行しても、何も送信されません。本文が { message } の 409 が返されます。これは最初のリクエストで作成されたメッセージの現在の状態なので、そこから id と status を読み取ってください。再実行への応答は、レート制限、日次制限、残高制限よりも先に返されます。そのため、宛先ごとの1分あたり1件のSMS制限内で再試行しても、429 ではなく 409 の再実行応答が返されます。SDK はこの 409 を ConflictError として発生させ(再試行しません)、再実行されたメッセージはエラーの error プロパティに含まれます。

import Zavudev from "@zavudev/sdk";

try {
  await zavu.messages.send({ to: "+14155551234", text: "Payment confirmed", idempotencyKey: key });
} catch (err) {
  if (err instanceof Zavudev.ConflictError) {
    const { message } = err.error as { message: { id: string; status: string } };
    // Already sent: message.id and message.status describe the first attempt.
  } else {
    throw err;
  }
}

SMS への自動フォールバック

WhatsApp テキストメッセージまたはテンプレートメッセージの送信に失敗すると、送信者が SMS も利用できる場合は SMS で再試行されます。フォールバックが行われるかどうかは、チャネルの選択方法によって異なります。

  • channel を省略するか "auto" に設定した場合:フォールバックは有効です。fallbackEnabled: false にすると無効になります。
  • channel を明示的に設定した場合(例:"whatsapp"):フォールバックは無効です。fallbackEnabled: true にすると有効になります。
// Explicit WhatsApp, with the SMS safety net
await zavu.messages.send({
  to: "+14155551234",
  channel: "whatsapp",
  text: "Your order has shipped.",
  fallbackEnabled: true,
});

メディア、ボタン、リストなど、WhatsApp 専用のタイプはフォールバックしません。

ステータスの取得とメッセージの一覧表示

// Get single message
const msg = await zavu.messages.get({ messageId: "msg_abc123" });
console.log(msg.message.status); // queued | sending | sent | delivered | read | failed

// List with filters + pagination
let cursor: string | undefined;
do {
  const result = await zavu.messages.list({ status: "delivered", limit: 50, cursor });
  for (const message of result.items) {
    console.log(message.id, message.status);
  }
  cursor = result.nextCursor ?? undefined;
} while (cursor);

よくあるエラー

エラーコード意味対処方法
whatsapp_window_closed24 時間のウィンドウが開いていません代わりにテンプレートメッセージを使用してください
a2p_limit_exceededHTTP 429:Free プランの月間上限に達しています。WhatsApp、Telegram、Instagram、Messenger では月間 2,000 件のメッセージを共有し、送受信の両方がカウントされます。連絡先から受信したメッセージも、送信したメッセージと同様に 1 件として消費されるため、300 件を送信し、1,700 件を受信したプロジェクトは上限に達しています。共存モードで WhatsApp Business App から送信したメッセージと、送信に失敗したメッセージはカウントされません。上限に達すると受信も拒否されます。保存されず、受信トレイにも表示されず、message.inbound Webhook も送信されません。下記の日次上限とは別であり、日次上限によってこの月間上限に達することが妨げられることはありません有料プラン(上限なし)にアップグレードするか、毎月 1 日の上限リセットをお待ちください
insufficient_balanceHTTP 402。他のすべてのエラーと同様、トップレベルに { code, message } が含まれます(判明している場合、details に requiredCents と availableCents が含まれます)。プリペイド残高が送信に足りません。メールは残高から 1,000 件単位(トランザクションメールは 1,000 件あたり $0.40、マーケティングメールは 1,000 件あたり $0.80)で請求され、SMS と音声通話はメッセージごとに請求されますダッシュボードからチャージしてから、再試行してください
sms_compliance_blockedHTTP 422。SMS と一方向 SMS のみ:コンテンツチェックでテキストが拒否されました(details.complianceFailures、details.requiredFixes、該当する場合は suggestedCompliantVersion に詳細が含まれます)。または、チェックを実行できなかった旨がメッセージに示されています。送信も課金もされません。ワンタイムコードは常にチェックを通過しますrequiredFixes(多くの場合、自社名を明記し、連絡の理由を説明すること)を反映して再送信してください。チェックを実行できなかった場合は、数分後に再試行してください
url_not_verifiedメッセージに未検証の URL が含まれています先に /v1/urls 経由で URL を送信してください
url_shortener_blockedURL 短縮サービスが検出されました完全な宛先 URL を使用してください
ten_dlc_requiredHTTP 403:宛先が米国またはカナダの(+1)電話番号で、プロジェクトに承認済みの 10DLC キャンペーンがありません。これは Zavu ではなく携帯電話会社の要件です。ブランドとキャンペーンの登録には、承認済みの事業者確認が必要ですダッシュボードで 10DLC ブランドとキャンペーンを登録するか、その連絡先に別のチャネルで送信してください
sending_suspendedHTTP 403:Zavu の自動審査で複数のメッセージが拒否されたため、このアカウントの送信が停止されています。停止中はすべてのチャネルで拒否され、details.dashboardUrl にサポートへのリンクが示されます。POST /v1/calls でも同じコードが返されますサポートにお問い合わせください。待っても解除されません
RISK_BLOCKED / RISK_REJECTED / RISK_REVIEW_EXPIRED失敗したメッセージに付与されます(非同期)。自動審査でメッセージが即時拒否された、保留中のメッセージが審査担当者に承認されなかった、または審査期限が先に切れたことを示します。失敗した SMS は課金されませんメッセージを書き直すか、拒否が誤りと思われる場合はメッセージ ID を添えてサポートにお問い合わせください
SENDING_SUSPENDED失敗したメッセージに付与されます(非同期)。このメッセージがキューに入っている間に、アカウントの送信が停止されましたサポートにお問い合わせください
daily_limit_exceededHTTP 429:チャネルグループの日次上限を超えています。details.limit に該当する上限が示されます。未認証の場合:sms + sms_oneway の合計が 1 日 25 件、voice が 1 日 5 件、WhatsApp + Telegram + Instagram + Messenger の合計が 1 日 100 件です。基本上限を超える場合:SMS は 1 日 200 件、本人確認または事業者確認の承認後は 1 日 10,000 件です。Free プランでは、音声通話は 1 日 50 件、会話型メッセージは 1 日 250 件です。有料プランには音声通話と会話型メッセージの上限はありません。メールはプランの上限(Free では 1 日 100 件)が適用されます。カウントは 00:00 UTC にリセットされます。これとは別に、すべてのプランでアカウント作成からの経過期間に応じたローリング方式のメール上限があります(最初の 7 日間は 24 時間あたり 1,000 件、1 時間あたり 200 件、30 日目までは 24 時間あたり 10,000 件、1 時間あたり 1,000 件、それ以降は 24 時間あたり 50,000 件、1 時間あたり 5,000 件)。この上限を超えた場合、メッセージは受け付けられた後、EMAIL_RATE_LIMIT_DAILY または EMAIL_RATE_LIMIT_HOURLY で失敗します。この 429 エラーにはなりませんリセットを待つか、本人確認を行う/支払い方法を追加して上限を引き上げてください(メールの場合はプランをアップグレードしてください)
EMAIL_INVALID_RECIPIENTメールアドレスの形式が正しくありません(非同期。失敗したメッセージに付与されます)アドレスを修正してください。POST /v1/introspect/email でリストを事前確認できます
EMAIL_DOMAIN_NOT_FOUND宛先ドメインに MX レコードまたは A レコードがありません(非同期)アドレスを削除してください。送信するとハードバウンスになります
EMAIL_RECIPIENT_SUPPRESSED以前にこのアドレスへの送信がバウンスしたか、苦情が寄せられています(非同期)リストから削除してください
認証によって変わること、変わらないこと。 アカウントは、利用開始直後から、どのチャネルでもあらゆる宛先に送信できます。送信にあたって、どのチャネルでも本人確認やビジネス認証は求められません。認証によって変わるのは、1 日あたりの上限です。本人確認(KYC)、登録済みの支払い方法、決済済みの入金、または有料プランによって、アカウントは最低の上限段階から引き上げられます。SMS の上限が 1 日 10,000 件になるには、本人確認またはビジネス認証が必要です。すべての +1 SMS 宛先で必要となる 10DLC ブランドとキャンペーンの登録には、引き続きビジネス認証(KYB)が必要です。メールに認証要件はありません。送信者は、ドメイン認証が済んでいれば、初日からプランの上限内で送信できます(Free では 1 日 100 件、1 か月 3,000 件)。参考:https://docs.zavu.dev/concepts/sending-limits

すべての sms、sms_oneway、email、voice メッセージは、送信前に内容と、その前後の送信パターンが確認されます。 結果は 3 通りです。通常どおり送信されます。短時間の審査のために保留されます。メッセージは queued のまま(新しいステータスは追加されず、MessageStatus に変更はありません)となり、承認されると通常どおり送信されます。保留期間は数時間です。または、拒否されます。メッセージは失敗し、errorCode に RISK_BLOCKED、RISK_REJECTED、または RISK_REVIEW_EXPIRED が設定され、message.failed が発生します。拒否された SMS の料金は請求されません。通話が保留されることはありません。審査によって停止された通話は、遅れて発信されるのではなく、失敗します。拒否が繰り返されると、アカウントの送信は停止されます(403 sending_suspended)。ブロードキャストは宛先ごとではなく、独自のテキストに基づいて 1 回だけ確認されます。これらのコードで失敗したメッセージを再送するリトライ処理は実装しないでください。拒否されたのはその内容であるため、同じ内容を再試行しても同様に拒否されます。

メール送信は、ディスパッチ時に自動で事前検証されます。確実なハードバウンス(不正な構文、無効なドメイン、抑制対象アドレス)は送信されず、上記のコードで失敗扱いとなるため、バウンス率を悪化させることはありません。これらはメッセージ上で非同期に通知され(status: "failed" と errorCode)、message.failed Webhook にも通知されます。参考情報となるシグナル(info@ のような役割アドレスや使い捨てドメイン)は送信を妨げません。事前に POST /v1/introspect/email で確認してください。

制約

  • ボタンのタイトル:最大 20 文字、ボタンは最大 3 個
  • リスト行のタイトル:最大 24 文字、説明:最大 72 文字、セクションあたり最大 10 行
  • 位置情報リクエストの本文:最大 1024 文字、content オブジェクトなし、WhatsApp のみ
  • メールの件名:最大 998 文字
  • 音声の言語コード:en-US、es-ES、pt-BR など(省略した場合は自動検出)
  • メディアメッセージでは WhatsApp チャネルが自動選択されます

コメント