Gửi Tin Nhắn Zalo Từ Google Sheets Bằng Apps Script: 6 Bước

Bạn có một danh sách khách hàng trong Google Sheets và muốn gửi tin nhắn Zalo hàng loạt mà không cần công cụ trung gian nào?
Bài viết hướng dẫn từng bước dùng Google Apps Script gọi trực tiếp Zalo OA Message API để gửi tin nhắn từ dữ liệu trong Sheets — kèm code mẫu thực tế, xử lý lỗi và ghi log kết quả gửi.
Tổng Quan: Vì Sao Nên Gửi Tin Zalo Trực Tiếp Từ Google Sheets?
Nhiều doanh nghiệp nhỏ có sẵn danh sách khách hàng, đơn hàng, lịch hẹn trong Google Sheets nhưng phải copy thủ công từng số điện thoại/Zalo ID để nhắn tin. Với Google Apps Script — công cụ lập trình miễn phí đi kèm Google Sheets — bạn có thể gọi thẳng Zalo OA Message API để gửi tin nhắn tự động ngay từ dòng dữ liệu, không cần Zapier, Make hay bất kỳ dịch vụ trả phí nào.
Toàn bộ hướng dẫn dưới đây dùng hàm UrlFetchApp có sẵn trong Apps Script để gọi endpoint chính thức của Zalo OA: https://openapi.zalo.me/v2.0/oa/message.
Bước 1: Đăng Ký Zalo OA Và Bật API
Trước khi viết bất kỳ dòng code nào, bạn cần có một Zalo Official Account (OA) đã được duyệt và bật quyền truy cập API.
- Truy cập Zalo for Business (business.zalo.me), đăng nhập bằng tài khoản Zalo cá nhân.
- Tạo hoặc chọn Official Account đã có sẵn của doanh nghiệp.
- Vào mục Official Account Manager (OA Manager) → phần Developer hoặc Quản lý ứng dụng.
- Tạo một App ID mới liên kết với OA, khai báo tên ứng dụng, mô tả mục đích sử dụng (gửi tin nhắn chăm sóc khách hàng).
- Bật quyền (scope) oa.message để ứng dụng được phép gọi API gửi tin nhắn.
Lưu ý: Zalo yêu cầu OA phải ở trạng thái đã xác thực (verified) và có gói phù hợp mới được gửi tin nhắn chủ động ổn định. OA mới tạo có thể cần thời gian chờ Zalo duyệt.
Bước 2: Lấy Access Token Qua OAuth
Zalo OA API xác thực qua cơ chế OAuth 2.0. Sau khi có App ID và Secret Key, bạn thực hiện luồng lấy token như sau:
- Tạo link ủy quyền theo mẫu:
https://oauth.zaloapp.com/v4/oa/permission?app_id={app_id}&redirect_uri={redirect_uri}, mở link này và đăng nhập bằng tài khoản quản trị OA để cấp quyền. - Sau khi cấp quyền, Zalo redirect về
redirect_urikèm theo tham sốoa_code(hoặccode). - Dùng
oa_codeđó gọi endpointhttps://oauth.zaloapp.com/v4/oa/access_token(phương thức POST, kèmapp_id,app_secret,code,grant_type=authorization_code) để đổi lấy access_token và refresh_token. - Lưu hai giá trị này vào Script Properties của Apps Script (menu Project Settings → Script Properties) thay vì hardcode trong code — tránh lộ token khi chia sẻ file.
function luuTokenVaoScriptProperties(accessToken, refreshToken) {
const props = PropertiesService.getScriptProperties();
props.setProperty('ZALO_ACCESS_TOKEN', accessToken);
props.setProperty('ZALO_REFRESH_TOKEN', refreshToken);
}
Bước 3: Viết Hàm Apps Script Gọi API Gửi Tin Nhắn Text Đơn Giản
Sau khi có Access Token, bạn viết một hàm dùng UrlFetchApp.fetch() gọi endpoint gửi tin nhắn của Zalo OA Message API. Endpoint chính thức: https://openapi.zalo.me/v2.0/oa/message.
function guiTinNhanZalo(zaloUserId, noiDung) {
const accessToken = PropertiesService.getScriptProperties().getProperty('ZALO_ACCESS_TOKEN');
const url = 'https://openapi.zalo.me/v2.0/oa/message';
const payload = {
recipient: { user_id: zaloUserId },
message: { text: noiDung }
};
const options = {
method: 'post',
contentType: 'application/json',
headers: { access_token: accessToken },
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(url, options);
const ketQua = JSON.parse(response.getContentText());
return ketQua;
}
Trường muteHttpExceptions: true giúp script không dừng đột ngột khi API trả lỗi, cho phép bạn đọc nội dung lỗi để xử lý và ghi log ở bước sau.
Bước 4: Đọc Danh Sách Người Nhận Từ Một Sheet
Tạo một sheet riêng tên "DanhSachGui" với cấu trúc 3 cột:
| Cột | Nội dung |
|---|---|
| A — Zalo User ID | Mã định danh người dùng Zalo (lấy từ webhook follow OA hoặc form thu thập) |
| B — Nội dung tin nhắn | Nội dung cần gửi cho từng người nhận |
| C — Trạng thái gửi | Để trống ban đầu, script sẽ tự điền "Thành công" hoặc "Thất bại" sau khi gửi |
Hàm đọc dữ liệu từ sheet này:
function docDanhSachNguoiNhan() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('DanhSachGui');
const soHang = sheet.getLastRow();
// Bỏ hàng 1 (header), lấy từ hàng 2 đến hết
const duLieu = sheet.getRange(2, 1, soHang - 1, 3).getValues();
return duLieu; // mảng [zaloUserId, noiDung, trangThai]
}
Bước 5: Vòng Lặp Gửi Hàng Loạt Kèm Xử Lý Lỗi Và Rate Limit
Đây là phần quan trọng nhất — gửi tin cho nhiều người liên tiếp mà không bị Zalo chặn vì gửi quá nhanh (rate limit). Nguyên tắc: luôn thêm độ trễ giữa các lần gọi API và bọc try-catch để một lỗi không làm dừng cả vòng lặp.
function guiTinHangLoat() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('DanhSachGui');
const soHang = sheet.getLastRow();
const duLieu = sheet.getRange(2, 1, soHang - 1, 3).getValues();
for (let i = 0; i < duLieu.length; i++) {
const hangHienTai = i + 2; // vì hàng 1 là header
const zaloUserId = duLieu[i][0];
const noiDung = duLieu[i][1];
const trangThaiCu = duLieu[i][2];
// Bỏ qua nếu đã gửi thành công trước đó
if (trangThaiCu === 'Thành công') continue;
if (!zaloUserId || !noiDung) continue;
try {
const ketQua = guiTinNhanZalo(zaloUserId, noiDung);
if (ketQua.error === 0) {
ghiKetQuaVaoSheet(hangHienTai, 'Thành công', '');
} else {
// Zalo trả về mã lỗi khác 0 nghĩa là gửi thất bại (vd: chưa follow OA, rate limit...)
ghiKetQuaVaoSheet(hangHienTai, 'Thất bại', ketQua.message || 'Lỗi không xác định');
}
} catch (loi) {
ghiKetQuaVaoSheet(hangHienTai, 'Thất bại', loi.toString());
}
// Độ trễ giữa các lần gửi để tránh rate limit
Utilities.sleep(400);
}
}
Nếu gặp lỗi liên quan rate limit (Zalo trả về mã lỗi báo quá nhiều request), nên tăng thời gian Utilities.sleep() lên 1000-2000ms và cân nhắc chia nhỏ danh sách gửi theo từng đợt (batch), thay vì gửi một lần cho toàn bộ hàng nghìn dòng.
Bước 6: Ghi Log Kết Quả Gửi Ngược Lại Vào Sheets
Hàm ghiKetQuaVaoSheet cập nhật cột "Trạng thái gửi" (cột C) và có thể thêm cột D lưu chi tiết lỗi để dễ tra cứu sau này:
function ghiKetQuaVaoSheet(hang, trangThai, chiTietLoi) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('DanhSachGui');
sheet.getRange(hang, 3).setValue(trangThai);
sheet.getRange(hang, 4).setValue(chiTietLoi);
sheet.getRange(hang, 5).setValue(new Date()); // thời điểm gửi, để đối chiếu
}
Với cấu trúc log này, sau mỗi lần chạy guiTinHangLoat(), bạn nhìn ngay vào Sheets biết được ai đã nhận tin thành công, ai thất bại và lý do — không cần tra log console của Apps Script.
Gợi Ý Chạy Tự Động Theo Lịch (Trigger)
Thay vì bấm chạy thủ công, bạn có thể gắn Time-driven Trigger trong Apps Script (menu Triggers) để hàm guiTinHangLoat() tự chạy mỗi ngày vào giờ cố định — phù hợp gửi nhắc lịch hẹn, nhắc thanh toán, hoặc chăm sóc khách hàng định kỳ.
Câu Hỏi Thường Gặp
Zalo OA giới hạn bao nhiêu tin nhắn gửi mỗi ngày?
Rate limit phụ thuộc vào loại tài khoản OA (Basic hay Premium). Luôn thêm độ trễ giữa các request và kiểm tra chính sách quota mới nhất trên Zalo for Business.
Có bắt buộc người nhận phải follow Zalo OA trước khi nhận tin không?
Có. API chỉ cho phép gửi tin chủ động tới User ID đã từng tương tác hoặc quan tâm OA của bạn.
Access Token của Zalo OA có hết hạn không?
Có, Access Token thường hết hạn sau vài giờ. Cần dùng Refresh Token để lấy token mới thay vì đăng nhập lại từ đầu.
Nếu gặp lỗi rate limit hàng loạt thì xử lý thế nào?
Bọc try-catch quanh lệnh gọi API, thêm Utilities.sleep() giữa mỗi lần gửi, và tăng độ trễ hoặc chia batch nhỏ hơn nếu vẫn gặp lỗi.
📚 Đọc Thêm Trong Chuỗi Hướng Dẫn Zalo OA
Không muốn tự viết và bảo trì code Apps Script?
Các phần mềm trên Google Sheets của SheetStore đã có sẵn tính năng gửi tin nhắn Zalo OA tích hợp — không cần tự cấu hình API, token hay xử lý lỗi thủ công.
Xem phần mềm tích hợp Zalo OA sẵn →Chia sẻ bài viết:
Tuân Hoang
Đội ngũ SheetStore
Google Workspace Certified, 5+ years experience
Bạn thấy bài viết hữu ích?
Đăng ký nhận thông báo khi có bài viết mới.

