Chiến dịch email
Chiến dịch email (broadcast) là email marketing, như bản tin, chương trình giảm giá hay thông báo sản phẩm mới, gửi tới mọi contact của một segment. Hạ Tầng tách chiến dịch thành mỗi contact một email, điền tên và thuộc tính của từng người, bỏ qua người đã huỷ đăng ký, rồi thêm link huỷ đăng ký và header List-Unsubscribe một chạm.
Trước khi bắt đầu
- Một tên miền đã xác minh cho địa chỉ
From. Xem Tên miền. - Một segment có contact, và các topic nếu muốn người nhận tự chọn loại thư. Xem Contacts, segments và topics.
- Nếu dùng API, key có
email:write.
Chiến dịch chọn người nhận thế nào
Một contact nhận chiến dịch khi thoả cả bốn điều kiện:
- Contact thuộc segment của chiến dịch.
- Contact chưa huỷ đăng ký mọi email.
- Nếu chiến dịch có topic: contact đang nhận topic đó. Với topic
opt_in, nghĩa là contact chưa rời topic; với topicopt_out, contact phải đã tự đăng ký. - Địa chỉ không nằm trong danh sách chặn (trả lại cứng, báo spam, chặn thủ công). Địa chỉ bị chặn được bỏ qua và báo là bị trả lại. Xem Danh sách chặn và thư trả lại.
Soạn
Trong dashboard, mở Email → Chiến dịch email → Chiến dịch email mới:
- Segment: ai nhận. Topic: không bắt buộc; Không gắn topic (mọi người đang nhận) gửi cho mọi contact đang nhận trong segment.
- Tên nội bộ, Người gửi và Tiêu đề.
- Bắt đầu từ template chép HTML, văn bản, người gửi và tiêu đề của một template đã publish vào chiến dịch.
- Nội dung HTML và bản Văn bản thuần (không bắt buộc), kèm Xem trước.
Lưu bằng Tạo nháp. Chiến dịch sửa được khi còn là bản nháp hoặc đang hẹn giờ.
Thẻ trộn
Thẻ trộn được điền cho từng contact:
| Thẻ | Giá trị |
|---|---|
{{{FIRST_NAME}}}, {{{LAST_NAME}}}, {{{EMAIL}}} | Tên và địa chỉ của contact. |
{{{contact.first_name}}}, {{{contact.plan}}}… | Trường của contact và thuộc tính tuỳ chỉnh. |
{{{UNSUBSCRIBE_URL}}} | Trang tuỳ chọn của contact. {{{RESEND_UNSUBSCRIBE_URL}}} cũng dùng được. |
Thêm giá trị mặc định sau dấu |: Chào {{{FIRST_NAME|bạn}}}. Thuộc tính chưa có giá trị thì lấy giá trị dự phòng. Thẻ không có giá trị nào sẽ thành chuỗi rỗng; không làm dừng việc gửi. Giá trị được escape trong phần HTML.
<p>Chào {{{FIRST_NAME|bạn}}},</p>
<p>Gói {{{contact.plan|free}}} của bạn giờ có thêm 10 GB lưu trữ.</p>
<p><a href="{{{UNSUBSCRIBE_URL}}}">Huỷ đăng ký hoặc chọn loại email muốn nhận</a></p>
Tạo bằng API
- cURL
- Node.js
- Python
- PHP
curl -X POST https://api.hatang.dev/broadcasts \
-H "Authorization: Bearer $HATANG_API_KEY" -H "Content-Type: application/json" \
-d '{
"name": "Bản tin tháng 10",
"segment_id": "aud_0199e6c17a2b7f009c1e3f6a2b1d9e10",
"topic_id": "topic_0199e6c17a2c7c418f0e61f1b2a3c4d5",
"from": "Shop <[email protected]>",
"subject": "Có gì mới tháng 10, {{{FIRST_NAME|bạn}}}",
"html": "<p>Chào {{{FIRST_NAME|bạn}}}, …</p><p><a href=\"{{{UNSUBSCRIBE_URL}}}\">Huỷ đăng ký</a></p>"
}'
# { "object": "broadcast", "id": "bc_…" }
const { data, error } = await hatang.broadcasts.create({
name: "Bản tin tháng 10",
segment_id: "aud_0199e6c17a2b7f009c1e3f6a2b1d9e10",
topic_id: "topic_0199e6c17a2c7c418f0e61f1b2a3c4d5",
subject: "Có gì mới tháng 10, {{{FIRST_NAME|bạn}}}",
html: '<p>Chào {{{FIRST_NAME|bạn}}}, …</p><p><a href="{{{UNSUBSCRIBE_URL}}}">Huỷ đăng ký</a></p>',
});
res = requests.post(
"https://api.hatang.dev/broadcasts",
headers={"Authorization": f"Bearer {os.environ['HATANG_API_KEY']}"},
json={
"name": "Bản tin tháng 10",
"segment_id": "aud_0199e6c17a2b7f009c1e3f6a2b1d9e10",
"subject": "Có gì mới tháng 10",
"html": '<p>Chào {{{FIRST_NAME|bạn}}}, …</p><p><a href="{{{UNSUBSCRIBE_URL}}}">Huỷ đăng ký</a></p>',
},
)
broadcast_id = res.json()["id"]
$res = $client->post('/broadcasts', [
'headers' => ['Authorization' => 'Bearer ' . getenv('HATANG_API_KEY')],
'json' => [
'name' => 'Bản tin tháng 10',
'segment_id' => 'aud_0199e6c17a2b7f009c1e3f6a2b1d9e10',
'subject' => 'Có gì mới tháng 10',
'html' => '<p>Chào {{{FIRST_NAME|bạn}}}, …</p><p><a href="{{{UNSUBSCRIBE_URL}}}">Huỷ đăng ký</a></p>',
],
]);
$broadcastId = json_decode($res->getBody(), true)['id'];
| Trường | Mô tả |
|---|---|
segment_id | Bắt buộc. Segment nhận chiến dịch. audience_id là tên cũ, vẫn dùng được. |
topic_id | Không bắt buộc. Chỉ gửi cho contact đang nhận topic này. |
template_id | Không bắt buộc. Chép html, text, from, subject và reply_to từ template đã publish này. Trường bạn tự gửi được ưu tiên. |
from | Bắt buộc, trừ khi template đã có. |
subject | Bắt buộc, trừ khi template đã có. Tối đa 998 ký tự. |
reply_to | Một địa chỉ hoặc một danh sách. |
html, text | Nội dung. Phải có ít nhất một trong hai trước khi gửi. |
name | Tên nội bộ, tối đa 200 ký tự. Mặc định là tiêu đề. |
Template được chép lúc tạo chiến dịch; publish phiên bản template mới sau đó không làm thay đổi chiến dịch.
Gửi hoặc hẹn giờ
Trong trình soạn, ô Gửi nhận now, một thời điểm ISO hoặc một cụm như in 2 hours. Lưu thay đổi trước khi bấm Gửi chiến dịch. Bằng API:
- cURL
- Node.js
- Python
- PHP
# Gửi ngay
curl -X POST https://api.hatang.dev/broadcasts/$BROADCAST_ID/send -H "Authorization: Bearer $HATANG_API_KEY"
# Hẹn giờ
curl -X POST https://api.hatang.dev/broadcasts/$BROADCAST_ID/send \
-H "Authorization: Bearer $HATANG_API_KEY" -H "Content-Type: application/json" \
-d '{ "scheduled_at": "2026-10-20T09:00:00+07:00" }'
# { "object": "broadcast", "id": "bc_…", "status": "scheduled" }
await hatang.broadcasts.send(broadcastId); // gửi ngay
await hatang.broadcasts.send(broadcastId, { scheduledAt: "in 2 hours" }); // hẹn giờ
headers = {"Authorization": f"Bearer {os.environ['HATANG_API_KEY']}"}
requests.post(f"https://api.hatang.dev/broadcasts/{broadcast_id}/send", headers=headers, json={"scheduled_at": "in 2 hours"})
$client->post("/broadcasts/$broadcastId/send", [
'headers' => ['Authorization' => 'Bearer ' . getenv('HATANG_API_KEY')],
'json' => ['scheduled_at' => 'in 2 hours'],
]);
scheduled_at nhận các giá trị giống email đơn lẻ: thời điểm ISO 8601 trong vòng 30 ngày tới, hoặc in N minutes|hours|days. Gửi lại một chiến dịch đang hẹn giờ sẽ thay lịch cũ.
POST /broadcasts/{id}/cancel dừng chiến dịch đang hẹn giờ hoặc còn trong hàng đợi. Chiến dịch đang gửi thì không huỷ hay xoá được.
| Trạng thái | Ý nghĩa |
|---|---|
draft | Chưa gửi. Sửa được. |
scheduled | Đang chờ tới giờ. Vẫn sửa hoặc huỷ được. |
queued | Sắp bắt đầu. |
sending | Đang tạo email cho từng contact. |
sent | Đã xử lý xong mọi người nhận. |
canceled | Đã huỷ trước khi bắt đầu. |
GET /broadcasts/{id} cho biết total (số contact được chọn) và sent_count (số email đã tạo). Mỗi email của chiến dịch là một email bình thường có tag broadcast_id, nên bạn theo dõi được ở Email → Email (lọc Chiến dịch email), qua GET /emails?source=broadcast và các webhook quen thuộc. Nếu việc gửi bị gián đoạn, Hạ Tầng tiếp tục từ chỗ dừng và không bao giờ gửi cùng một chiến dịch hai lần cho một contact.
Mỗi email của chiến dịch đều tính vào hạn mức ngày. Chiến dịch gửi cho nhiều contact hơn hạn mức còn lại sẽ dừng gửi khi hết hạn mức; các contact còn lại được đánh dấu lỗi. Xem Cài đặt email trước khi gửi số lượng lớn.
Huỷ đăng ký
Mỗi email của chiến dịch có hai lối thoát:
- Link
{{{UNSUBSCRIBE_URL}}}trong nội dung mở trang tuỳ chọn, nơi contact chọn topic hoặc huỷ đăng ký mọi email. Chỉ mở trang thì không thay đổi gì. - Header
List-UnsubscribecùngList-Unsubscribe-Post: List-Unsubscribe=One-Click(RFC 8058), để Gmail, Apple Mail và các ứng dụng khác hiện nút huỷ đăng ký của riêng chúng. Một chạm sẽ gỡ contact khỏi topic của chiến dịch, hoặc khỏi mọi email nếu chiến dịch không gắn topic.
Hạ Tầng tự thêm header. Hãy đặt cả link trong nội dung: một số ứng dụng email không hiện nút, và pháp luật nhiều nước yêu cầu cách huỷ đăng ký phải nhìn thấy được.
Khi contact huỷ đăng ký mọi email, họ không còn nhận chiến dịch email và email của automation, đồng thời địa chỉ được đưa vào danh sách chặn với lý do unsubscribe, chặn luôn các email khác gửi tới địa chỉ đó. Xem Danh sách chặn và thư trả lại.
Đổi giao diện trang tuỳ chọn ở Cài đặt email → Trang huỷ đăng ký: tiêu đề, mô tả, logo và màu.
Tham chiếu API
| Endpoint | Mô tả |
|---|---|
POST /broadcasts | Tạo bản nháp. Trả về 201 kèm id. |
GET /broadcasts, GET /broadcasts/{id} | Liệt kê, hoặc xem một chiến dịch kèm status, total, sent_count, scheduled_at và sent_at. |
PATCH /broadcasts/{id} | Sửa chiến dịch nháp hoặc đang hẹn giờ. |
POST /broadcasts/{id}/send | Gửi ngay hoặc hẹn giờ (scheduled_at). |
POST /broadcasts/{id}/cancel | Huỷ chiến dịch đang hẹn giờ hoặc trong hàng đợi. |
DELETE /broadcasts/{id} | Xoá, trừ khi đang gửi. |