Hướng dẫn

Google Sheets + Telegram: Cách Gửi Thông Báo Tự Động Cho Doanh Nghiệp 2026

Tuân HoangTuân Hoang
27 tháng 2, 2026
Cập nhật: 25 tháng 3, 2026
14 phút đọc
Google Sheets + Telegram: Cách Gửi Thông Báo Tự Động Cho Doanh Nghiệp 2026

Bạn đang mở Google Sheets thủ công mỗi giờ để kiểm tra đơn hàng mới, tồn kho thấp, hay deadline sắp hết?

Hướng dẫn kết nối Google Sheets với Telegram để nhận thông báo tự động: tạo bot, lấy token, viết Apps Script — kèm code đầy đủ cho 6 use case kinh doanh thực tế năm 2026.

1. Tại Sao Chọn Telegram Thay Vì Email Hay Zalo?

Khi cần nhận thông báo tự động từ hệ thống, có nhiều lựa chọn: Email, SMS, Zalo, Slack, Telegram. Vậy tại sao Telegram lại là lựa chọn hàng đầu cho developer và doanh nghiệp Việt Nam năm 2026?

Kênh API miễn phí Tốc độ nhận Giới hạn gửi Định dạng rich text
Telegram Bot Miễn phí hoàn toàn Tức thì 30 msg/giây HTML + Markdown
Email (Gmail) Miễn phí 1–5 phút 100/ngày (Apps Script) HTML đầy đủ
Zalo OA Có phí template Tức thì Giới hạn theo gói Hạn chế
SMS Trả phí (800đ/tin) Tức thì Không giới hạn Text thuần
Slack Miễn phí (giới hạn) Tức thì Cao Block Kit

Telegram Bot API nổi bật vì: hoàn toàn miễn phí, không giới hạn thực tế, API cực đơn giản (chỉ cần HTTP GET/POST), hỗ trợ HTML formatting, và Telegram rất phổ biến tại Việt Nam (đặc biệt trong cộng đồng doanh nhân và tech).

2. Bước 1: Tạo Telegram Bot Với BotFather

BotFather là bot chính thức của Telegram để tạo và quản lý bot. Thực hiện theo từng bước:

  1. Mở Telegram và tìm kiếm @BotFather (có dấu tick xanh xác thực)
  2. Gửi lệnh: /start để bắt đầu
  3. Gửi lệnh: /newbot để tạo bot mới
  4. Đặt tên cho bot: BotFather hỏi "What name would you like to give to your bot?" — nhập tên hiển thị, ví dụ: SheetStore Alerts Bot
  5. Đặt username cho bot: Phải kết thúc bằng "bot", ví dụ: sheetstore_alerts_bot. Username phải duy nhất toàn Telegram.
  6. Nhận Bot Token: BotFather gửi lại token dạng: 7234567890:AAH1234abcd5678efghijklmnopqrstuvwxy

Bảo mật Bot Token:

  • Không bao giờ chia sẻ Bot Token công khai (GitHub, Sheets, email...)
  • Lưu vào Script Properties của Apps Script (không hardcode trong code)
  • Nếu lộ token: /revoke trong BotFather để thu hồi ngay

Các Lệnh BotFather Hữu Ích

Lệnh Chức năng
/mybots Xem danh sách bot của bạn
/setdescription Đặt mô tả bot (hiện khi user nhấn Start)
/setuserpic Đặt ảnh đại diện cho bot
/revoke Thu hồi token cũ và tạo token mới
/deletebot Xóa bot vĩnh viễn

3. Bước 2: Lấy Chat ID

Để gửi tin nhắn, bạn cần biết Chat ID — định danh duy nhất của cuộc trò chuyện (với cá nhân hoặc nhóm) mà bot sẽ gửi tin vào.

Lấy Chat ID Cá Nhân

  1. Mở Telegram, tìm bot vừa tạo theo username
  2. Nhấn Start để bắt đầu trò chuyện với bot
  3. Mở trình duyệt, truy cập URL sau (thay YOUR_BOT_TOKEN):
    https://api.telegram.org/botYOUR_BOT_TOKEN/getUpdates
  4. Trong response JSON, tìm "chat":{"id": 123456789} — đây là Chat ID của bạn
// Ví dụ response từ getUpdates
{
  "ok": true,
  "result": [{
    "update_id": 123456,
    "message": {
      "from": { "id": 987654321, "first_name": "Nguyen" },
      "chat": {
        "id": 987654321,   // ← ĐÂY LÀ CHAT ID CỦA BẠN
        "type": "private"
      },
      "text": "/start"
    }
  }]
}

Lấy Chat ID Nhóm

  1. Thêm bot vào nhóm Telegram (nhấn tên nhóm → Add members → tìm username bot)
  2. Gửi một tin nhắn bất kỳ trong nhóm
  3. Truy cập URL getUpdates như trên
  4. Chat ID nhóm sẽ là số âm, ví dụ: -1001234567890

4. Bước 3: Gửi Tin Nhắn Đầu Tiên Từ Apps Script

Mở Google Sheets → Extensions → Apps Script. Dán code sau:

// ============================================================
// CONFIG — lưu vào Script Properties (File → Project settings)
// ============================================================
function getConfig() {
  const props = PropertiesService.getScriptProperties()
  return {
    botToken: props.getProperty('TELEGRAM_BOT_TOKEN'),
    chatId: props.getProperty('TELEGRAM_CHAT_ID'),
  }
}

// Lưu config lần đầu (chạy 1 lần duy nhất)
function setupConfig() {
  PropertiesService.getScriptProperties().setProperties({
    'TELEGRAM_BOT_TOKEN': '7234567890:AAH1234abcd...', // Thay bằng token thật
    'TELEGRAM_CHAT_ID': '987654321',                   // Thay bằng chat ID thật
  })
  console.log('Config đã lưu!')
}

// ============================================================
// HÀM GỬI TIN NHẮN CHÍNH
// ============================================================
function sendTelegramMessage(text, parseMode = 'HTML') {
  const { botToken, chatId } = getConfig()

  if (!botToken || !chatId) {
    console.error('Chưa cấu hình Bot Token hoặc Chat ID!')
    return null
  }

  const url = `https://api.telegram.org/bot${botToken}/sendMessage`

  const payload = {
    chat_id: chatId,
    text: text,
    parse_mode: parseMode,
    disable_web_page_preview: true,
  }

  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload),
    muteHttpExceptions: true,
  }

  try {
    const response = UrlFetchApp.fetch(url, options)
    const result = JSON.parse(response.getContentText())

    if (!result.ok) {
      console.error('Telegram API error:', result.description)
    }
    return result
  } catch (e) {
    console.error('Lỗi gửi Telegram:', e.message)
    return null
  }
}

// ============================================================
// TEST: Gửi tin nhắn thử nghiệm
// ============================================================
function testSendMessage() {
  const message = `
🤖 Bot đã kết nối thành công!

📅 Thời gian: ${new Date().toLocaleString('vi-VN')}
📊 Sheet: ${SpreadsheetApp.getActiveSpreadsheet().getName()}

Bot sẵn sàng gửi thông báo tự động.
`
  const result = sendTelegramMessage(message)
  if (result && result.ok) {
    SpreadsheetApp.getUi().alert('Gửi thành công! Kiểm tra Telegram của bạn.')
  }
}

Sau khi dán code:

  1. Chạy hàm setupConfig() để lưu token và chat ID (chỉ cần chạy 1 lần)
  2. Chạy hàm testSendMessage() để kiểm tra kết nối
  3. Kiểm tra Telegram — bạn sẽ nhận được tin nhắn thử nghiệm

5. Bước 4: Trigger Thông Báo Khi Ô Thay Đổi

Google Apps Script có event trigger onEdit — tự động chạy khi bất kỳ ô nào trong Sheet được chỉnh sửa:

// Trigger khi ô trong Sheet thay đổi
function onEdit(e) {
  const sheet = e.source.getActiveSheet()
  const range = e.range
  const col = range.getColumn()
  const row = range.getRow()
  const newValue = e.value
  const oldValue = e.oldValue

  // Chỉ xử lý Sheet "Orders" và cột D (trạng thái)
  if (sheet.getName() !== 'Orders' || col !== 4) return

  // Bỏ qua nếu không thay đổi hoặc là hàng header
  if (row === 1 || newValue === oldValue) return

  // Lấy thông tin đơn hàng từ cùng hàng đó
  const orderId = sheet.getRange(row, 1).getValue()   // Cột A: Mã đơn
  const customer = sheet.getRange(row, 2).getValue()  // Cột B: Khách hàng
  const amount = sheet.getRange(row, 3).getValue()    // Cột C: Giá trị

  // Chỉ gửi thông báo khi trạng thái chuyển sang PAID
  if (newValue === 'PAID') {
    const message = `
💰 Đơn hàng đã thanh toán!

🔖 Mã đơn: ${orderId}
👤 Khách: ${customer}
💵 Giá trị: ${formatCurrency(amount)}
📅 Thời gian: ${new Date().toLocaleString('vi-VN')}

✅ Trạng thái: PAID
`
    sendTelegramMessage(message)
  }

  // Gửi thông báo khi đơn bị hủy
  if (newValue === 'CANCELLED') {
    const message = `
❌ Đơn hàng bị hủy

🔖 Mã đơn: ${orderId}
👤 Khách: ${customer}
💵 Giá trị: ${formatCurrency(amount)}
⏰ Hủy lúc: ${new Date().toLocaleString('vi-VN')}
`
    sendTelegramMessage(message)
  }
}

function formatCurrency(amount) {
  return new Intl.NumberFormat('vi-VN', {
    style: 'currency', currency: 'VND'
  }).format(amount)
}

Lưu ý quan trọng về onEdit:

  • onEdit đơn giản không có quyền gọi external services (UrlFetchApp). Bạn phải dùng Installable Trigger.
  • Cách tạo Installable Trigger: Extensions → Apps Script → Triggers (đồng hồ ⏰) → Add Trigger → chọn function: onEdit, event: "From spreadsheet" → "On edit"
  • Installable Trigger chạy với quyền của bạn — có thể gọi UrlFetchApp.

6. Bước 5: Báo Cáo Tự Động Định Kỳ

Bên cạnh trigger theo sự kiện, bạn có thể tạo time-based trigger để gửi báo cáo vào giờ cố định:

// Tạo time-based trigger tự động
function createDailyReportTrigger() {
  // Xóa trigger cũ nếu có
  const triggers = ScriptApp.getProjectTriggers()
  triggers.forEach(t => {
    if (t.getHandlerFunction() === 'sendDailyReport') {
      ScriptApp.deleteTrigger(t)
    }
  })

  // Tạo trigger mới: chạy mỗi ngày lúc 8:00 sáng
  ScriptApp.newTrigger('sendDailyReport')
    .timeBased()
    .everyDays(1)
    .atHour(8)
    .create()

  console.log('Đã tạo trigger báo cáo hàng ngày lúc 8:00')
}

// Chạy hàm này để đăng ký trigger (1 lần duy nhất)
function setup() {
  createDailyReportTrigger()
}

7. Use Case 1: Cảnh Báo Tồn Kho Thấp

Tự động gửi cảnh báo khi tồn kho sản phẩm xuống dưới ngưỡng tối thiểu — không cần kiểm tra thủ công.

// Kiểm tra tồn kho và gửi cảnh báo
// Trigger: mỗi giờ hoặc mỗi khi Sheet "Inventory" thay đổi
function checkInventoryAlert() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName('Inventory')

  if (!sheet) return

  const data = sheet.getDataRange().getValues()
  const headers = data[0] // Hàng đầu là tiêu đề
  const rows = data.slice(1) // Từ hàng 2 trở đi

  // Giả sử: Cột A=Mã SP, B=Tên SP, C=Tồn kho, D=Tồn kho tối thiểu
  const criticalItems = []  // Hết hàng (< 5)
  const lowItems = []       // Sắp hết (< tồn kho tối thiểu)

  rows.forEach((row, i) => {
    const [code, name, qty, minQty] = row
    if (!name || qty === '') return

    if (qty < 5) {
      criticalItems.push({ code, name, qty, minQty })
    } else if (qty < minQty) {
      lowItems.push({ code, name, qty, minQty })
    }
  })

  // Không có cảnh báo → không gửi
  if (criticalItems.length === 0 && lowItems.length === 0) return

  let message = '📦 CẢNH BÁO TỒN KHO
'
  message += `📅 ${new Date().toLocaleString('vi-VN')}

`

  if (criticalItems.length > 0) {
    message += '🔴 SẮP HẾT HÀNG (cần đặt gấp):
'
    criticalItems.forEach(item => {
      message += `  • ${item.name} (${item.code}): còn ${item.qty} cái
`
    })
    message += '
'
  }

  if (lowItems.length > 0) {
    message += '🟡 TỒN KHO THẤP (nên đặt thêm):
'
    lowItems.forEach(item => {
      message += `  • ${item.name}: còn ${item.qty}/${item.minQty} (min)
`
    })
  }

  message += `
👉 Xem bảng tồn kho`

  sendTelegramMessage(message)
}

8. Use Case 2: Thông Báo Đơn Hàng Mới

Khi nhân viên nhập đơn hàng mới vào Sheets, tự động gửi thông báo cho quản lý qua Telegram:

// Trigger: onEdit trên Sheet "Orders"
// Phát hiện hàng mới được thêm vào (cột A vừa có giá trị mới)
function onOrderAdded(e) {
  const sheet = e.source.getActiveSheet()
  if (sheet.getName() !== 'Orders') return

  const range = e.range
  const row = range.getRow()
  const col = range.getColumn()

  // Chỉ xử lý khi điền vào cột A (Mã đơn) ở hàng mới
  if (col !== 1 || row === 1) return
  if (!e.value) return // Bỏ qua nếu xóa

  // Đợi một chút để người dùng điền xong các cột khác
  // (Dùng trigger onChange thay vì onEdit sẽ tốt hơn)
  Utilities.sleep(1000)

  const orderId = sheet.getRange(row, 1).getValue()
  const customer = sheet.getRange(row, 2).getValue()
  const phone = sheet.getRange(row, 3).getValue()
  const products = sheet.getRange(row, 4).getValue()
  const amount = sheet.getRange(row, 5).getValue()
  const address = sheet.getRange(row, 6).getValue()
  const note = sheet.getRange(row, 7).getValue()

  const message = `
🛒 ĐƠN HÀNG MỚI!

🔖 Mã đơn: ${orderId}
👤 Khách: ${customer}
📞 SĐT: ${phone}
🛍️ Sản phẩm: ${products}
💰 Giá trị: ${formatCurrency(amount)}
📍 Địa chỉ: ${address}
📝 Ghi chú: ${note || 'Không có'}
⏰ Thời gian: ${new Date().toLocaleString('vi-VN')}
`
  sendTelegramMessage(message)
}

9. Use Case 3: Báo Cáo Doanh Thu Hàng Ngày

Tự động tổng hợp và gửi báo cáo doanh thu mỗi ngày lúc 8 giờ sáng:

function sendDailyReport() {
  const ss = SpreadsheetApp.getActiveSpreadsheet()
  const sheet = ss.getSheetByName('Orders')
  if (!sheet) return

  const today = new Date()
  const yesterday = new Date(today)
  yesterday.setDate(yesterday.getDate() - 1)

  const data = sheet.getDataRange().getValues()
  const rows = data.slice(1)

  // Lọc đơn hàng của hôm qua
  const yesterdayOrders = rows.filter(row => {
    const orderDate = new Date(row[0]) // Giả sử cột A là ngày đặt hàng
    return orderDate.toDateString() === yesterday.toDateString()
      && row[4] === 'PAID' // Cột E: trạng thái
  })

  // Tính toán
  const totalRevenue = yesterdayOrders.reduce((sum, row) => sum + (row[3] || 0), 0)
  const orderCount = yesterdayOrders.length
  const avgOrderValue = orderCount > 0 ? totalRevenue / orderCount : 0

  // Top sản phẩm (đếm tần suất trong cột C)
  const productCount = {}
  yesterdayOrders.forEach(row => {
    const product = row[2]
    productCount[product] = (productCount[product] || 0) + 1
  })
  const topProducts = Object.entries(productCount)
    .sort((a, b) => b[1] - a[1])
    .slice(0, 3)

  const dateStr = yesterday.toLocaleDateString('vi-VN', {
    weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'
  })

  let message = `
📊 BÁO CÁO DOANH THU
📅 ${dateStr}

━━━━━━━━━━━━━━━━━━━━
💰 Doanh thu: ${formatCurrency(totalRevenue)}
📦 Số đơn: ${orderCount} đơn
📈 Giá trị TB: ${formatCurrency(avgOrderValue)}

🏆 Top sản phẩm:
`

  topProducts.forEach(([product, count], i) => {
    const medal = ['🥇', '🥈', '🥉'][i]
    message += `  ${medal} ${product}: ${count} đơn
`
  })

  // So sánh với hôm kia
  const dayBefore = new Date(yesterday)
  dayBefore.setDate(dayBefore.getDate() - 1)
  const dayBeforeOrders = rows.filter(row => {
    const orderDate = new Date(row[0])
    return orderDate.toDateString() === dayBefore.toDateString() && row[4] === 'PAID'
  })
  const dayBeforeRevenue = dayBeforeOrders.reduce((sum, row) => sum + (row[3] || 0), 0)

  if (dayBeforeRevenue > 0) {
    const growth = ((totalRevenue - dayBeforeRevenue) / dayBeforeRevenue * 100).toFixed(1)
    const arrow = growth >= 0 ? '📈' : '📉'
    message += `
${arrow} So với hôm kia: ${growth > 0 ? '+' : ''}${growth}%`
  }

  message += `

👉 Xem chi tiết`

  sendTelegramMessage(message)
}

10. Use Case 4: Nhắc Nhở Deadline

Mỗi sáng lúc 7:30, kiểm tra danh sách task và gửi nhắc nhở cho task sắp đến hạn:

function sendDeadlineReminder() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName('Tasks')
  if (!sheet) return

  const data = sheet.getDataRange().getValues()
  const rows = data.slice(1)
  const today = new Date()
  today.setHours(0, 0, 0, 0)

  const overdueTask = []
  const todayTasks = []
  const upcomingTasks = []

  rows.forEach(row => {
    const [taskId, taskName, assignee, deadline, status] = row
    if (!taskName || status === 'DONE' || !deadline) return

    const deadlineDate = new Date(deadline)
    deadlineDate.setHours(0, 0, 0, 0)

    const diffDays = Math.floor((deadlineDate - today) / (1000 * 60 * 60 * 24))

    if (diffDays < 0) {
      overdueTask.push({ taskName, assignee, deadline: deadlineDate, diffDays })
    } else if (diffDays === 0) {
      todayTasks.push({ taskName, assignee })
    } else if (diffDays <= 3) {
      upcomingTasks.push({ taskName, assignee, diffDays })
    }
  })

  if (overdueTask.length === 0 && todayTasks.length === 0 && upcomingTasks.length === 0) {
    return // Không có gì cần nhắc
  }

  let message = `⏰ NHẮC NHỞ DEADLINE
📅 ${today.toLocaleDateString('vi-VN')}

`

  if (overdueTask.length > 0) {
    message += `🔴 QUÁ HẠN (${overdueTask.length} task):
`
    overdueTask.forEach(t => {
      message += `  • ${t.taskName} — ${t.assignee} (quá ${Math.abs(t.diffDays)} ngày)
`
    })
    message += '
'
  }

  if (todayTasks.length > 0) {
    message += `🟠 HÔM NAY (${todayTasks.length} task):
`
    todayTasks.forEach(t => {
      message += `  • ${t.taskName} — ${t.assignee}
`
    })
    message += '
'
  }

  if (upcomingTasks.length > 0) {
    message += `🟡 SẮP ĐẾN HẠN:
`
    upcomingTasks.forEach(t => {
      message += `  • ${t.taskName} — còn ${t.diffDays} ngày
`
    })
  }

  sendTelegramMessage(message)
}

11. Use Case 5: Theo Dõi Lỗi và Bất Thường

// Phát hiện giá trị bất thường trong doanh thu
function detectAnomalies() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName('Sales')
  if (!sheet) return

  const revenueCol = sheet.getRange('E2:E100').getValues().flat()
    .filter(v => v > 0)

  if (revenueCol.length < 10) return // Cần đủ data

  // Tính trung bình và độ lệch chuẩn
  const avg = revenueCol.reduce((a, b) => a + b) / revenueCol.length
  const stdDev = Math.sqrt(
    revenueCol.map(x => (x - avg) ** 2).reduce((a, b) => a + b) / revenueCol.length
  )

  // Ngưỡng bất thường: ngoài 3 sigma
  const upperBound = avg + 3 * stdDev
  const lowerBound = avg - 3 * stdDev

  const lastRow = sheet.getLastRow()
  const lastRevenue = sheet.getRange(lastRow, 5).getValue()

  if (lastRevenue > upperBound) {
    sendTelegramMessage(`
🚨 GIÁ TRỊ BẤT THƯỜNG!

Doanh thu hàng ${lastRow}: ${formatCurrency(lastRevenue)}
Mức trung bình: ${formatCurrency(avg)}
Cao hơn trung bình: ${((lastRevenue/avg - 1) * 100).toFixed(0)}%

Vui lòng kiểm tra lại đơn hàng.
`)
  }
}

12. Use Case 6: Báo Cáo Tuần Tự Động

// Gửi báo cáo tuần mỗi sáng thứ 2 lúc 9:00
function sendWeeklyReport() {
  const today = new Date()
  if (today.getDay() !== 1) return // Chỉ chạy thứ 2

  const ss = SpreadsheetApp.getActiveSpreadsheet()
  const sheet = ss.getSheetByName('Orders')
  const data = sheet.getDataRange().getValues().slice(1)

  // Tuần trước: thứ 2 đến chủ nhật
  const lastMonday = new Date(today)
  lastMonday.setDate(today.getDate() - 7)
  lastMonday.setHours(0, 0, 0, 0)

  const lastSunday = new Date(today)
  lastSunday.setDate(today.getDate() - 1)
  lastSunday.setHours(23, 59, 59, 999)

  const weekOrders = data.filter(row => {
    const d = new Date(row[0])
    return d >= lastMonday && d <= lastSunday && row[4] === 'PAID'
  })

  const totalRevenue = weekOrders.reduce((s, r) => s + (r[3] || 0), 0)
  const orderCount = weekOrders.length
  const newCustomers = new Set(weekOrders.map(r => r[1])).size

  // Doanh thu từng ngày
  const dailyRevenue = {}
  const days = ['CN', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7']
  weekOrders.forEach(row => {
    const d = new Date(row[0])
    const dayKey = days[d.getDay()]
    dailyRevenue[dayKey] = (dailyRevenue[dayKey] || 0) + (row[3] || 0)
  })

  let message = `
📊 BÁO CÁO TUẦN
📅 ${lastMonday.toLocaleDateString('vi-VN')} – ${lastSunday.toLocaleDateString('vi-VN')}

━━━━━━━━━━━━━━━━━━━━
💰 Tổng doanh thu: ${formatCurrency(totalRevenue)}
📦 Số đơn hàng: ${orderCount}
👥 Khách hàng: ${newCustomers}

📅 Theo ngày:
`

  Object.entries(dailyRevenue)
    .sort()
    .forEach(([day, rev]) => {
      const bar = '█'.repeat(Math.min(10, Math.floor(rev / (totalRevenue / orderCount) * 2)))
      message += `  ${day}: ${bar} ${formatCurrency(rev)}
`
    })

  message += `
👉 Xem bảng chi tiết`

  sendTelegramMessage(message)
}

13. Tối Ưu Định Dạng Tin Nhắn Telegram

Telegram hỗ trợ HTML parse mode với các tag sau:

Tag HTML Hiệu ứng Ví dụ
<b>text</b> In đậm Tiêu đề, số quan trọng
<i>text</i> In nghiêng Ghi chú phụ
<code>text</code> Monospace Mã đơn hàng, mã sản phẩm
<a href="url">text</a> Hyperlink Link đến Sheet
<pre>text</pre> Block code Bảng dữ liệu dạng text
<tg-spoiler>text</tg-spoiler> Spoiler (ẩn đến khi tap) Thông tin nhạy cảm

14. Gửi Vào Nhóm Telegram

Để gửi thông báo vào group thay vì chat cá nhân, chỉ cần thay Chat ID bằng Group Chat ID (số âm). Bạn cũng có thể gửi đến nhiều nhóm cùng lúc:

// Gửi đến nhiều chat khác nhau
function sendToMultipleChats(message) {
  const props = PropertiesService.getScriptProperties()
  const botToken = props.getProperty('TELEGRAM_BOT_TOKEN')

  // Danh sách nhận: managers group + owner personal
  const chatIds = [
    props.getProperty('TELEGRAM_GROUP_MANAGERS'),   // Nhóm quản lý
    props.getProperty('TELEGRAM_OWNER_ID'),          // Chủ doanh nghiệp
  ].filter(Boolean) // Bỏ qua giá trị null/empty

  chatIds.forEach(chatId => {
    const url = `https://api.telegram.org/bot${botToken}/sendMessage`
    UrlFetchApp.fetch(url, {
      method: 'post',
      contentType: 'application/json',
      payload: JSON.stringify({ chat_id: chatId, text: message, parse_mode: 'HTML' }),
      muteHttpExceptions: true,
    })
  })
}

// Gửi document/file (ví dụ báo cáo Excel)
function sendDocument(fileId, caption) {
  const { botToken, chatId } = getConfig()
  const url = `https://api.telegram.org/bot${botToken}/sendDocument`

  UrlFetchApp.fetch(url, {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify({
      chat_id: chatId,
      document: fileId, // Google Drive file ID
      caption: caption,
    }),
    muteHttpExceptions: true,
  })
}

15. Xử Lý Lỗi và Debug

// Wrapper an toàn cho sendTelegramMessage với retry
function sendTelegramMessageSafe(text, retries = 3) {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      const result = sendTelegramMessage(text)
      if (result && result.ok) return result

      // Xử lý rate limit (429 Too Many Requests)
      if (result && result.error_code === 429) {
        const retryAfter = result.parameters?.retry_after || 5
        console.log(`Rate limited. Chờ ${retryAfter}s...`)
        Utilities.sleep(retryAfter * 1000)
        continue
      }

      console.error(`Lần ${attempt}: Telegram lỗi:`, result?.description)
    } catch (e) {
      console.error(`Lần ${attempt}: Exception:`, e.message)
      if (attempt < retries) Utilities.sleep(2000)
    }
  }
  console.error('Gửi Telegram thất bại sau', retries, 'lần thử')
  return null
}

// Log lỗi vào Sheet để theo dõi
function logError(functionName, errorMessage) {
  try {
    const logSheet = SpreadsheetApp.getActiveSpreadsheet()
      .getSheetByName('Error Log')
    if (!logSheet) return

    logSheet.appendRow([
      new Date(),
      functionName,
      errorMessage,
      Session.getActiveUser().getEmail()
    ])
  } catch (e) {
    console.error('Không thể ghi log:', e.message)
  }
}

16. SheetStore + Telegram: Tích Hợp Sẵn

SheetStore tích hợp sẵn Telegram Bot trong tất cả gói — không cần viết code từ đầu. Chỉ cần:

  1. Vào Admin Panel → Cài đặt → Telegram
  2. Nhập Bot Token và Chat ID
  3. Chọn các sự kiện muốn nhận thông báo
  4. Bật toggle và lưu

SheetStore hỗ trợ 8 loại thông báo Telegram:

  • Đơn hàng mới được tạo
  • Đơn hàng thay đổi trạng thái (thanh toán, hủy, hoàn trả)
  • Tồn kho xuống dưới ngưỡng
  • Báo cáo doanh thu ngày (8h sáng)
  • Báo cáo tuần (sáng thứ 2)
  • Báo cáo tháng (ngày 1 hàng tháng)
  • Cảnh báo lỗi hệ thống
  • Nhắc nhở deadline công việc

FAQ — Câu Hỏi Thường Gặp

Bot có thể nhận lệnh từ Telegram để truy vấn dữ liệu Sheets không?

Được, nhưng phức tạp hơn: cần dùng Webhook (setWebhook) để Telegram gửi tin nhắn của user đến Apps Script Web App, sau đó Apps Script xử lý và trả lời. Đây là hai chiều (bidirectional bot). Ví dụ: user gửi "doanh thu hôm nay" → bot đọc Sheets và trả về số liệu.

Apps Script có bị giới hạn số lần gọi Telegram không?

Apps Script giới hạn UrlFetchApp: 20,000 lần/ngày (tài khoản Google thường) hoặc 100,000 lần/ngày (Google Workspace). Telegram Bot API giới hạn 30 tin nhắn/giây cho cùng một chat. Trong thực tế, các use case thông báo không bao giờ chạm giới hạn này.

Trigger onEdit có hoạt động khi Sheets được cập nhật bởi script khác không?

Không. onEdit chỉ kích hoạt khi người dùng trực tiếp chỉnh sửa. Nếu muốn theo dõi thay đổi từ script/API, dùng trigger onChange hoặc tạo custom trigger trong script tự gọi sau khi ghi dữ liệu.

Làm sao để không gửi thông báo trùng lặp?

Lưu ID của giao dịch/đơn hàng đã gửi vào một Sheet riêng hoặc Script Properties. Trước khi gửi, kiểm tra ID đó đã tồn tại chưa. Đây là pattern idempotency cơ bản cho notification system.

SheetStore có cần tôi tự cấu hình Apps Script không?

Không. SheetStore quản lý toàn bộ Apps Script backend. Bạn chỉ cần cung cấp Bot Token và Chat ID trong Admin Panel — tất cả code được cài sẵn. Xem demo để biết thêm.

Nhận Thông Báo Kinh Doanh Tự Động Với SheetStore

SheetStore tích hợp sẵn Telegram Bot — đơn hàng, tồn kho, doanh thu gửi thẳng về điện thoại của bạn, không cần mở Sheets.

Chia sẻ bài viết:

Tuân Hoang

Tuân Hoang

Đội ngũ SheetStore

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.

Nhận thông báo khi có bài viết mới. Không spam, hứa luôn! 😊

Bình luận (0)

Vui lòng đăng nhập để tham gia thảo luận