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?

blastengine insert_codeでパーソナライズ配信を実装する設計パターン

1
Posted at

メール配信で「○○様」のような宛名差し込みや、ユーザーごとに異なる情報を埋め込むパーソナライズ配信は、開封率やクリック率を上げる定番の手法です。blastengineでは insert_code という仕組みを使って、テンプレートと宛先属性を組み合わせたパーソナライズ配信を実現できます。

この記事では、insert_codeの基本的な考え方から実装例、運用時の注意点までを実装中心でまとめます。

insert_codeの考え方

insert_codeは、宛先ごとに異なる値をメール本文に差し込むための仕組みです。考え方はシンプルで、以下の2つを用意します。

  1. テンプレート: 差し込み位置をプレースホルダ(__name__ など)で指定した本文
  2. 属性辞書: 宛先ごとにキーと値のペアを定義したデータ

配信時にblastengineがプレースホルダを属性辞書の値で置換し、宛先ごとにカスタマイズされたメールを送信します。

テンプレート: "__name__様、ご登録ありがとうございます。"
属性辞書:    {"name": "田中"}
↓
送信される本文: "田中様、ご登録ありがとうございます。"

プレースホルダの形式は __キー名__ です。キー名は英数字とアンダースコアが使えます。

データ設計の最小モデル

パーソナライズ配信を始めるにあたって、まず「どの属性を差し込むか」を決める必要があります。顧客テーブルやイベントデータから必要な属性だけを抽出しましょう。

よく使う属性の例

キー名 用途
name 宛名 田中太郎
company 会社名 株式会社サンプル
plan 契約プラン スタンダード
signup_date 登録日 2024年1月15日
expire_date 有効期限 2025年1月14日

欠損値の扱い

属性値が欠損している場合の挙動を事前に決めておくことが重要です。

# 方針1: 空文字で埋める
insert_code = {
    "name": user.name or "",
    "company": user.company or ""
}

# 方針2: デフォルト値を設定
insert_code = {
    "name": user.name or "お客",
    "company": user.company or "御社"
}

どちらの方針を取るかは、テンプレートの文面との兼ね合いで決めます。「○○様」のように敬称が続く場合は空文字だと不自然になるため、デフォルト値を入れる方が無難です。

テンプレ側の実装

テンプレート本文にプレースホルダを配置します。

基本形

件名: __name__様、サービスのご案内

本文:
__name__様

いつもご利用いただきありがとうございます。
__company__にてご契約いただいている__plan__プランの
更新時期が近づいてまいりました。

有効期限: __expire_date__

HTMLとテキストの両方を用意する場合

HTMLメールとテキストメールの両方を用意する場合、プレースホルダのキー名は統一しておきます。

HTMLテンプレート

<p><strong>__name__</strong></p>
<p>ご契約プラン: __plan__</p>

テキストテンプレート

__name__様

ご契約プラン: __plan__

エスケープの考え方

insert_codeで差し込まれる値はそのまま埋め込まれます。HTMLメールの場合、属性値に <> が含まれているとHTMLとして解釈されてしまうため、事前にエスケープしておく必要があります。

import html

insert_code = {
    "name": html.escape(user.name),
    "company": html.escape(user.company)
}

テキストメールのみの場合はエスケープ不要です。

改行や全角の扱い

  • 改行: 属性値に改行が含まれていると、意図しない位置で本文が折り返されます。差し込む前に改行を除去するか、改行を含む値を使わない設計にします
  • 全角文字: 問題なく使えますが、文字数カウントに注意が必要です(全角1文字=バイト数では2〜3バイト)

リクエストの組み立て

トランザクション配信(1通ずつ送信)を例に、insert_codeを含むリクエストの組み立て方を見ていきます。

基本的な構造は以下の通りです。

{
  "from": {
    "email": "sender@example.com",
    "name": "送信者名"
  },
  "to": "recipient@example.com",
  "subject": "__name__様、お知らせ",
  "text_part": "__name__様\n\nメール本文です。",
  "insert_code": [
    { "key": "__name__", "value": "田中太郎" }
  ]
}

insert_code は配列形式で、各要素に key(プレースホルダ名)と value(差し込む文字列)を指定します。設定上限は50件です。

実装例 curl

curl -X POST "https://app.engn.jp/api/v1/deliveries/transaction" \
  -H "Authorization: Bearer YOUR_BEARER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "from": {
      "email": "sender@example.com",
      "name": "サービス事務局"
    },
    "to": "recipient@example.com",
    "subject": "__name__様、ご登録ありがとうございます",
    "text_part": "__name__様\n\nこの度はご登録いただきありがとうございます。\nご契約プラン: __plan__",
    "insert_code": [
      { "key": "__name__", "value": "田中太郎" },
      { "key": "__plan__", "value": "スタンダード" }
    ]
  }'

実装例 Python

import requests
import hashlib
import base64

# 認証情報
USER_ID = "YOUR_USER_ID"
API_KEY = "YOUR_API_KEY"

# Bearer Token生成
digest = hashlib.sha256(f'{USER_ID}{API_KEY}'.encode()).hexdigest()
bearer_token = base64.b64encode(digest.encode()).decode('utf-8')

ENDPOINT = "https://app.engn.jp/api/v1/deliveries/transaction"

# 宛先情報(DBから取得した想定)
user = {
    "email": "recipient@example.com",
    "name": "田中太郎",
    "plan": "スタンダード"
}

# insert_codeの組み立て(配列形式)
insert_code = [
    {"key": "__name__", "value": user["name"] or "お客"},
    {"key": "__plan__", "value": user["plan"] or "ご契約"}
]

# リクエストペイロード
payload = {
    "from": {
        "email": "sender@example.com",
        "name": "サービス事務局"
    },
    "to": user["email"],
    "subject": "__name__様、ご登録ありがとうございます",
    "text_part": "__name__様\n\nご契約プラン: __plan__",
    "insert_code": insert_code
}

response = requests.post(
    ENDPOINT,
    headers={
        "Authorization": f"Bearer {bearer_token}",
        "Content-Type": "application/json"
    },
    json=payload
)

print(response.status_code, response.json())

実装例 Node.js

const crypto = require('crypto');

// 認証情報
const USER_ID = "YOUR_USER_ID";
const API_KEY = "YOUR_API_KEY";

// Bearer Token生成
const digest = crypto.createHash('sha256').update(USER_ID + API_KEY).digest('hex');
const bearerToken = Buffer.from(digest).toString('base64');

const ENDPOINT = "https://app.engn.jp/api/v1/deliveries/transaction";

// 宛先情報(DBから取得した想定)
const user = {
  email: "recipient@example.com",
  name: "田中太郎",
  plan: "スタンダード"
};

// insert_codeの組み立て(配列形式)
const insertCode = [
  { key: "__name__", value: user.name || "お客" },
  { key: "__plan__", value: user.plan || "ご契約" }
];

// リクエストペイロード
const payload = {
  from: {
    email: "sender@example.com",
    name: "サービス事務局"
  },
  to: user.email,
  subject: "__name__様、ご登録ありがとうございます",
  text_part: "__name__様\n\nご契約プラン: __plan__",
  insert_code: insertCode
};

fetch(ENDPOINT, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${bearerToken}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify(payload)
})
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.error(err));

よくある落とし穴

insert_codeを使った実装でつまずきやすいポイントと対策をまとめます。

キー名の不一致

テンプレートのプレースホルダ __userName__ に対して、insert_codeで username(小文字)を指定すると置換されません。キー名は大文字小文字を区別します。

対策: キー名の命名規則を決めて統一する(例: すべてスネークケース)

値の欠損

insert_codeにキーを含めなかった場合、プレースホルダがそのまま残って __name__様 のように表示されてしまいます。

対策: 送信前にすべてのキーが揃っているかバリデーションする

REQUIRED_KEYS = ["name", "company", "plan"]

def validate_insert_code(insert_code):
    missing = [k for k in REQUIRED_KEYS if k not in insert_code]
    if missing:
        raise ValueError(f"Missing keys: {missing}")

型の揺れ

数値や日付をそのまま渡すと、想定外の形式で表示される場合があります。

対策: insert_codeに渡す前にすべて文字列に変換する

insert_code = {
    "price": f"{price:,}",  # "1,000円"
    "date": date.strftime("%Y年%m月%d日")  # "2024年01月15日"
}

HTMLエスケープ漏れ

ユーザー入力値をそのままHTMLメールに差し込むと、XSSやレイアウト崩れの原因になります。

対策: HTMLメールの場合は必ずエスケープする

長すぎる値や改行による崩れ

会社名が異常に長い、備考欄に改行が大量に含まれているなど、想定外のデータで本文が崩れることがあります。

対策: 文字数制限や改行除去のサニタイズ処理を入れる

def sanitize(value, max_length=100):
    if value is None:
        return ""
    value = value.replace("\n", " ").replace("\r", "")
    return value[:max_length]

運用で効く小技

属性セットの固定化

テンプレートを変更するたびにinsert_codeの構造を変えていると、コードとの整合性が取れなくなります。使用する属性のセットを固定化し、テンプレート側で使わない属性があっても常に全属性を渡す方式にすると、テンプレート変更に強くなります。

# 常に同じ構造で渡す
def build_insert_code(user):
    return {
        "name": user.name or "",
        "company": user.company or "",
        "plan": user.plan or "",
        "signup_date": format_date(user.signup_date),
        "expire_date": format_date(user.expire_date),
    }

差し込み項目一覧の自動生成

insert_codeで使用するキーの一覧をコードから自動生成すると、テンプレート作成者との認識合わせがスムーズになります。

# 定数として定義
INSERT_CODE_SCHEMA = {
    "name": "宛名(例: 田中太郎)",
    "company": "会社名(例: 株式会社サンプル)",
    "plan": "契約プラン(例: スタンダード)",
    "signup_date": "登録日(例: 2024年01月15日)",
    "expire_date": "有効期限(例: 2025年01月14日)",
}

# ドキュメント生成
for key, description in INSERT_CODE_SCHEMA.items():
    print(f"__{key}__: {description}")

テストデータのfixtures化

開発・テスト時に使うサンプルデータをfixturesとして用意しておくと、テンプレートの確認が楽になります。

# test_fixtures.py
TEST_INSERT_CODES = {
    "standard": {
        "name": "テスト太郎",
        "company": "株式会社テスト",
        "plan": "スタンダード",
        "signup_date": "2024年01月15日",
        "expire_date": "2025年01月14日",
    },
    "empty_company": {
        "name": "テスト次郎",
        "company": "",  # 会社名なしのケース
        "plan": "ライト",
        "signup_date": "2024年02月01日",
        "expire_date": "2025年01月31日",
    },
    "long_values": {
        "name": "テスト三郎" * 10,  # 長い名前のケース
        "company": "とても長い会社名株式会社" * 5,
        "plan": "エンタープライズ",
        "signup_date": "2024年03月01日",
        "expire_date": "2025年02月28日",
    },
}

まとめ

blastengineのinsert_codeは、テンプレートと属性辞書の「契約」でパーソナライズ配信を実現する仕組みです。

実装を始める際のポイントは以下の3つです。

  1. 最小モデルから始める: 必要な属性だけを抽出し、シンプルな構造でスタートする
  2. 欠損値のルールを先に決める: 空文字にするかデフォルト値を入れるか、最初に方針を固める
  3. エスケープを忘れない: HTMLメールの場合は必ずエスケープ処理を入れる

これらを押さえておけば、安定したパーソナライズ配信の基盤を作ることができます。

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?