How to Send Personalized Gmail Newsletters Directly from Google Sheets
Email personalization increases open rates by up to 26% and click-through rates by 760% according to Campaign Monitor. But you don’t need expensive email marketing software to send personalized newsletters. Using Google Sheets and Apps Script, you can create your own mail merge system for free.
In this tutorial, I’ll walk you through creating a complete system to send personalized newsletters directly from Google Sheets.
What You’ll Build
By the end of this tutorial, you’ll have:
- A Google Sheet that stores your contacts and email template
- A script that sends personalized emails to each recipient
- The ability to track who has been emailed
- A simple user interface to trigger the sending process
Prerequisites
- A Google account
- Basic familiarity with Google Sheets
- No coding experience required (though it helps)
Step 1: Set Up Your Google Sheet
First, let’s create the structure for our newsletter system:
- Create a new Google Sheet
- Create three sheets (tabs) named:
- Contacts: For your recipient list
- Template: For your email template
- Log: To track sent emails
Sheet 1: Contacts
Set up your Contacts sheet with these columns:
A: Email | B: First Name | C: Last Name | D: Custom Field 1 | E: Custom Field 2 | F: Sent |
---|---|---|---|---|---|
john@example.com | John | Smith | Gold Member | Web Developer | |
jane@example.com | Jane | Doe | Silver Member | Designer |
Sheet 2: Template
In the Template sheet, add:
- Cell A1: Subject line for your email
- Cell A3: Your email template
Here’s a sample template:
1
2
3
4
5
6
7
8
9
10
Hello ,
I hope this email finds you well. As a , we wanted to share some exclusive updates with you.
Our latest resources for s are now available!
Check them out at https://example.com/resources
Best regards,
Your Name
The `` will be replaced with each contact’s data.
Sheet 3: Log
The Log sheet will be populated automatically by our script with:
- Timestamp
- Recipient email
- Success/Failure status
- Any error messages
Step 2: Create the Apps Script
Now, let’s create the script that powers our newsletter system:
- Click Extensions → Apps Script
- Delete any existing code
- Paste the following script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
function sendPersonalizedEmails() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const contactsSheet = ss.getSheetByName("Contacts");
const templateSheet = ss.getSheetByName("Template");
const logSheet = ss.getSheetByName("Log");
// Get email template and subject
const emailSubject = templateSheet.getRange("A1").getValue();
const emailTemplate = templateSheet.getRange("A3").getValue();
// Get all recipient data
const contacts = contactsSheet.getDataRange().getValues();
const headerRow = contacts[0]; // First row contains headers
// Prepare log sheet
if (!logSheet.getRange("A1").getValue()) {
logSheet.getRange("A1:D1").setValues([["Timestamp", "Recipient", "Status", "Notes"]]);
logSheet.getRange("A1:D1").setFontWeight("bold");
}
// Track how many emails we've sent
let emailCount = 0;
// Loop through each row starting from row 2 (skip headers)
for (let i = 1; i < contacts.length; i++) {
// Check if already sent
if (contacts[i][5] == "YES") continue; // Skip if already marked as sent
const emailAddress = contacts[i][0];
// Skip empty email addresses
if (!emailAddress) continue;
try {
// Create personalized email by replacing placeholders
let personalizedEmail = emailTemplate;
// Replace each placeholder with actual data
for (let j = 1; j < headerRow.length; j++) {
const placeholder = `}`;
personalizedEmail = personalizedEmail.replace(new RegExp(placeholder, 'g'), contacts[i][j]);
}
// Send the email
GmailApp.sendEmail(
emailAddress,
emailSubject,
personalizedEmail
);
// Mark as sent in the contacts sheet
contactsSheet.getRange(i + 1, 6).setValue("YES");
// Log success
logSheet.appendRow([new Date(), emailAddress, "SUCCESS", ""]);
// Count emails sent
emailCount++;
// Stay within Gmail quotas (100 emails per day for regular accounts)
if (emailCount >= 100) {
logSheet.appendRow([new Date(), "", "LIMIT REACHED", "Daily limit of 100 emails reached"]);
break;
}
// Add a small delay to avoid hitting rate limits
Utilities.sleep(1000);
} catch (error) {
// Log errors
logSheet.appendRow([new Date(), emailAddress, "ERROR", error.toString()]);
}
}
// Return summary
return `${emailCount} emails sent successfully.`;
}
What This Script Does
- Retrieves your email template and contacts: Reads data from your sheets
- Personalizes each email: Replaces placeholders like `` with actual data
- Sends emails one by one: Uses Gmail’s API to send each personalized message
- Tracks progress: Marks each recipient as “sent” and logs the activity
- Handles errors: Catches and logs any issues without stopping the entire process
- Respects limits: Pauses between sends and stops after 100 emails (Gmail’s daily limit for personal accounts)
Step 3: Add a User Interface
Let’s make it easy to trigger the email sending with a custom button:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu('Newsletter')
.addItem('Send Personalized Emails', 'showSendConfirmation')
.addToUi();
}
function showSendConfirmation() {
const ui = SpreadsheetApp.getUi();
const response = ui.alert(
'Send Newsletters',
'Are you sure you want to send personalized emails to all contacts who haven\'t received one yet?',
ui.ButtonSet.YES_NO
);
if (response == ui.Button.YES) {
const result = sendPersonalizedEmails();
ui.alert('Email Status', result, ui.ButtonSet.OK);
}
}
Add this code to your script and save it. This creates a new menu in your spreadsheet called “Newsletter” with the option to send emails.
Step 4: Test and Authorize
- Save your script (give it a name like “Newsletter System”)
- Reload your Google Sheet
- You should see a new “Newsletter” menu item
- Before using it with real emails, test with your own email address
Important: When you first run the script, Google will ask for permission to send emails on your behalf. You’ll need to authorize these permissions.
Step 5: Send Your Newsletters
Now you’re ready to send your personalized newsletters:
- Make sure your Contacts sheet contains real data
- Verify your email template looks good
- Click Newsletter → Send Personalized Emails
- Confirm that you want to proceed
- The script will run, sending emails and updating the log
Advanced Customization Options
Once you’re comfortable with the basic system, consider these enhancements:
Add HTML Formatting
For rich text emails with formatting, links, and images, modify the script to send HTML emails:
1
2
3
4
5
6
7
8
9
GmailApp.sendEmail(
emailAddress,
emailSubject,
"", // Plain text fallback (empty here)
{
htmlBody: personalizedHtmlEmail,
name: "Your Newsletter Name" // The sender name that appears
}
);
Add Attachments
Add PDF brochures or images to your emails:
1
2
3
4
5
6
7
8
9
10
11
12
13
// Get a file from Google Drive
const fileId = "YOUR_DRIVE_FILE_ID";
const attachment = DriveApp.getFileById(fileId);
GmailApp.sendEmail(
emailAddress,
emailSubject,
personalizedEmail,
{
attachments: [attachment.getBlob()],
name: "Your Newsletter Name"
}
);
Schedule Regular Newsletters
Set up a time-driven trigger to send newsletters automatically:
- In the Apps Script editor, click the “Triggers” icon (clock)
- Click “Add Trigger”
- Choose:
- Function:
sendPersonalizedEmails
- Event source: “Time-driven”
- Type of time: “Week timer” (or your preferred schedule)
- Function:
- Save the trigger
Troubleshooting
Common Issues
- Emails not sending: Check your Gmail quota (100 emails/day for personal accounts)
- Placeholder not working: Ensure placeholder names match exactly with column headers
- Script timeout: For large lists, consider breaking into batches or using triggers
Gmail Limitations
- Personal Gmail accounts: 100 emails per day
- Google Workspace accounts: 1,500+ per day (varies by plan)
- Emails sent through Apps Script count toward your daily quota
Conclusion
You now have a powerful, free alternative to expensive email marketing tools. This Google Sheets mail merge system is perfect for:
- Small business newsletters
- Club or organization announcements
- Classroom communications
- Event invitations and updates
The best part? You have complete control over your data and can customize every aspect of the system.
What’s Next?
In future tutorials, we’ll cover:
- Building a subscription/unsubscription system
- Adding email tracking to see who opened your newsletters
- Creating a template library for different types of communications
Have you used Apps Script for email automation before? Share your experiences or questions in the comments below!
Want to explore more Google Sheets automation? Check out our guide to Top 10 Google Sheets Time-Saving Scripts.
Questions? Corrections? Issues and pull requests are always welcome.