Mở đầu — vì sao bài này quan trọng
Push notification là một trong số ít tính năng mà PM có thể "chạm" trực tiếp vào người dùng kể cả khi họ đã đóng app. Đó vừa là sức mạnh, vừa là con dao hai lưỡi. Một push được viết tốt có thể kéo người dùng quay lại, hồi sinh một giỏ hàng bị bỏ quên, hay nhắc một học viên hoàn thành bài học. Nhưng một push viết ẩu — sai giờ, sai nội dung, gửi quá nhiều — sẽ khiến người dùng tắt notification vĩnh viễn, và một khi họ tắt rồi thì gần như không có đường quay lại.
Vấn đề là rất nhiều PM coi push notification như một dòng chữ ngắn cần "điền vào ô trống", và viết nó trong vài phút ngay trước khi ship. Họ quên rằng push là một hệ thống có hợp đồng kỹ thuật (payload, deeplink, trigger), có ràng buộc nền tảng (iOS khác Android), có vấn đề về quyền (permission), về tần suất (frequency capping), và về đo lường (làm sao biết push này có hiệu quả hay không). Tất cả những thứ đó phải nằm trong spec, chứ không phải để engineer hoặc growth team tự đoán.
Trong bài này, chúng ta sẽ học cách viết một push notification spec đầy đủ: từ các thành phần hiển thị (title, body, image), đến trigger và segment, payload kỹ thuật, hành vi khi người dùng chạm vào (gắn với deeplink — thứ bạn đã học ở Bài 26), cho đến frequency capping và analytics. Mục tiêu là sau bài này, bạn có thể giao một spec cho engineer mà họ không cần hỏi lại, và growth team có thể đo được hiệu quả ngay từ ngày đầu.
Khái niệm cốt lõi
Một push notification spec hoàn chỉnh gồm bốn nhóm thông tin: nội dung hiển thị, trigger & targeting, hợp đồng kỹ thuật (payload), và chính sách & đo lường. Nếu thiếu bất kỳ nhóm nào, push của bạn sẽ "lủng" ở đâu đó.
1. Nội dung hiển thị (Display components)
Đây là phần người dùng nhìn thấy, gồm các thành phần:
- Title — tiêu đề. Quy ước chung là ≤ 30 ký tự và action-oriented (hướng hành động). Title bị cắt rất sớm trên màn hình khóa, nhất là tiếng Việt có dấu chiếm nhiều không gian hiển thị. Title tốt nói rõ "có gì cho tôi", không chỉ là tên app.
- Body — nội dung chính. Quy ước ≤ 80 ký tự và nên personalize (cá nhân hóa) khi có thể: gọi tên, nhắc đúng sản phẩm họ đã xem, đúng số tiền. Body dài hơn 80 ký tự vẫn gửi được nhưng sẽ bị "…" trên đa số thiết bị khi notification chưa được mở rộng.
- Image / rich media (tùy chọn) — ảnh lớn cho rich notification. Trên iOS gọi là Notification Service Extension với rich media; trên Android là
BigPictureStyle. Cần spec rõ kích thước, tỷ lệ, dung lượng tối đa, và luôn phải có fallback khi ảnh tải lỗi (notification vẫn hiển thị bình thường, chỉ mất ảnh). - Icon & badge — icon nhỏ (Android cần icon đơn sắc), và badge count (con số đỏ trên icon app của iOS). Badge phải có logic rõ ràng: tăng khi nào, reset về 0 khi nào.
- Deeplink (tap action) — nơi người dùng được đưa đến khi chạm vào. Đây chính là cầu nối với Bài 26: mỗi push phải khai báo một deeplink đích, kèm fallback khi deeplink không mở được.
2. Trigger & targeting
Push không tự nhiên xuất hiện. Bạn phải định nghĩa:
- Trigger type:
transactional(kích hoạt bởi một sự kiện cụ thể của user — ví dụ "đơn hàng đã giao") hayscheduled/campaign(gửi theo lịch hoặc theo chiến dịch marketing). - Audience / segment: ai sẽ nhận. Ví dụ "user đã thêm sản phẩm vào giỏ trong 24h qua nhưng chưa thanh toán".
- Timing rules: gửi ngay, hay theo múi giờ của user (quiet hours — không gửi 22h–7h), hay đợi đến "thời điểm vàng".
3. Hợp đồng kỹ thuật (Payload contract)
Đây là phần phân biệt một spec nghiệp dư với một spec chuyên nghiệp. Payload là JSON mà server gửi qua APNs (Apple) hoặc FCM (Firebase Cloud Messaging cho Android). Spec cần liệt kê đầy đủ các trường, đặc biệt là custom data đi kèm để app biết phải làm gì.
4. Chính sách & đo lường
- Permission state: nếu user chưa cấp quyền push thì sao? Spec phải định nghĩa thời điểm xin quyền (pre-permission priming) và hành vi khi bị từ chối.
- Frequency capping: tối đa bao nhiêu push/ngày, /tuần cho một user, và ưu tiên loại nào khi đụng trần.
- Analytics: các event cần track —
push_sent,push_delivered,push_opened, và conversion sau khi mở.
Tình huống thực tế
Ví dụ 1 — Tiki và cuộc chiến giỏ hàng bị bỏ quên
Giả định một PM tại Tiki phụ trách growth muốn làm push nhắc giỏ hàng (abandoned cart). Phiên bản spec đầu tiên chỉ ghi: "Gửi push nhắc user quay lại thanh toán." Engineer làm xong, gửi đi, và kết quả là tỷ lệ mở chỉ 4%, kèm một loạt user phàn nàn vì nhận push lúc 1 giờ sáng.
PM viết lại spec với đầy đủ thành phần:
- Title: "Giỏ hàng đang chờ bạn 🛒" (28 ký tự) — nhưng theo luật no-emoji của nhiều brand thì thay bằng "Còn món đồ chưa thanh toán" để an toàn.
- Body: "{tên}, {tên sản phẩm} trong giỏ sắp hết khuyến mãi. Hoàn tất ngay nhé!" — personalize tên user và tên sản phẩm cụ thể, giữ dưới 80 ký tự bằng cách rút gọn tên sản phẩm nếu quá dài.
- Deeplink:
tiki://cart(mở thẳng giỏ hàng), fallback vềhttps://tiki.vn/cart. - Trigger: transactional, kích hoạt 3 giờ sau khi user rời app mà giỏ còn hàng.
- Timing: tôn trọng quiet hours theo múi giờ user — nếu 3 giờ đó rơi vào 22h–7h thì hoãn đến 8h sáng.
- Frequency cap: tối đa 1 push abandoned cart / giỏ hàng, không lặp lại.
Ví dụ 2 — Grab và payload "câm"
Một PM tại một super-app gọi xe (giả định kiểu Grab) làm tính năng push thông báo "Tài xế đã đến". Spec chỉ mô tả title và body, không nói gì về payload. Khi user chạm vào push, app mở ra... màn hình home, vì engineer không biết phải đưa user tới đâu, và payload không chứa mã chuyến đi.
PM bổ sung phần payload contract vào spec:
{
"aps": {
"alert": {
"title": "Tài xế đã đến",
"body": "Anh Minh (Toyota Vios · 51F-123.45) đang chờ bạn"
},
"sound": "default",
"badge": 1
},
"type": "driver_arrived",
"ride_id": "R-88231",
"deeplink": "grab://ride/R-88231",
"expiry": 1719500000
}
Điểm mấu chốt: trường ride_id và deeplink nằm trong custom data để app mở thẳng màn hình theo dõi đúng chuyến. Trường expiry để hệ thống bỏ qua push đã cũ (nếu user offline 10 phút rồi mới online, push "tài xế đã đến" không còn ý nghĩa). PM cũng spec rõ priority: push này là high priority / time-sensitive, được phép vượt qua chế độ Focus của iOS.
Bài học: title và body chỉ là phần nổi. Phần custom payload (ride_id, deeplink, expiry, priority) mới quyết định push có "thông minh" hay không. Một spec thiếu payload contract sẽ đẩy quyết định kỹ thuật sang engineer, và họ sẽ đoán — thường là đoán sai.
Ví dụ 3 — Một app học tiếng Anh và bẫy permission
Một startup edtech Việt Nam làm app học từ vựng, muốn dùng push nhắc học mỗi ngày. PM ban đầu cho hiện hộp xin quyền push ngay khi user vừa mở app lần đầu. Kết quả: 70% user bấm "Không cho phép" vì chưa hiểu giá trị, và một khi đã từ chối ở cấp hệ điều hành, app gần như không thể xin lại.
PM viết lại spec với pre-permission priming: thay vì gọi thẳng hộp quyền của hệ thống, app hiện một màn hình giải thích của riêng mình — "Bạn muốn được nhắc học mỗi ngày lúc mấy giờ?" với nút "Có, nhắc tôi lúc 20h". Chỉ khi user bấm đồng ý, app mới gọi hộp quyền thật của iOS. Spec cũng định nghĩa rõ:
- Nếu user từ chối ở màn hình priming → không gọi hộp hệ thống, ghi nhận để hỏi lại sau 7 ngày.
- Nếu user đã từ chối ở cấp hệ điều hành → hiện banner mềm trong app "Bật thông báo trong Cài đặt để không bỏ lỡ" với deeplink tới Settings.
Hướng dẫn từng bước
Đây là quy trình viết một push notification spec từ đầu:
Bước 1 — Xác định mục tiêu và loại push. Push này để làm gì (re-engagement, transactional, marketing)? Đo bằng metric nào (mở app, conversion, hoàn thành tác vụ)? Ghi rõ một câu mục tiêu ở đầu spec.
Bước 2 — Viết nội dung hiển thị. Soạn title (≤ 30 ký tự, hướng hành động), body (≤ 80 ký tự, personalize). Luôn viết phiên bản tiếng Việt thật và đếm ký tự — đừng dùng placeholder lorem ipsum. Liệt kê các biến personalize ({first_name}, {product_name}) kèm giá trị mặc định khi biến rỗng.
Bước 3 — Định nghĩa trigger và segment. Sự kiện nào kích hoạt? Ai nhận? Có quiet hours không? Có delay không? Viết dưới dạng điều kiện rõ ràng để engineer implement được mà không cần hỏi.
Bước 4 — Viết payload contract. Liệt kê toàn bộ JSON payload: phần hiển thị (aps/notification), và phần custom data (type, deeplink, các id cần thiết, expiry, priority). Đây là phần bắt buộc, không được bỏ qua.
Bước 5 — Khai báo tap action / deeplink. Mỗi push trỏ tới deeplink nào? Fallback ra sao nếu deeplink không mở được? (Tham chiếu Bài 26.)
Bước 6 — Định nghĩa permission flow. Khi nào xin quyền, priming ra sao, xử lý thế nào khi bị từ chối.
Bước 7 — Đặt frequency cap. Tối đa bao nhiêu push loại này / user / ngày? Khi đụng trần nhiều loại push cùng lúc, ưu tiên loại nào?
Bước 8 — Spec analytics. Liệt kê event cần track và định nghĩa "thành công" (ví dụ: open rate, và conversion trong 1 giờ sau khi mở).
Bước 9 — Liệt kê khác biệt nền tảng. Ghi rõ những chỗ iOS và Android xử lý khác nhau (badge, channel của Android, rich media, priority) để QA test đủ cả hai.
Lỗi thường gặp & mẹo
Lỗi 1 — Chỉ viết title/body, bỏ qua payload. Đây là lỗi phổ biến nhất. Hậu quả là tap action sai và app không có dữ liệu để xử lý. Mẹo: luôn kèm một block JSON payload mẫu trong spec.
Lỗi 2 — Không đếm ký tự, không tính dấu tiếng Việt. Title 30 ký tự tiếng Anh khác với 30 ký tự tiếng Việt có dấu về độ rộng hiển thị. Mẹo: thử preview trên màn hình khóa thật của cả iPhone và một máy Android, ở cả light/dark mode.
Lỗi 3 — Quên fallback cho biến personalize. Khi {first_name} rỗng, body biến thành "Chào , giỏ hàng…". Mẹo: luôn định nghĩa giá trị mặc định ("Chào bạn,").
Lỗi 4 — Bỏ quên quiet hours và múi giờ. Gửi push lúc 2 giờ sáng là cách nhanh nhất để bị tắt notification. Mẹo: mặc định tôn trọng quiet hours theo timezone user, trừ push time-sensitive thật sự.
Lỗi 5 — Không có frequency cap. Nhiều campaign chạy song song dội bom một user 8 push/ngày. Mẹo: đặt cap toàn cục cho marketing push (ví dụ ≤ 2/ngày), tách riêng transactional push.
Lỗi 6 — Quên expiry/TTL. Push "flash sale còn 10 phút" mà tới sau khi sale kết thúc thì phản tác dụng. Mẹo: spec expiry cho mọi push nhạy thời gian.
Lỗi 7 — Xin quyền sai thời điểm. Hỏi quyền ngay màn hình đầu là tự bắn vào chân. Mẹo: dùng pre-permission priming, xin quyền sau khi user đã thấy giá trị.
Bài tập thực hành
Hãy chọn một sản phẩm bạn quen thuộc (hoặc app bạn đang dùng) và viết một push notification spec hoàn chỉnh cho một tình huống cụ thể — ví dụ "nhắc người dùng hoàn thành khóa học còn dang dở".
Yêu cầu spec của bạn phải có đủ:
- Một câu mục tiêu + metric thành công.
- Title (≤ 30 ký tự) và body (≤ 80 ký tự) bằng tiếng Việt thật, có ít nhất một biến personalize kèm giá trị mặc định. Đếm và ghi số ký tự.
- Trigger, segment, và timing rule (gồm quiet hours).
- Một block JSON payload đầy đủ, có custom data:
type,deeplink, ít nhất một id, vàexpiry. - Tap action / deeplink đích kèm fallback.
- Permission flow (priming + xử lý từ chối).
- Frequency cap.
- Danh sách analytics event và định nghĩa "thành công".
Tóm tắt
Push notification không phải một dòng chữ — nó là một hệ thống nhỏ với hợp đồng kỹ thuật riêng. Một spec tốt gồm bốn nhóm: nội dung hiển thị (title ≤ 30 ký tự hướng hành động, body ≤ 80 ký tự personalize, image tùy chọn có fallback), trigger & targeting (loại push, segment, timing với quiet hours), payload contract (JSON đầy đủ với custom data: type, deeplink, id, expiry, priority), và chính sách & đo lường (permission flow, frequency cap, analytics).
Ba bài học cốt lõi từ các tình huống thực tế: (1) giá trị của push nằm nhiều ở trigger/timing/frequency hơn là câu chữ; (2) payload contract là thứ phân biệt spec chuyên nghiệp — thiếu nó, engineer phải đoán; (3) permission là một phần của push spec, và bạn chỉ có một cơ hội xin quyền nên đừng phí phạm. Viết push spec đầy đủ ngay từ đầu là cách tôn trọng cả người dùng lẫn đội kỹ thuật của bạn.