Back to Blog

How to Automate Emails from Google Sheets: 3 Practical Methods

Bypublished on

You can automate emails from Google Sheets using Google Apps Script and the GmailApp service. This lets you send individual messages from selected rows, bulk emails to entire contact lists, or personalized mail merges that customize each message with recipient data. The setup takes about 10 minutes and requires no external tools or paid services.

This guide covers three methods with copy-paste code examples. All methods use the same spreadsheet structure and work from a custom menu, starting with simple single emails and progressing to advanced mail merge functionality.

Why Automate Emails from Google Sheets

Automating Google Sheets eliminates manual email tasks that consume hours each week. Instead of copying addresses, drafting messages, and clicking send repeatedly, you can trigger hundreds of personalized emails with one click.

Common use cases include:

  • Customer notifications: Send order confirmations, shipping updates, or service reminders automatically when data changes
  • Team alerts: Notify stakeholders when metrics hit thresholds or tasks require attention
  • Report distribution: Email weekly summaries, monthly reports, or dashboards to distribution lists
  • Event invitations: Send personalized invites with unique details from spreadsheet rows
  • Follow-up sequences: Automate outreach campaigns based on response tracking in sheets

The scripts run within Google's infrastructure, so you don't need to maintain servers or manage API credentials beyond the initial authorization.

Initial Setup: Creating Your Email Sheet

Before diving into the three methods, set up your spreadsheet with a consistent structure that works for all automation approaches.

Sheet Structure

Create a Google Sheet with the following columns in row 1:

  • Column A: Email Address
  • Column B: First Name
  • Column C: Company (optional for personalization)
  • Column D: Subject
  • Column E: Message
  • Column F: Status (tracks whether email was sent)
  • Column G: Sent Date (timestamp)

Starting from row 2, add your recipient data. Each row represents one email to send.

Creating the Custom Menu

All three methods will be accessible from a custom menu that appears in your spreadsheet menu bar. Open the Apps Script editor by going to Extensions → Apps Script, delete any default code, and paste this:

// This function runs automatically when you open the spreadsheet function onOpen() { const ui = SpreadsheetApp.getUi(); // Create custom menu ui.createMenu('📧 Email Automation') .addItem('Send Single Email', 'sendSingleEmail') // Method 1 .addItem('Send Bulk Emails', 'sendBulkEmails') // Method 2 .addItem('Mail Merge (Personalized)', 'mailMerge') // Method 3 .addToUi(); }

Save the script, then reload your spreadsheet. You'll see a new "📧 Email Automation" menu in the menu bar with options for all three methods.

First-Time Authorization

The first time you run any script, Google will ask for permission to access Gmail and your spreadsheet. Click Review Permissions, sign in with your Google account, and click Allow. You only need to do this once.

Level: BeginnerMethod 1: Send Individual Emails

The simplest way to automate emails from Google Sheets is sending a single message from the currently selected row. Select any row in your sheet, go to the custom menu, and click "Send Single Email" to send just that one email.

How It Works

This method reads data from whichever row you have selected. It's perfect for reviewing each email before sending, or sending one-off messages without processing your entire list.

The Apps Script Code

Add this function to your Apps Script editor (below the onOpen function):

function sendSingleEmail() { const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); const activeRow = sheet.getActiveRange().getRow(); // Skip if header row is selected if (activeRow === 1) { SpreadsheetApp.getUi().alert('Please select a data row, not the header.'); return; } // Read values from the active row const email = sheet.getRange(activeRow, 1).getValue(); const firstName = sheet.getRange(activeRow, 2).getValue(); const subject = sheet.getRange(activeRow, 4).getValue(); const message = sheet.getRange(activeRow, 5).getValue(); const status = sheet.getRange(activeRow, 6).getValue(); // Validate email address if (!email || !email.toString().includes('@')) { SpreadsheetApp.getUi().alert('Error: Invalid email address in selected row'); return; } // Check if already sent if (status === 'Sent') { const response = SpreadsheetApp.getUi().alert( 'This email was already sent. Send again?', SpreadsheetApp.getUi().ButtonSet.YES_NO ); if (response !== SpreadsheetApp.getUi().Button.YES) { return; } } // Send the email try { GmailApp.sendEmail(email, subject, message); // Update status and timestamp sheet.getRange(activeRow, 6).setValue('Sent'); sheet.getRange(activeRow, 7).setValue(new Date()); SpreadsheetApp.getUi().alert('Email sent successfully to: ' + email); } catch (error) { sheet.getRange(activeRow, 6).setValue('Failed: ' + error.message); SpreadsheetApp.getUi().alert('Error sending email: ' + error.message); } }

Using Method 1

  1. Click any cell in the row you want to send
  2. Go to 📧 Email Automation → Send Single Email
  3. The script sends the email and updates the Status column

Level: IntermediateMethod 2: Send Bulk Emails from a List

Send emails to every row in your spreadsheet at once. This method processes all rows with data, skips emails already marked as "Sent," and tracks progress automatically. Perfect for newsletters, announcements, or batch notifications.

How It Works

The script loops through all rows starting from row 2, reads the email details from each row's columns, sends the email, and updates the status. If you run it multiple times, it only sends to rows where the Status column is empty, preventing duplicates.

The Apps Script Code

Add this function to your Apps Script editor:

function sendBulkEmails() { const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); const lastRow = sheet.getLastRow(); // Track results let sentCount = 0; let skippedCount = 0; let failedCount = 0; // Loop through each row (skip header) for (let i = 2; i <= lastRow; i++) { const email = sheet.getRange(i, 1).getValue(); const firstName = sheet.getRange(i, 2).getValue(); const subject = sheet.getRange(i, 4).getValue(); const message = sheet.getRange(i, 5).getValue(); const status = sheet.getRange(i, 6).getValue(); // Skip if already sent if (status === 'Sent') { skippedCount++; continue; } // Skip if missing data if (!email || !subject || !message) { continue; } // Validate email format if (!email.toString().includes('@')) { sheet.getRange(i, 6).setValue('Invalid Email'); failedCount++; continue; } // Send email try { GmailApp.sendEmail(email, subject, message); sheet.getRange(i, 6).setValue('Sent'); sheet.getRange(i, 7).setValue(new Date()); sentCount++; // Delay to respect Gmail rate limits Utilities.sleep(1000); } catch (error) { sheet.getRange(i, 6).setValue('Failed: ' + error.message); failedCount++; } } // Show summary const summary = ` Bulk Email Results: ✅ Sent: ${sentCount} ⏭️ Skipped (already sent): ${skippedCount} ❌ Failed: ${failedCount} `; SpreadsheetApp.getUi().alert(summary); }

Using Method 2

  1. Fill in all rows with email data (columns A through E)
  2. Go to 📧 Email Automation → Send Bulk Emails
  3. The script processes all rows and shows a summary when complete

Gmail Sending Limits

Google imposes daily sending limits to prevent spam. Free Gmail accounts can send up to 100 emails per day, while Google Workspace accounts get 1,500 per day. The script includes a 1-second delay between messages to stay within rate limits. For more information, check Google's email sending guidelines.

Level: AdvancedMethod 3: Gmail Mail Merge with Google Sheets

Mail merge creates personalized emails where each recipient gets a custom message using their First Name and Company from the spreadsheet. Instead of using the Subject and Message columns directly, this method uses templates with placeholders that get replaced with data from each row.

How It Works

The script uses the same spreadsheet structure but pulls the First Name (column B) and Company (column C) to personalize email templates. This lets you send custom messages to hundreds of recipients while maintaining a personal touch.

The Apps Script Code

Add this function to your Apps Script editor:

function mailMerge() { const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); // Email templates with placeholders const subjectTemplate = 'Hello {{firstName}} from {{company}}'; const messageTemplate = `Hi {{firstName}}, I hope this email finds you well. I wanted to reach out regarding {{company}}. We have a special offer that might interest your team. Best regards, Your Name`; const lastRow = sheet.getLastRow(); let sentCount = 0; let skippedCount = 0; let failedCount = 0; // Process each row for (let i = 2; i <= lastRow; i++) { const email = sheet.getRange(i, 1).getValue(); const firstName = sheet.getRange(i, 2).getValue(); const company = sheet.getRange(i, 3).getValue(); const status = sheet.getRange(i, 6).getValue(); // Skip if already sent if (status === 'Sent') { skippedCount++; continue; } // Validate data if (!email || !email.toString().includes('@')) { sheet.getRange(i, 6).setValue('Invalid Email'); failedCount++; continue; } // Skip if missing personalization data if (!firstName || !company) { sheet.getRange(i, 6).setValue('Missing Data'); failedCount++; continue; } // Replace placeholders with actual values const personalizedSubject = subjectTemplate .replace('{{firstName}}', firstName) .replace('{{company}}', company); const personalizedMessage = messageTemplate .replace(/{{firstName}}/g, firstName) .replace(/{{company}}/g, company); try { GmailApp.sendEmail(email, personalizedSubject, personalizedMessage); sheet.getRange(i, 6).setValue('Sent'); sheet.getRange(i, 7).setValue(new Date()); sentCount++; Utilities.sleep(1000); } catch (error) { sheet.getRange(i, 6).setValue('Failed: ' + error.message); failedCount++; } } // Show summary const summary = ` Mail Merge Results: ✅ Sent: ${sentCount} ⏭️ Skipped (already sent): ${skippedCount} ❌ Failed: ${failedCount} `; SpreadsheetApp.getUi().alert(summary); }

Customizing the Template

Edit the subjectTemplate and messageTemplate variables to match your needs. Use {{firstName}} and {{company}} as placeholders that will be replaced with data from columns B and C.

You can add more placeholders by reading additional columns. For example, to add a {{role}} placeholder from column H:

// Read the role from column 8 const role = sheet.getRange(i, 8).getValue(); // Add to template replacement const personalizedMessage = messageTemplate .replace(/{{firstName}}/g, firstName) .replace(/{{company}}/g, company) .replace(/{{role}}/g, role);

Using Method 3

  1. Fill in Email Address, First Name, and Company for each recipient
  2. Customize the templates in the script to match your message
  3. Go to 📧 Email Automation → Mail Merge (Personalized)
  4. The script sends personalized emails and shows a summary

Pro Tip: Test First

Before running mail merge on your full list, test with just one or two rows. Add test data with your own email address, run the script, and verify the personalization works correctly.

Best Practices for Email Automation

Follow these guidelines to ensure your Apps Script automation runs smoothly and avoids common pitfalls.

Respect Rate Limits

Always include delays between emails using Utilities.sleep(1000). This prevents hitting Gmail's rate limits and keeps your account in good standing. For large campaigns, consider splitting sends across multiple days.

Test Before Sending

Test your scripts with your own email address first. Verify that placeholders are replaced correctly, formatting looks right, and attachments load properly. Send to a small test group before running full campaigns.

Track Status

Always use a status column to track sent emails. This prevents accidentally sending duplicate messages and helps you resume if the script fails partway through a large batch.

Handle Errors Gracefully

Wrap email sending in try-catch blocks to handle failures without stopping the entire script. Log errors to the spreadsheet so you can review and fix issues later.

Use Professional From Names

Set a custom sender name using the name parameter in sendEmail(). This makes emails look more professional than showing your personal Gmail address.

Common Issues and Solutions

Authorization Errors

If you see "Authorization required" errors, the script needs permission to access Gmail. Click Review Permissions, sign in with your Google account, and click Allow. Google may show a warning for unverified apps—click Advanced and proceed to grant access.

Quota Exceeded

If you hit Gmail's daily sending limit (100 for free accounts, 1,500 for Workspace), the script will fail. Upgrade to Google Workspace for higher limits, or split your campaign across multiple days. The status tracking ensures you can resume where you left off.

Emails Going to Spam

Automated emails may land in spam folders if they lack proper formatting or contain spammy language. Use professional from names, include unsubscribe links for marketing emails, and avoid excessive links or promotional language. Test sends to verify deliverability.

Script Timeout

Apps Script has a 6-minute execution limit. If sending to large lists, the script may timeout. Process emails in smaller batches of 100-200 at a time, or set up a time-based trigger to run the script every hour until complete.

Next Steps

You now have three methods to automate emails from Google Sheets. Start with individual emails to understand the basics, then move to bulk sending and mail merge as your needs grow.

For more advanced automation, combine email sending with other Google Apps Script examples like automated data entry forms or scheduled triggers. You can also explore third-party tools like Zapier or n8n for complex workflows that connect Google Sheets to other platforms.