poplar-direct-mail について
PoplarのHTMLテンプレートでダイレクトメールをデザインし、API経由で送るスキルです。パーソナライズ、自動配信キャンペーン、はがき・折り・手紙のテンプレート要件を扱います。
主な機能
- 01各種郵便物向けHTMLテンプレート
- 02Liquidによるパーソナライズ
- 03Poplar APIの発送リクエスト
- 04キャンペーンとクリエイティブの照会
- 05テンプレートと印刷の設計ガイド
活用シーン
- 01個別対応のはがきや手紙の作成
- 02トリガー配信やトランザクション郵便
- 03カゴ落ち・休眠顧客向けキャンペーン
- 04CSVからの一括発送
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ ckorhonen/ claude-skills --skill poplar-direct-mailインストール後、Claude Code で/poplar-direct-mailを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
scripts/send_batch.pysend_mailing.pytest_connection.pytemplates/postcard-4x6-back.htmlpostcard-4x6-front.html詳細
Poplar ダイレクトメール
概要
Poplar の HTML テンプレートシステムを使用してプロフェッショナルなダイレクトメールのクリエイティブをデザインし、API 経由でプログラムによるメールを送信します。このスキルでは、動的なパーソナライズ機能を備えたテンプレートの作成、トリガー配信メール向けの API 統合、高いコンバージョンを実現するダイレクトメールキャンペーンのクリエイティブに関するベストプラクティスを扱います。
使用する場面
- ダイレクトメールのポストカード、二つ折り、三つ折り、またはレター向けの HTML テンプレートを作成する
- トリガー配信/プログラム型のダイレクトメールキャンペーンを構築する
- ダイレクトメールをマーケティングオートメーションのワークフローに統合する
- 動的コンテンツ(名前、オファー、QR コード)を使ってパーソナライズされたメールをデザインする
- トランザクションメール(領収書、確認書、明細書)を送信する
- カート放棄や再獲得のメールキャンペーンを設定する
前提条件
- Poplar アカウント(heypoplar.com)
- Poplar Credentials から取得した API アクセストークン
- Python 3.9 以降(スクリプト用)
requestsパッケージ
インストール
pip install requests
API トークンを設定します。
# Test token (for development - only works with mailing endpoint)
export POPLAR_API_TOKEN="test_your_token_here"
# Production token (for live mailings)
export POPLAR_API_TOKEN="your_production_token_here"
メールの形式と寸法
ポストカード
| サイズ | 仕上がり寸法 | 塗り足し込み | ピクセル数(300 PPI) |
|---|---|---|---|
| 4" x 6" | 4" x 6" | 4.25" x 6.25" | 1275 x 1875 |
| 6" x 9" | 6" x 9" | 6.25" x 9.25" | 1875 x 2775 |
| 6" x 11" | 6" x 11" | 6.25" x 11.25" | 1875 x 3375 |
最適な用途: リターゲティング、期間限定プロモーション、シンプルなメッセージ、ダイレクトメールを初めて利用する場合
二つ折り
| 形式 | ピクセル数(300 PPI) |
|---|---|
| ショートフォールド(左から右に折る) | 1725 x 5175 |
| ロングフォールド(上から下に折る) | 3375 x 2625 |
最適な用途: 見込み顧客開拓キャンペーン、新製品の発売、複雑な価値提案
三つ折り
| 形式 | ピクセル数(300 PPI) |
|---|---|
| 標準的な三つ折り | 2625 x 4987 |
最適な用途: 複数の製品選択肢、詳細情報、カタログ
レター
| 形式 | 寸法 |
|---|---|
| 8.5" x 11"(カラーまたは白黒) | 612 x 792 px(塗り足し不要) |
最適な用途: 金融/保険、機密性の高いコンテンツ、トランザクションメール、ビジネス向けの通信
HTML テンプレートの構造
基本テンプレート
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&display=swap"
rel="stylesheet" type="text/css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
width: 4.25in;
height: 6.25in;
position: relative;
font-family: 'Open Sans', sans-serif;
}
.background {
position: absolute;
width: 100%;
height: 100%;
background-image: url('https://your-hosted-image.com/background.png');
background-size: cover;
}
.headline {
position: absolute;
top: 0.5in;
left: 0.375in;
font-size: 28pt;
font-weight: 700;
color: #1a1a1a;
}
.personalized-text {
position: absolute;
top: 1.2in;
left: 0.375in;
font-size: 14pt;
color: #333333;
}
.offer-box {
position: absolute;
bottom: 1in;
left: 0.375in;
width: 3.5in;
padding: 0.25in;
background-color: #ff6b35;
color: white;
text-align: center;
font-size: 18pt;
font-weight: 600;
}
</style>
</head>
<body>
<div class="background"></div>
<div class="headline">Special Offer Inside!</div>
<div class="personalized-text">
Hi {{recipient.first_name | default: "Friend"}},
</div>
<div class="offer-box">
Use code: {{promotion.promo_code}}
</div>
</body>
</html>
CSS の要件
| 要件 | 詳細 |
|---|---|
| CSS の配置場所 | すべてのスタイルを <head> 内の <style> タグに記述 |
| 配置 | すべての要素に position: absolute を使用 |
| 単位 | in(インチ)または px(ピクセル)のみ。em、rem、vw、vh は不可 |
| 画像 URL | 絶対 URL のみ(一般公開されている必要があります) |
| フォント | Google Fonts またはセルフホストの .ttf/.woff ファイル |
フォントの実装
Google Fonts(推奨):
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap"
rel="stylesheet" type="text/css">
カスタムフォント:
@font-face {
font-family: "CustomFont";
src: url("https://your-domain.com/fonts/CustomFont.ttf") format("truetype");
}
マージタグとパーソナライズ
受取人データ
{{recipient.first_name}}
{{recipient.last_name}}
{{recipient.full_name}}
{{recipient.address_1}}
{{recipient.city}}
{{recipient.state}}
{{recipient.postal_code}}
プロモーションコード
{{promotion.promo_code}}
{{promotion.qr_url}}
場所に基づく情報
{{location.city}}
{{location.state}}
{{location.store_address}}
カスタムデータ
{{custom.purchase_amount}}
{{custom.product_name}}
{{custom.loyalty_tier}}
Liquid テンプレートのロジック
条件付きコンテンツ
{% if recipient.first_name %}
Hi {{recipient.first_name | capitalize}},
{% else %}
Dear Valued Customer,
{% endif %}
購入履歴に基づくオファーの段階
{% if custom.lifetime_value >= 500 %}
<div class="offer">Enjoy 25% off your next order!</div>
{% elsif custom.lifetime_value >= 200 %}
<div class="offer">Take 15% off your next purchase!</div>
{% else %}
<div class="offer">Get $10 off orders over $50!</div>
{% endif %}
日付を基準とする有効期限
Expires: {{ "now" | date: "%s" | plus: 2592000 | date: "%B %e, %Y" }}
Expires: {{ "now" | date: "%s" | plus: 7776000 | date: "%B %e, %Y" }}
テキスト書式フィルター
{{ recipient.first_name | capitalize }}
{{ recipient.first_name | upcase }}
{{ recipient.first_name | downcase }}
{{ custom.price | money }}
API リファレンス
認証
すべてのリクエストで Bearer トークンが必要です。
curl -H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
https://api.heypoplar.com/v1/me
メールを作成
エンドポイント: POST https://api.heypoplar.com/v1/mailing
curl -X POST https://api.heypoplar.com/v1/mailing \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"campaign_id": "your-campaign-id",
"creative_id": "your-creative-id",
"recipient": {
"first_name": "Jane",
"last_name": "Smith",
"address_1": "123 Main Street",
"address_2": "Apt 4B",
"city": "San Francisco",
"state": "CA",
"postal_code": "94102"
},
"merge_tags": {
"promo_code": "SAVE20",
"expiration_date": "December 31, 2024"
}
}'
レスポンス(201 Created):
{
"id": "mailing-uuid",
"campaign_id": "campaign-uuid",
"creative_id": "creative-uuid",
"state": "processing",
"front_url": "https://app.heypoplar.com/preview/front.png",
"back_url": "https://app.heypoplar.com/preview/back.png",
"pdf_url": "https://app.heypoplar.com/preview/proof.pdf",
"total_cost": "0.89",
"created_at": "2024-01-15T10:30:00Z"
}
今後のメールをスケジュール
curl -X POST https://api.heypoplar.com/v1/mailing \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"campaign_id": "your-campaign-id",
"recipient": {
"full_name": "John Doe",
"address_1": "456 Oak Avenue",
"city": "Austin",
"state": "TX",
"postal_code": "78701"
},
"send_at": "2024-02-01T09:00:00Z"
}'
メールのステータスを取得
エンドポイント: GET https://api.heypoplar.com/v1/mailing/:id
curl https://api.heypoplar.com/v1/mailing/MAILING_ID \
-H "Authorization: Bearer YOUR_TOKEN"
キャンペーンを一覧表示
エンドポイント: GET https://api.heypoplar.com/v1/campaigns
curl https://api.heypoplar.com/v1/campaigns \
-H "Authorization: Bearer YOUR_TOKEN"
キャンペーンのクリエイティブを一覧表示
エンドポイント: GET https://api.heypoplar.com/v1/campaign/:id/creatives
curl https://api.heypoplar.com/v1/campaign/CAMPAIGN_ID/creatives \
-H "Authorization: Bearer YOUR_TOKEN"
メール送信不可リストに追加
エンドポイント: POST https://api.heypoplar.com/v1/do-not-mail
curl -X POST https://api.heypoplar.com/v1/do-not-mail \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"address": {
"address_1": "123 Main St",
"postal_code": "94102"
}
}'
Python スクリプト
1 件のメールを送信
#!/usr/bin/env python3
"""Send a single direct mail piece via Poplar API."""
import os
import requests
import argparse
POPLAR_API_URL = "https://api.heypoplar.com/v1"
def send_mailing(
campaign_id: str,
recipient: dict,
creative_id: str = None,
merge_tags: dict = None,
send_at: str = None
) -> dict:
"""Send a mailing via Poplar API."""
token = os.environ.get("POPLAR_API_TOKEN")
if not token:
raise ValueError("POPLAR_API_TOKEN environment variable not set")
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
payload = {
"campaign_id": campaign_id,
"recipient": recipient
}
if creative_id:
payload["creative_id"] = creative_id
if merge_tags:
payload["merge_tags"] = merge_tags
if send_at:
payload["send_at"] = send_at
response = requests.post(
f"{POPLAR_API_URL}/mailing",
headers=headers,
json=payload
)
response.raise_for_status()
return response.json()
def main():
parser = argparse.ArgumentParser(description="Send a Poplar direct mail piece")
parser.add_argument("--campaign-id", required=True, help="Campaign ID")
parser.add_argument("--creative-id", help="Creative ID (optional)")
parser.add_argument("--first-name", required=True, help="Recipient first name")
parser.add_argument("--last-name", required=True, help="Recipient last name")
parser.add_argument("--address", required=True, help="Street address")
parser.add_argument("--city", required=True, help="City")
parser.add_argument("--state", required=True, help="State (2-letter code)")
parser.add_argument("--zip", required=True, help="ZIP code")
parser.add_argument("--promo-code", help="Promo code merge tag")
args = parser.parse_args()
recipient = {
"first_name": args.first_name,
"last_name": args.last_name,
"address_1": args.address,
"city": args.city,
"state": args.state,
"postal_code": args.zip
}
merge_tags = {}
if args.promo_code:
merge_tags["promo_code"] = args.promo_code
result = send_mailing(
campaign_id=args.campaign_id,
recipient=recipient,
creative_id=args.creative_id,
merge_tags=merge_tags if merge_tags else None
)
print(f"Mailing created: {result['id']}")
print(f"Status: {result['state']}")
print(f"Cost: ${result['total_cost']}")
print(f"PDF Preview: {result['pdf_url']}")
if __name__ == "__main__":
main()
CSV から一括送信
#!/usr/bin/env python3
"""Send batch mailings from a CSV file."""
import os
import csv
import time
import requests
import argparse
from typing import Generator
POPLAR_API_URL = "https://api.heypoplar.com/v1"
def read_recipients(csv_path: str) -> Generator[dict, None, None]:
"""Read recipients from CSV file."""
with open(csv_path, 'r') as f:
reader = csv.DictReader(f)
for row in reader:
yield {
"recipient": {
"first_name": row.get("first_name", ""),
"last_name": row.get("last_name", ""),
"address_1": row.get("address_1", ""),
"address_2": row.get("address_2", ""),
"city": row.get("city", ""),
"state": row.get("state", ""),
"postal_code": row.get("postal_code", "")
},
"merge_tags": {
k: v for k, v in row.items()
if k not in ["first_name", "last_name", "address_1",
"address_2", "city", "state", "postal_code"]
}
}
def send_batch(
csv_path: str,
campaign_id: str,
creative_id: str = None,
delay: float = 0.1
) -> tuple[int, int]:
"""Send batch mailings from CSV."""
token = os.environ.get("POPLAR_API_TOKEN")
if not token:
raise ValueError("POPLAR_API_TOKEN environment variable not set")
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
success_count = 0
error_count = 0
for record in read_recipients(csv_path):
payload = {
"campaign_id": campaign_id,
"recipient": record["recipient"]
}
if creative_id:
payload["creative_id"] = creative_id
if record["merge_tags"]:
payload["merge_tags"] = record["merge_tags"]
try:
response = requests.post(
f"{POPLAR_API_URL}/mailing",
headers=headers,
json=payload
)
response.raise_for_status()
success_count += 1
print(f"Sent to {record['recipient']['first_name']} {record['recipient']['last_name']}")
except requests.exceptions.RequestException as e:
error_count += 1
print(f"Error sending to {record['recipient']}: {e}")
time.sleep(delay)
return success_count, error_count
def main():
parser = argparse.ArgumentParser(description="Send batch Poplar mailings from CSV")
parser.add_argument("--csv", required=True, help="Path to CSV file")
parser.add_argument("--campaign-id", required=True, help="Campaign ID")
parser.add_argument("--creative-id", help="Creative ID (optional)")
parser.add_argument("--delay", type=float, default=0.1, help="Delay between requests (seconds)")
args = parser.parse_args()
success, errors = send_batch(
csv_path=args.csv,
campaign_id=args.campaign_id,
creative_id=args.creative_id,
delay=args.delay
)
print(f"\nCompleted: {success} sent, {errors} errors")
if __name__ == "__main__":
main()
API 接続をテスト
#!/usr/bin/env python3
"""Test Poplar API connection and list campaigns."""
import os
import requests
POPLAR_API_URL = "https://api.heypoplar.com/v1"
def test_connection():
"""Test API connection and list available campaigns."""
token = os.environ.get("POPLAR_API_TOKEN")
if not token:
print("Error: POPLAR_API_TOKEN environment variable not set")
return False
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/json"
}
# Test authentication
print("Testing API connection...")
try:
response = requests.get(f"{POPLAR_API_URL}/me", headers=headers)
response.raise_for_status()
org = response.json()
print(f"Connected to organization: {org.get('name', 'Unknown')}")
except requests.exceptions.RequestException as e:
print(f"Authentication failed: {e}")
return False
# List campaigns
print("\nAvailable campaigns:")
try:
response = requests.get(f"{POPLAR_API_URL}/campaigns", headers=headers)
response.raise_for_status()
campaigns = response.json()
if not campaigns:
print(" No campaigns found")
else:
for campaign in campaigns:
print(f" - {campaign.get('name', 'Unnamed')}: {campaign.get('id')}")
except requests.exceptions.RequestException as e:
print(f"Failed to list campaigns: {e}")
return False
return True
if __name__ == "__main__":
test_connection()
デザインのベストプラクティス
ビジュアルデザイン
- シンプルに:情報を詰め込みすぎないようにします
- 高いコントラスト:文字が読みやすいよう、コントラストの高い色を使います
- 大きなフォント:本文は最低10pt、見出しは24pt以上にします
- 高品質な画像:必ず300 PPI/DPIの解像度を使用します
- 端まで広げるデザイン:背景を塗り足し領域まで広げます
メッセージ
- 明確なCTAを1つに:各制作物につき、主要な行動喚起を1つにします
- オファーを目立たせる:表面と裏面の両方にプロモーションを表示します
- 短いURL:簡潔なURL(brand.com/save20)を使用します
- ソーシャルプルーフ:関連する場合は、お客様の声を含めます
- 緊急性:期間限定オファーには有効期限を追加します
QRコード
QRコード画像は事前に生成し、マージタグ内でURLとして指定する必要があります。PoplarはQRコードを自動生成しません。
QRコードの指定方法:
merge_tags = {
"promotion": {
"qr_url": "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=https://yoursite.com/offer123"
}
}
または、次のようなサービスを利用します:
- QR Code Generator API
- QRCode Monkey
- 独自のQRコード生成サービス
デザインガイドライン:
- 最小サイズ:0.75" x 0.75"(複雑なURLの場合はさらに大きくします)
- クワイエットゾーン:コードの周囲に余白を設けます
- 印刷前にテスト:QRコードが正しく機能することを必ず確認します
- URL短縮サービスを利用:URLが短いほど、QRコードはシンプルになります
- URLは大文字に:すべて大文字にすると、コードが小さくなり、読みやすくなります
住所ブロック
- 自動で追加:Poplarが住所ブロックを自動で追加します
- 背面のデザイン:住所エリアを覆うように背景を広げます
- 含めない:テンプレートに独自の住所ブロックを追加しないでください
テンプレートの例
4x6のポストカード - 表面(プロモーション)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;800&display=swap" rel="stylesheet">
<style>
body {
width: 4.25in;
height: 6.25in;
position: relative;
font-family: 'Poppins', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
margin: 0;
}
.safe-zone {
position: absolute;
top: 0.25in;
left: 0.25in;
right: 0.25in;
bottom: 0.25in;
}
.logo {
position: absolute;
top: 0;
left: 0;
width: 1.5in;
}
.headline {
position: absolute;
top: 1in;
left: 0;
right: 0;
font-size: 32pt;
font-weight: 800;
color: white;
text-align: center;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.subhead {
position: absolute;
top: 1.8in;
left: 0;
right: 0;
font-size: 16pt;
color: rgba(255,255,255,0.9);
text-align: center;
}
.offer-badge {
position: absolute;
top: 2.5in;
left: 50%;
transform: translateX(-50%);
width: 2.5in;
height: 2.5in;
background: white;
border-radius: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.offer-amount {
font-size: 48pt;
font-weight: 800;
color: #764ba2;
line-height: 1;
}
.offer-text {
font-size: 14pt;
color: #333;
text-transform: uppercase;
letter-spacing: 2px;
}
</style>
</head>
<body>
<div class="safe-zone">
<img src="https://your-domain.com/logo-white.svg" class="logo" alt="Logo">
<div class="headline">{{recipient.first_name | capitalize}}, You're Invited!</div>
<div class="subhead">Exclusive offer just for you</div>
<div class="offer-badge">
<div class="offer-amount">25%</div>
<div class="offer-text">Off Everything</div>
</div>
</div>
</body>
</html>
4x6のポストカード - 裏面(QRコード付き)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
<style>
body {
width: 4.25in;
height: 6.25in;
position: relative;
font-family: 'Poppins', sans-serif;
background: white;
margin: 0;
}
.content-area {
position: absolute;
top: 0.25in;
left: 0.25in;
width: 3.25in;
bottom: 0.25in;
}
.details {
position: absolute;
top: 0;
left: 0;
font-size: 11pt;
color: #333;
line-height: 1.6;
}
.promo-code-box {
position: absolute;
top: 1.5in;
left: 0;
background: #f8f8f8;
border: 2px dashed #764ba2;
padding: 0.15in 0.25in;
text-align: center;
}
.promo-label {
font-size: 10pt;
color: #666;
text-transform: uppercase;
}
.promo-code {
font-size: 18pt;
font-weight: 600;
color: #764ba2;
letter-spacing: 3px;
}
.expiry {
position: absolute;
top: 2.4in;
left: 0;
font-size: 9pt;
color: #999;
}
.qr-section {
position: absolute;
bottom: 0;
left: 0;
}
.qr-code {
width: 1in;
height: 1in;
}
.qr-text {
font-size: 9pt;
color: #666;
margin-top: 0.1in;
}
/* Address block area - leave clear for Poplar */
.address-area {
position: absolute;
top: 0.375in;
right: 0.25in;
width: 2.625in;
height: 1.125in;
/* This area will be covered by Poplar's address block */
}
</style>
</head>
<body>
<div class="content-area">
<div class="details">
Shop our entire collection with your<br>
exclusive discount. Free shipping on<br>
orders over $50!
</div>
<div class="promo-code-box">
<div class="promo-label">Your Code</div>
<div class="promo-code">{{promotion.promo_code}}</div>
</div>
<div class="expiry">
Expires: {{ "now" | date: "%s" | plus: 2592000 | date: "%B %e, %Y" }}
</div>
<div class="qr-section">
<img src="{{promotion.qr_url}}" class="qr-code" alt="Scan to shop">
<div class="qr-text">Scan to shop now</div>
</div>
</div>
</body>
</html>
トラブルシューティング
テンプレートの問題
「Unsupported unit」エラー
em、rem、vw、vh、%ではなく、inまたはpx単位のみを使用します
画像が表示されない
- すべての画像URLが絶対URL(
https://で始まる完全なURL)であることを確認します - URLに一般公開でアクセスできることを確認します(シークレットウィンドウでテストします)
- ファイルサイズが5MB未満であることを確認します
フォントが正しく表示されない
- Google Fontsまたはセルフホストしたフォントファイルを使用します
- TypekitとAdobe Fontsには対応していません
- 使用するすべてのフォントの太さを
<link>タグに含めます
マージタグが置換されない
- データの列見出しがマージタグ名と完全に一致していることを確認します
- Liquid構文が正しいことを確認します(入力ミスがないこと)
- 任意項目には
| default: "fallback"を使用します
APIの問題
401 Unauthorized
- APIトークンが正しいことを確認します
- メール配信以外のエンドポイントでテストトークンを使用していないか確認します(テストトークンは
/mailingでのみ機能します)
400 Bad Request
- キャンペーンが有効になっている必要があります
- クリエイティブがアップロードされている必要があります
- 必須の宛先フィールド:
address_1、city、state、postal_code
キャンペーンが有効でないというエラー
- 送信前にPoplarダッシュボードでキャンペーンを有効にします
プレビューの違い
ブラウザーとPDFのプレビューが異なる
- ブラウザーのプレビューではなく、PoplarのPDF校正を必ず確認します
- HTMLからPDFへの変換では、一部のCSSが異なる形でレンダリングされる場合があります
- PDFレンダリングで十分にサポートされていないCSS機能は避けます