Mail Merge in Google Sheets Without YAMM: Free Apps Script Alternative
Stop Paying \$400/Year for Mail Merge: Build a Free, Reliable System in Google Sheets. You are paying up to \$400 a year for brittle tools like YAMM
Build Your Own Mail Merge
You're paying $300-400/year for tools like YAMM that break when you hit quotas. Outreach sequences stall. The team spends hours debugging "Daily limit exceeded" errors.
This is straightforward to solve. You can build a mail merge system in Google Sheets using Apps Script. It uses Gmail quotas you already have, handles errors properly, costs nothing to run.
Build vs. Buy
Subscription tools sell convenience. You get stuck with their limitations and their pricing. Building it yourself means full control, no monthly bills, and you can customize it however you need.
| Feature | Subscription Tool (e.g., YAMM) | Google Apps Script (Build) | The Bottom Line |
|---|---|---|---|
| Cost | ~$40 - $400 USD/year | $0 (Included with Google account) | Build it once. Zero running cost. |
| Quota | Tied to a paid tier | Your native Google limit (100/day consumer, 1,500/day Workspace via MailApp) |
Maximize the tools you already own. You control the code that consumes the quota. |
| Customization | Limited to UI features | Unlimited (Full JavaScript/V8 runtime access) | Total control over workflow, error handling, and email content (HTML, attachments). |
| Execution Limit | N/A | 6 mins/run (consumer), 30 mins/run (Workspace) | Requires smart batch processing for large lists, which this script includes. |
Apps Script runs on Google's servers and uses the native MailApp class, which means it uses your Gmail quota directly. You get real error handling and can control batching—something a third-party add-on can't.
How It Works
The script does four things:
- Reads your contact data from the Sheet in one call
- Loops through each row, personalizing your email template
- Sends via your Gmail account
- Marks each row as "SENT" or "ERROR" so it doesn't duplicate
It also stops gracefully when you hit your daily quota and can resume later.
The Core Script: Building the Engine
Prerequisites
- Permissions: You need editor access to the Google Sheet.
- Sheet Structure: Your active sheet must have headers in Row 1. Mandatory columns:
EmailandStatus. You can add others likeNameorCompany. - OAuth Scopes: The script requires authorization. When you first run it, a pop-up will ask for permission.
Setup & Code
- Open your Google Sheet.
- Go to
Extensions>Apps Script. - Delete any placeholder code and paste the following.
- Save the project.
/**
* Adds a custom menu to the active spreadsheet.
*/
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu('Mail Merge')
.addItem('Send Emails', 'sendEmails')
.addToUi();
}
/**
* Sends emails based on the active sheet data.
*/
function sendEmails() {
const sheet = SpreadsheetApp.getActiveSheet();
const dataRange = sheet.getDataRange();
const data = dataRange.getValues();
// Identify column indices (0-based)
const headers = data[0];
const emailCol = headers.indexOf('Email');
const statusCol = headers.indexOf('Status');
const nameCol = headers.indexOf('Name'); // Optional
if (emailCol === -1 || statusCol === -1) {
SpreadsheetApp.getUi().alert('Error: "Email" and "Status" columns are required.');
return;
}
const subject = "Update regarding your account"; // Custom subject line
// Iterate through rows (skipping header)
for (let i = 1; i < data.length; i++) {
const row = data[i];
const email = row[emailCol];
const status = row[statusCol];
const name = nameCol !== -1 ? row[nameCol] : "Customer";
// Skip if already sent or email is empty
if (status === 'SENT' || !email) {
continue;
}
// Check Quota
if (MailApp.getRemainingDailyQuota() < 1) {
sheet.getRange(i + 1, statusCol + 1).setValue('QUOTA_LIMIT_REACHED');
break;
}
try {
// Send Email
MailApp.sendEmail({
to: email,
subject: subject,
htmlBody: `
<p>Hi ${name},</p>
<p>This is a personalized update just for you.</p>
<p>Best,<br>Mereth Team</p>
`
});
// Mark as Sent
sheet.getRange(i + 1, statusCol + 1).setValue('SENT');
SpreadsheetApp.flush(); // Force update UI
} catch (e) {
sheet.getRange(i + 1, statusCol + 1).setValue('ERROR: ' + e.message);
}
}
SpreadsheetApp.getUi().alert('Mail merge complete.');
}How to Run It
- To run it, refresh your spreadsheet.
- A new "Mail Merge" menu will appear after a few seconds.
- Click
Mail Merge > Send Emails. - The first time, you must grant permission to access your Sheet and send email on your behalf.
How to Avoid the "Daily Limit Exceeded" Error
The core technical constraint is Google's daily email quota. For Apps Script's MailApp service, this is 100 recipients/day for consumer Gmail accounts and 1,500 recipients/day for Google Workspace accounts.
Here is how the script handles this constraint:
- Mandatory State Management: The
Statuscolumn is the source of truth. If the script stops for any reason, it restarts without emailing the same person twice. - Proactive Quota Checks:
MailApp.getRemainingDailyQuota()prevents the script from failing on a quota error. - Use Time-Based Triggers for Large Lists: For lists over a few hundred contacts, set a time-driven trigger to run this function hourly. It will chip away at the list safely within limits.
Next Steps: Build It or Ship It
You now have a reliable, free mail merge system.
The code's above. Build it yourself this weekend.
Get more tips like this
Subscribe for practical Google Workspace automation tips.
Free updates. No spam. Unsubscribe any time.
Want more help?
We're here to help. Drop us an email and let's explore how we can optimize your workflows.
Email ushello@mereth.dev