Skip to content
// Sheets · Gmail · Apps Script

Send an email when a cell value changes in Google Sheets.

A step-by-step guide to firing a Gmail notification when a specific cell in Google Sheets changes, using an installable Apps Script trigger instead of the broken simple onEdit approach.

I want to get an email automatically when a specific cell in my spreadsheet changes, without having to check it manually.

The script

copy · paste · trigger
notify-on-change.gs
Apps Script
// Installable edit trigger — paste in Apps Script editor, then
// create the trigger via Triggers > Add Trigger (not via plain onEdit)
function notifyOnCellChange(e) {
  var watchSheet = 'Orders';
  var watchRow = 2;
  var watchCol = 5; // column E
  var notifyEmail = '[email protected]';

  var range = e.range;
  if (range.getSheet().getName() !== watchSheet) return;
  if (range.getRow() !== watchRow || range.getColumn() !== watchCol) return;

  var newValue = range.getValue();
  var subject = 'Cell E2 changed on Orders sheet';
  var body = 'New value: ' + newValue + '\nSpreadsheet: ' + e.source.getUrl();

  MailApp.sendEmail(notifyEmail, subject, body);
}

Need a variant? Gnaw writes a custom version from one sentence — fields, triggers, edge cases handled.

Walkthrough

Why plain onEdit silently fails

The first time I tried this I pasted a function named onEdit, added a MailApp.sendEmail call inside it, edited the cell, and heard nothing. No error. No email. The function ran fine — it just skipped the email without complaint.

Google Apps Script has two tiers of triggers. Simple triggers (named onEdit, onOpen, onChange) run without asking for permissions and are deliberately sandboxed: they cannot call services that act on your behalf, including MailApp and GmailApp. The quota-guarded services require OAuth consent, and simple triggers never go through that flow.

Installable triggers work differently. You register them through the Triggers UI (or programmatically via ScriptApp.newTrigger), and the first time you save one, Apps Script prompts for authorization. After that, the function runs with your credentials and MailApp works. The function name does not have to be onEdit — in fact naming it something else makes the distinction obvious.

Setting up the installable trigger

Open your spreadsheet, go to Extensions > Apps Script, and paste the notifyOnCellChange function above. Change watchSheet to your sheet tab name, watchRow and watchCol to the cell you want to watch (row and column are both 1-indexed, so column E is 5), and notifyEmail to a real address.

Save the script, then open the Triggers panel: click the clock icon on the left sidebar, or go to Triggers in the menu. Click Add Trigger. Set the function to notifyOnCellChange, the deployment to Head, the event source to From spreadsheet, and the event type to On edit. Save. Google will immediately ask you to authorize the script — go through the consent flow. If you see a 'This app isn't verified' warning, click Advanced and proceed; that warning appears for any personal script that hasn't been through Google's verification process.

After authorization, edit the target cell. The email arrives within a few seconds. Gmail quota for Apps Script is 100 recipients per day on consumer accounts and 1,500 on Workspace; for a single-cell watch this limit is not a practical concern.

Watching a range instead of one cell

The script above gates on exact row and column, which works for a single cell. To watch a range — say E2:E100 for a whole column section — replace the row/column check with a range overlap test.

The event object e.range gives you the edited range. Call range.getRow() and range.getLastRow() to get its bounds, then check whether those bounds overlap your target rows. For column-only watches, checking range.getColumn() <= watchCol && range.getLastColumn() >= watchCol is enough.

One gotcha: if someone pastes a block of cells that includes your target, e.range will span multiple cells but getValue() returns only the top-left cell. For pastes you may want getValues() and then locate the relevant position explicitly. For single-cell edits getValue() is always correct.

Want a custom version?

Describe your sheet and the rule you want. Gnaw writes the Apps Script — fields, triggers, edge cases — in one shot.

FAQ

4 questions
My script runs but I never get the email — what's wrong?
Almost always the simple-trigger problem: your function is named onEdit and is running without authorization, so MailApp is silently skipped. Rename the function to something else (e.g. notifyOnCellChange), then register it as an installable trigger through the Triggers UI and go through the OAuth flow.
Can I send the old value as well as the new value?
The event object does not include the old value — Apps Script does not expose it natively. The standard workaround is to cache the cell value in PropertiesService (using ScriptProperties.setProperty) on each edit and read it back at the start of the next run before overwriting it.
Will this work if someone edits the sheet from a mobile device or via an API write?
Installable edit triggers fire on human edits from any client, including mobile. They do not fire on programmatic writes made by Apps Script itself (e.g. setValue calls in another script) or by the Sheets API from an external service — those require a separate onChange trigger set to 'On change' with event type SPREADSHEET_CHANGE.
I get a 'You do not have permission to call MailApp.sendEmail' error even after authorizing.
This usually means the trigger is still registered as a simple trigger, not an installable one. Open the Triggers panel and confirm there is an entry for notifyOnCellChange listed under 'Your triggers'. If the panel is empty, the trigger was never registered — add it there. A simple onEdit with a MailApp call will always throw this error regardless of manual authorization.
// one good script a week

Get a working Apps Script snippet in your inbox, weekly.