WhatsApp 营销模板
营销模板用于发送促销内容,例如优惠活动、产品公告或优惠券。发送前,请准备好已审核通过的内容、收件人许可,以及处理回复和退订的明确方式。
发送前的准备
Bird 的托管目录中不包含任何营销模板,因此营销发送始终使用您的工作区自行创建的模板,并需要您自己的 WhatsApp Business Account:
- 关联您自己的 WhatsApp Business Account 和号码。参阅电话号码设置。
- 创建类别为 marketing 的模板并提交审核。参阅模板指南了解审核通过的要求。
- 从与模板同属一个 WhatsApp Business Account 的号码发送。营销发送要求 from,使用不同账户的发送号码会在产生任何费用之前被拒绝,返回 422 E15023 WhatsAppSenderWABAMismatch。
发送营销模板
设置 from 的 POST /v1/whatsapp/messages,以及一个指定您自己 slug 的 template 对象:
const msg = await bird.whatsapp.send({
to: "+16505551234",
from: "+13125550101",
template: {
slug: "summer_sale",
language: "en",
components: [
{
type: "header",
parameters: [{ type: "image", url: "https://cdn.example.com/banners/summer.png" }],
},
{ type: "body", parameters: [{ type: "text", name: "first_name", text: "Pablo" }] },
{ type: "button", parameters: [{ type: "text", text: "SUMMER25" }] },
],
},
});
console.log(msg.id, msg.status);msg = client.whatsapp.send(
to="+16505551234",
from_="+13125550101",
template="summer_sale",
language="en",
components=[
{
"type": "header",
"parameters": [{"type": "image", "url": "https://cdn.example.com/banners/summer.png"}],
},
{"type": "body", "parameters": [{"type": "text", "name": "first_name", "text": "Pablo"}]},
{"type": "button", "parameters": [{"type": "text", "text": "SUMMER25"}]},
],
)
print(msg.id, msg.status)package main
import (
"context"
"fmt"
"log"
"os"
bird "github.com/messagebird/bird-sdk-go"
"github.com/messagebird/bird-sdk-go/option"
)
func main() {
client, err := bird.NewClient(option.WithAPIKey(os.Getenv("BIRD_API_KEY")))
if err != nil {
log.Fatal(err)
}
name := "Pablo"
nameKey := "first_name"
banner := "https://cdn.example.com/banners/summer.png"
coupon := "SUMMER25"
components := []bird.WhatsAppMessageTemplateComponent{
{Type: "header", Parameters: &[]bird.WhatsAppMessageTemplateComponentParameter{{Type: "image", Url: &banner}}},
{Type: "body", Parameters: &[]bird.WhatsAppMessageTemplateComponentParameter{{Type: "text", Name: &nameKey, Text: &name}}},
{Type: "button", Parameters: &[]bird.WhatsAppMessageTemplateComponentParameter{{Type: "text", Text: &coupon}}},
}
msg, err := client.Whatsapp.Send(context.Background(), bird.WhatsappSendParams{
To: "+16505551234",
From: "+13125550101",
Template: "summer_sale",
Language: "en",
Components: components,
})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id, *msg.Status)
}$components = [
(new WhatsAppMessageTemplateComponent())
->setType('header')
->setParameters([
(new WhatsAppMessageTemplateComponentParameter())->setType('image')->setUrl('https://cdn.example.com/banners/summer.png'),
]),
(new WhatsAppMessageTemplateComponent())
->setType('body')
->setParameters([
(new WhatsAppMessageTemplateComponentParameter())->setType('text')->setName('first_name')->setText('Pablo'),
]),
(new WhatsAppMessageTemplateComponent())
->setType('button')
->setParameters([
(new WhatsAppMessageTemplateComponentParameter())->setType('text')->setText('SUMMER25'),
]),
];
$message = $bird->whatsapp->send(
to: '+16505551234',
from: '+13125550101',
template: 'summer_sale',
language: 'en',
components: $components,
);
echo $message->getId(), ' ', $message->getStatus();bird whatsapp send \
--from +13125550101 \
--components '[{"parameters":[{"type":"image","url":"https://cdn.example.com/banners/summer.png"}],"type":"header"},{"parameters":[{"name":"first_name","text":"Pablo","type":"text"}],"type":"body"},{"parameters":[{"text":"SUMMER25","type":"text"}],"type":"button"}]' \
--language en \
--template summer_sale \
--to +16505551234{
"name": "whatsapp_send",
"arguments": {
"from": "+13125550101",
"template": {
"components": [
{
"parameters": [
{
"type": "image",
"url": "https://cdn.example.com/banners/summer.png"
}
],
"type": "header"
},
{
"parameters": [
{
"name": "first_name",
"text": "Pablo",
"type": "text"
}
],
"type": "body"
},
{
"parameters": [
{
"text": "SUMMER25",
"type": "text"
}
],
"type": "button"
}
],
"language": "en",
"slug": "summer_sale"
},
"to": "+16505551234"
}
}curl -X POST "https://us1.platform.bird.com/v1/whatsapp/messages" \
-H "Authorization: Bearer $BIRD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+16505551234",
"from": "+13125550101",
"template": {
"slug": "summer_sale",
"language": "en",
"components": [
{
"type": "header",
"parameters": [
{
"type": "image",
"url": "https://cdn.example.com/banners/summer.png"
}
]
},
{
"type": "body",
"parameters": [
{
"type": "text",
"name": "first_name",
"text": "Pablo"
}
]
},
{
"type": "button",
"parameters": [
{
"type": "text",
"text": "SUMMER25"
}
]
}
]
}
}'- 正文参数是命名的,与 utility 模板相同。每个参数都带有一个 name,数组中的顺序没有意义。
- 优惠券按钮的代码是一个普通的 text 参数,与上方按钮上的参数相同。没有单独的优惠券代码参数类型。
- gif 头部使用 gif 参数,而非 video 或 image。营销是唯一接受动画 GIF 头部的类别。
- 轮播的值放在 cards 上,而非 parameters,发送时必须提供与模板审核通过时完全相同数量的卡片。
Bird 会自动将每次营销发送路由到 Meta 的 Marketing Messages API;您无需选择加入,也没有逐次发送的开关。商业账户在该 API 上的入驻状态影响的是 Meta 的优化功能而非送达本身,但有一个例外:gif 头部需要已入驻的账户,否则发送会在 WhatsApp 失败。参阅 Marketing templates 了解账户状态、入驻解锁的功能以及营销按国家/地区受限的情况。
退订
当 Bird 收到 Meta 发来的有效营销停止事件时,会为该商业账户记录一条收件人偏好。该偏好与全消息抑制是分开的。发送前请同时检查这两项记录。投递失败可能在对应的偏好事件之前到达;请保留收件人的选择并调查该历史记录,而非重试。请参阅退订了解如何记录和读取这些记录。
费用
请使用目标地区和币种对应的已公布营销费率。Bird 在提交前收取出站费用;可计费的投递或已读结果可能会额外产生 Meta 的费用。请参阅费用与计费和 WhatsApp 定价。
注意事项
- Meta 只会将模板重新归类为营销,不会从营销移出,这意味着价格变化。 如果 Meta 认定模板内容实质上具有推广性,无论您提交时选择了什么类别,该模板都会变为 marketing,并以更高的新价格继续发送。无法选择退出,也无法将类别改回;唯一的补救办法是创建新模板。
- 131049 是一种投递暂停,不是您配置的速率限制,重试只会使情况恶化。 Meta 对美国范围的全面暂停和针对单用户的营销上限都报告 131049,其自身的建议是等待大约一天后再重新发送。过早重新发送可能导致该接收者更长时间无法接收消息,并影响您的送达率。Bird 将该失败报告为 rate_limited。
- 131050 表示接收者已关闭 "Offers and announcements",绝不能重试。 Meta 接受发送请求但拒绝投递。正确的响应是走消息偏好设置路径,而非重新发送:自行屏蔽该接收者,或等待其重新启用投递,Bird 会通过报告停止的同一偏好设置机制得知该变化。参阅退订。
- 132015 和 132016 是模板暂停,而非接收者问题。 132015 是因质量低下而暂停;132016 是多次暂停后的永久停用,唯一的补救办法是用不同内容创建新模板。检查语言本身的状态而非模板的状态,因为被暂停的语言会立即停止发送。
- 使用错误 WhatsApp Business Account 的发送号码会在产生任何费用之前被拒绝。 from 必须与模板在同一账户上,否则发送失败,返回 422 E15023 WhatsAppSenderWABAMismatch。
后续步骤
- WhatsApp templates:浏览目录以及共享的按模板发送约定
- Marketing templates:Marketing Messages API、入驻状态以及营销受限的地区
- 退订:记录和查询屏蔽与偏好设置
- Utility templates:订单更新、预约提醒和账户通知