Node.js・Python・cURLでトランザクションメールを送信する方法

Node.js、Python、PHP、コマンドラインからREST APIでメールを送信する、コピーして使えるコード例。タイムアウト、エラー処理、再利用できるヘルパー関数まで紹介します。

トランザクションメールとは、ユーザーが何らかの操作をしたことをきっかけにプロダクトが送るメッセージです。新規登録、パスワードのリセット、請求書の支払い、ワークスペースへの招待などがこれにあたります。これらは素早く、自社のドメインから、きっかけとなったリクエストの処理を遅らせることなく送信する必要があります。

この記事では、チームでよく使われる言語からInbixio REST APIを使ってメールを送信する方法を紹介し、さらにその単発の呼び出しを、どのコードベースにも組み込める小さなヘルパー関数にまとめます。

リクエストの概要

以下のすべての例は、同じHTTPリクエストを送信します。

  • メソッドとURL:POST https://backend.inbixio.com/api/send-email/(末尾のスラッシュを含む)
  • 認証:Send権限を持つキーを指定したX-API-Keyヘッダー
  • 本文:to_email、subject、message(HTML)を含むJSON

呼び出しが成功すると、200と{"message": "Email queued successfully."}が返されます。まだキーを作成していない、またはSMTPサーバーを接続していない場合は、5分でできるクイックスタートから始めてください。

すべての例で、キーはINBIXIO_API_KEYという環境変数から読み込んでいます。キーはサーバー上に置き、ブラウザやモバイルアプリには決して含めないでください。

cURL

キーのテスト、サーバー上でのデバッグ、シェルスクリプトやcronジョブからの呼び出しに便利です。

curl -sS -X POST https://backend.inbixio.com/api/send-email/ \
  -H "X-API-Key: $INBIXIO_API_KEY" \
  -H "Content-Type: application/json" \
  --max-time 15 \
  -d '{
    "to_email": "ada@example.com",
    "subject": "Your invoice is ready",
    "message": "<p>Hi Ada, your invoice for October is ready.</p>"
  }'

-sSは進行状況バーを非表示にしつつ、エラーは表示します。--max-timeは、ネットワークが応答しなくなったときにスクリプトが止まったままになるのを防ぎます。

Node.js

Node.js 18以降にはfetchが標準で搭載されているので、HTTPライブラリは不要です。

// send-email.js
const INBIXIO_URL = 'https://backend.inbixio.com/api/send-email/';

export async function sendEmail({ to, subject, html }) {
  const res = await fetch(INBIXIO_URL, {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.INBIXIO_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ to_email: to, subject, message: html }),
    signal: AbortSignal.timeout(15_000),
  });

  const body = await res.json().catch(() => ({}));
  if (!res.ok) {
    const reason = body.error || body.detail || res.statusText;
    throw new Error(`Inbixio ${res.status}: ${reason}`);
  }
  return body; // { message: 'Email queued successfully.' }
}

Expressのルートから使う場合:

app.post('/signup', async (req, res) => {
  const user = await createUser(req.body);
  await sendEmail({
    to: user.email,
    subject: 'Welcome aboard',
    html: `<h1>Welcome, ${escapeHtml(user.name)}!</h1><p>Your account is ready.</p>`,
  });
  res.status(201).json({ id: user.id });
});

escapeHtmlに注目してください。messageフィールドはHTMLとしてレンダリングされるため、名前などユーザーが入力したものは、本文に入れる前に必ずエスケープする必要があります。

Python

requestsを使う場合:

# send_email.py
import os
import requests

INBIXIO_URL = "https://backend.inbixio.com/api/send-email/"

class EmailError(Exception):
    pass

def send_email(to: str, subject: str, html: str) -> dict:
    res = requests.post(
        INBIXIO_URL,
        headers={"X-API-Key": os.environ["INBIXIO_API_KEY"]},
        json={"to_email": to, "subject": subject, "message": html},
        timeout=15,
    )
    if not res.ok:
        try:
            body = res.json()
        except ValueError:
            body = {}
        reason = body.get("error") or body.get("detail") or res.reason
        raise EmailError(f"Inbixio {res.status_code}: {reason}")
    return res.json()

json=を渡すと、Content-Typeヘッダーが自動で設定されます。timeoutは必ず設定しましょう。requestsはデフォルトでは無期限に待ち続けます。

Djangoでは、データベースのトランザクションがコミットされた後に呼び出すことで、ロールバックされた行についてメールを送ってしまうことを防げます。

from django.db import transaction

transaction.on_commit(lambda: send_email(user.email, "Welcome!", render_welcome(user)))

PHP

<?php
function send_email(string $to, string $subject, string $html): array {
    $ch = curl_init('https://backend.inbixio.com/api/send-email/');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 15,
        CURLOPT_HTTPHEADER => [
            'X-API-Key: ' . getenv('INBIXIO_API_KEY'),
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode([
            'to_email' => $to,
            'subject'  => $subject,
            'message'  => $html,
        ]),
    ]);
    $body = json_decode(curl_exec($ch), true) ?? [];
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);

    if ($status !== 200) {
        throw new RuntimeException("Inbixio $status: " . ($body['error'] ?? $body['detail'] ?? 'request failed'));
    }
    return $body;
}

複数の宛先に送信する

1回のリクエストで配信できるのは1つのto_emailだけです。ユーザーのリストに通知するには、受信者ごとに1回ずつリクエストを送ります。これはトランザクションメールにとって望ましい形でもあります。ユーザーごとに個別のメッセージが届き、他の人のアドレスが見えることはなく、1つのアドレスに問題があっても他の送信は妨げられません。

自社のSMTPサーバーに負荷をかけすぎないよう、並行数は控えめにしましょう。ほとんどのアプリでは、5〜10件の並列リクエストで十分です。

リクエストの処理中に送信しない

APIの呼び出しは高速ですが、それでもネットワークの往復が発生します。急ぎでないものは、送信処理をバックグラウンドジョブ(BullMQ、Celery、Sidekiq、クラウドのキュー)に任せましょう。そうすれば、次のようなメリットがあります。

  • ユーザーのページはメールの送信を待たずに表示されます。
  • 一時的なエラーは、ユーザーに気づかれることなくリトライできます。
  • 数千通のウェルカムメールを発生させる大規模なインポートなど、急な増加も一定のペースで処理できます。

OTPコードやパスワードリセットの場合はユーザーが待っているので、すぐに送信し、失敗したらわかりやすいエラーを表示しましょう。どのエラーをリトライし、どのエラーをユーザーに表示すべきかは、メールAPIのエラーと上限への対処で解説しています。

リリース前のチェックリスト

  • APIキーは環境変数またはシークレットマネージャーから読み込み、キーにはSend権限だけが付与されている。
  • HTML内のユーザー入力値はすべてエスケープされている。
  • リクエストに10〜15秒のタイムアウトが設定されている。
  • 200以外のレスポンスはレスポンス本文とともにログに記録され、errorやdetailのメッセージが失われない。
  • 送信ドメインにSPF、DKIM、DMARCが設定されており、メールが受信トレイに届く。

Inbixioで送信しよう

お使いのSMTPサーバーを接続し、権限を絞ったAPIキーを作成すれば、HTTPSリクエスト1回であらゆる言語から送信できます。

アカウントを作成 料金を見る