mcpField
S
SendLetter

SendLetter

リモート
sendletter.euコミュニケーション07 週間前に更新
リポジトリ公式サイト

SendLetter について

SendLetterは、Node向けのサービスとライブラリで、対応する欧州の宛先へ印刷した手紙を郵送できます。住所確認、見積もり、状況追跡に加え、冪等キーとテストモードで送信を管理できます。

主な機能

  1. 01印刷した手紙を郵送
  2. 02住所確認と料金見積もり
  3. 03状況追跡と手紙PDFの取得
  4. 04可能な場合に手紙をキャンセル
  5. 05請求書とクレジットノートの取得
  6. 06費用なしで送信フローをテスト

活用シーン

  1. 01請求書や督促状を郵送
  2. 02送信前に住所と郵送料を確認
  3. 03郵送状況の追跡と発送証明の取得
  4. 04本番利用前に郵送ワークフローをテスト

MCP 対応

入力にはSendLetterのライブラリとAPIが記載されていますが、MCP機能についての説明はありません。

設定

リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。

エンドポイントhttps://sendletter.eu/api/mcp
インストールコマンドをコピー
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。

ツール

詳細

sendletter

Node から本物の手紙を送ります。印刷、折りたたみ、切手の貼付を行い、郵便局に差し出します。

npm install sendletter
import { SendLetter } from 'sendletter'

const sendletter = new SendLetter(process.env.SENDLETTER_API_KEY!)

const input = {
  idempotencyKey: 'invoice-2026-0042-reminder-1',
  sender: {
    company: 'Twilper',
    name: 'Gert Snijder',
    street: 'Merelstraat',
    number: '64',
    postalCode: '8916 AX',
    city: 'Leeuwarden',
    country: 'NL',
  },
  recipient: {
    name: 'Jan de Vries',
    street: 'Keizersgracht',
    number: '123',
    postalCode: '1015 CJ',
    city: 'Amsterdam',
    country: 'NL',
  },
  text: 'Beste heer De Vries,\n\nBijgaand de herinnering voor factuur 2026-0042.',
}

const prepared = await sendletter.prepare(input)
console.log(prepared.id, prepared.pages, prepared.totalCents)
const pdf = await sendletter.download(prepared.id)

PDF と正確な合計金額をユーザーに表示します。承認されたら、同じ入力とキーを使います。

const letter = await sendletter.send({ ...input, expectedTotalCents: prepared.totalCents })

console.log(letter.id, letter.status, letter.totalCents)

キーは sendletter.eu で取得できます。sk_test_ で始まるキーでは、郵送も課金もされませんが、ステータスの一連の流れはすべて実行されるため、実環境と同じ条件で開発できます。

送信

内容は text、document(リッチテキスト)、file(base64)、fileUrl のいずれか一つで指定してください。二つ指定すると、どちらを使うか推測するのではなく拒否されます。間違った書類を郵便ポストに投函してしまうと、取り戻せないためです。

// An invoice you already have as a PDF.
await sendletter.send({
  sender,
  recipient,
  file: { name: 'invoice.pdf', contentBase64: pdf.toString('base64') },
  product: 'registered',        // 'standard' | 'priority' | 'registered'
  colour: true,
  idempotencyKey: `invoice-${invoice.id}`,
})

再試行の可能性がある場合は、必ず idempotencyKey を指定してください。 同じキーで再度送信すると、二通目の封筒ではなく、元の手紙が返されます。このフィールドがあれば、ネットワークのタイムアウト後も安全に再試行できます。タイムアウトしても、手紙が受け付けられたかどうかは分かりません。

読み取り

await sendletter.get(id)                       // one letter
await sendletter.list({ status: 'posted' })    // one page
await sendletter.cancel(id, 'order withdrawn') // while it is still cancellable
await sendletter.download(id)                  // the PDF as printed
await sendletter.download(id, { proof: true }) // proof of posting

for await (const letter of sendletter.all({ mode: 'live' })) {
  // walks every page; you never hold the cursor
}

請求書とクレジットノート

有料の本番環境の手紙には、支払い時に請求書番号が付与されます。テストの手紙では法定の連番は消費されません。全額返金の場合も請求書番号はそのまま残り、別の番号が付いたマイナスのクレジットノートが追加されます。

const page = await sendletter.listInvoices()
await writeFile('invoice.pdf', await sendletter.downloadInvoice(page.data[0].id))

if (page.data[0].creditNote) {
  await writeFile(
    'credit-note.pdf',
    await sendletter.downloadCreditNote(page.data[0].creditNote.id),
  )
}

料金をかける前の確認

const check = await sendletter.validateAddress(recipient)
if (!check.valid) console.log(check.problems)   // answers, does not throw

const quote = await sendletter.quote({ destination: 'DE', pages: 3 })

validateAddress は不正な住所を例外としてスローせずに返します。誤った郵便番号が返るのは、問い合わせが正常に行われた結果です。住所を編集しても修正できないのは supported: false の場合です。その国への郵送には対応していません。

エラー

API によって拒否された内容はすべて、分岐に使える code を持つ SendLetterError になります。

import { SendLetterError } from 'sendletter'

try {
  await sendletter.send({ sender, recipient, text })
} catch (error) {
  if (!(error instanceof SendLetterError)) throw error

  if (error.code === 'insufficient_balance') {
    // Put this in front of the customer; the wallet is short, nothing else.
    return redirect(error.topUpUrl!)
  }
  if (error.isRetryable) {
    await sleep((error.retryAfter ?? 5) * 1000)
  }
}

isRetryable の対象は 429 と 5xx です。それ以外は再試行しないでください。400 の場合、二度目も同じように手紙が拒否されます。

Webhook

ステータスが変わると、登録した URL に POST で通知されます。必ず検証してください。 検証しない場合、エンドポイントを知った人なら誰でも、手紙が配達されたとシステムに通知できてしまいます。

import { verifyWebhook, SendLetterError } from 'sendletter'

export async function POST(request: Request) {
  const rawBody = await request.text()   // the raw text, not the parsed object

  let event
  try {
    event = verifyWebhook({
      rawBody,
      signature: request.headers.get('x-sendletter-signature'),
      secret: process.env.SENDLETTER_WEBHOOK_SECRET!,
    })
  } catch (error) {
    if (error instanceof SendLetterError) return new Response('nope', { status: 400 })
    throw error
  }

  if (await alreadyHandled(event.id)) return new Response('ok')   // see below
  await handle(event)                                            // letter.posted, ...
  return new Response('ok')
}

この形式には、手作業で検証する場合には見落としがちな、適切な点が二つあります。

  • 再シリアライズした本文ではなく、生の本文を使います。 署名の対象は送信した正確なバイト列であり、JSON.stringify が同じ内容を再現する保証はありません。本文をテキストとして読み取り、検証してから解析してください。
  • event.id で重複を排除します。 配信は再試行を伴う少なくとも一回の方式なので、受信側でタイムアウトすると同じイベントが再び届きます。letter.posted を二度処理しても、顧客に二重請求しないようにしてください。

イベント:letter.submitted、letter.printed、letter.posted、letter.delivered、letter.failed、letter.refunded。

テストモード

sk_test_ キーを使うと、ウォレットにもプリンターにも影響を与えず、Webhook も本番環境とまったく同じように発火しながら、一連の流れ(submitted → printed → posted → delivered)全体が実行されます。client.isTestMode で使用中のキーの種類を確認できます。デプロイ時のチェックで必ず検証するとよいでしょう。ログ上では二つのキーは見分けがつかず、料金が発生するのは片方だけです。

リファレンス

sendletter.eu/en/developers · /api/v1/openapi.json に OpenAPI 仕様 · MIT

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。