Google Apps Script

Tự Động Gửi Email Khi Có Row Mới Trong Google Sheets Bằng Apps Script

Tuân HoangTuân Hoang
14 tháng 5, 2026
5 phút đọc
Ảnh minh họa bài viết: Tự Động Gửi Email Khi Có Row Mới Trong Google Sheets Bằng Apps Script

Giới Thiệu

Mỗi khi nhân viên điền form đặt hàng, khách hàng gửi yêu cầu hỗ trợ, hoặc nhóm bán hàng cập nhật deal mới... bạn muốn nhận email ngay lập tức. Google Apps Script cho phép làm điều này hoàn toàn miễn phí, chạy trực tiếp trên Google infrastructure — không cần server, không cần Zapier trả phí.

Bài này hướng dẫn 3 cách gửi email tự động từ Google Sheets: khi có form submit, khi có row mới, và khi có ô thay đổi giá trị.

Cách 1: Gửi Email Khi Google Form Có Submission Mới

Đây là cách phổ biến nhất — mỗi khi khách hàng điền form, bạn nhận email ngay lập tức.

Bước 1: Mở Apps Script Editor

Trong Google Sheets, vào Extensions → Apps Script. Xóa code mặc định và paste code sau:

function onFormSubmit(e) {
  try {
    const sheet = e.range.getSheet();
    const row = e.range.getRow();
    const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
    const values = e.values; // Mảng giá trị từ form

    // Tạo nội dung email từ dữ liệu form
    let emailBody = '<h2>Có đơn hàng mới!</h2>';
    emailBody += '<table border="1" cellpadding="8" style="border-collapse:collapse">';

    for (let i = 0; i < headers.length; i++) {
      emailBody += '<tr>';
      emailBody += '<td><strong>' + headers[i] + '</strong></td>';
      emailBody += '<td>' + (values[i] || '') + '</td>';
      emailBody += '</tr>';
    }
    emailBody += '</table>';
    emailBody += '<p>Xem chi tiết: <a href="' + SpreadsheetApp.getActiveSpreadsheet().getUrl() + '">Mở Google Sheets</a></p>';

    // Gửi email
    GmailApp.sendEmail(
      'email-cua-ban@gmail.com', // Đổi thành email nhận thông báo
      'Đơn hàng mới #' + row,
      '', // plain text (để trống vì dùng htmlBody)
      {
        htmlBody: emailBody,
        name: 'SheetStore Thông Báo'
      }
    );

    console.log('Email sent for row: ' + row);
  } catch (error) {
    console.error('Error sending email: ' + error.toString());
    // Không throw để tránh block form submission
  }
}

Bước 2: Cài Installable Trigger

Trigger đơn giản onFormSubmit trong code không đủ quyền gửi email. Phải dùng installable trigger:

  1. Trong Apps Script Editor, click Triggers (icon đồng hồ bên trái)
  2. Click + Add Trigger
  3. Function: onFormSubmit
  4. Event source: From spreadsheet
  5. Event type: On form submit
  6. Save → Authorize permissions

Từ giờ, mỗi khi form được submit, hàm onFormSubmit sẽ tự chạy và gửi email cho bạn.

Cách 2: Gửi Email Khi Có Row Mới (Không Dùng Form)

Khi nhân viên tự thêm row trực tiếp vào sheet (không qua form), dùng trigger onChange:

function onSheetChange(e) {
  try {
    // Chỉ xử lý khi có row mới được thêm
    if (e.changeType !== 'INSERT_ROW') return;

    const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Đơn Hàng');
    if (!sheet) return;

    const lastRow = sheet.getLastRow();
    if (lastRow <= 1) return; // Chỉ có header, bỏ qua

    // Lấy dữ liệu row vừa thêm
    const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
    const newRowData = sheet.getRange(lastRow, 1, 1, sheet.getLastColumn()).getValues()[0];

    let emailBody = '<h2>Row mới được thêm vào sheet "Đơn Hàng"</h2>';
    emailBody += '<table border="1" cellpadding="8" style="border-collapse:collapse">';
    headers.forEach((header, i) => {
      if (newRowData[i]) {
        emailBody += '<tr><td><strong>' + header + '</strong></td><td>' + newRowData[i] + '</td></tr>';
      }
    });
    emailBody += '</table>';

    MailApp.sendEmail({
      to: 'quan-ly@cong-ty.com',
      subject: 'Row mới tại hàng ' + lastRow + ' — ' + new Date().toLocaleDateString('vi-VN'),
      htmlBody: emailBody
    });

  } catch (error) {
    console.error('onChange error: ' + error.toString());
  }
}

Cài trigger: Event type chọn On change, gắn với hàm onSheetChange.

Cách 3: Báo Cáo Tổng Hợp Theo Lịch (Daily/Weekly)

Thay vì nhận email mỗi row, gửi tổng hợp hàng ngày lúc 8 giờ sáng:

function sendDailyReport() {
  try {
    const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Đơn Hàng');
    const today = new Date();
    today.setHours(0, 0, 0, 0);

    const allData = sheet.getDataRange().getValues();
    const headers = allData[0];

    // Tìm cột ngày (giả sử cột đầu tiên là ngày)
    const todayRows = allData.slice(1).filter(row => {
      const rowDate = new Date(row[0]);
      rowDate.setHours(0, 0, 0, 0);
      return rowDate.getTime() === today.getTime();
    });

    if (todayRows.length === 0) {
      console.log('No new orders today, skipping email.');
      return;
    }

    let emailBody = '<h2>Báo Cáo Đơn Hàng Ngày ' + today.toLocaleDateString('vi-VN') + '</h2>';
    emailBody += '<p>Tổng số đơn hôm nay: <strong>' + todayRows.length + '</strong></p>';
    emailBody += '<table border="1" cellpadding="6" style="border-collapse:collapse;width:100%">';
    emailBody += '<tr>' + headers.map(h => '<th style="background:#f0f0f0">' + h + '</th>').join('') + '</tr>';
    todayRows.forEach(row => {
      emailBody += '<tr>' + row.map(cell => '<td>' + cell + '</td>').join('') + '</tr>';
    });
    emailBody += '</table>';

    GmailApp.sendEmail('bao-cao@cong-ty.com', 'Báo cáo đơn hàng ngày ' + today.toLocaleDateString('vi-VN'), '', { htmlBody: emailBody });
    console.log('Daily report sent: ' + todayRows.length + ' orders');

  } catch (error) {
    console.error('Daily report error: ' + error.toString());
  }
}

Cài time-based trigger: Event source: Time-driven, Type: Day timer, Time: 8am–9am.

GmailApp vs MailApp: Dùng Cái Nào?

Tiêu chíGmailAppMailApp
Quota miễn phí100 email/ngày100 email/ngày
Google Workspace1.500 email/ngày1.500 email/ngày
Reply-To tùy chỉnh
Đính kèm fileCó (từ Drive)Có (blob)
Nên dùng khiCần full Gmail APIGửi đơn giản

Xử Lý Lỗi Và Best Practices

  • Luôn bọc trong try/catch: Nếu hàm throw error, trigger sẽ bị vô hiệu hoá sau nhiều lần thất bại.
  • Dùng PropertiesService để lưu config: Email nhận thông báo nên lưu trong Script Properties, không hardcode.
  • Kiểm tra quota: Apps Script giới hạn 6 phút execution/trigger và 100 email/ngày (tài khoản cá nhân).
  • Log để debug: Dùng console.log() và xem trong Executions tab của Apps Script.
// Lấy email từ Script Properties thay vì hardcode
function getRecipientEmail() {
  const props = PropertiesService.getScriptProperties();
  return props.getProperty('NOTIFY_EMAIL') || Session.getActiveUser().getEmail();
}

Use Case Thực Tế

  • Form đặt hàng: Mỗi đơn mới → email cho kho và sales ngay lập tức
  • Lead form: Khách tiềm năng đăng ký → email cho sales kèm thông tin đầy đủ
  • Báo cáo hàng ngày: 8h sáng nhận tổng kết doanh thu, tồn kho, đơn chờ xử lý
  • Cảnh báo tồn kho thấp: Khi ô tồn kho < mức tối thiểu → email cho bộ phận mua hàng

Muốn đi xa hơn? Kết hợp với Apps Script API endpoint để nhận dữ liệu từ bên ngoài vào Sheet rồi gửi email thông báo tự động.

Chia sẻ bài viết:

Tuân Hoang

Tuân Hoang

Đội ngũ SheetStore

Google SheetsGoogle Apps ScriptCRMAutomationPhần mềm quản lý doanh nghiệp

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.

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