Task guide
Log new emails to a Google Sheet
Keep a list of recent emails in a spreadsheet, one row per message with the date, From address, subject, and a link. A message ID column keeps a second run from adding the same message twice.
1 · Prepare
Put these pieces in place
- Create a Sheet tab named Email log.
- Put these headers in row 1: Date, From, Subject, Thread link, Message ID.
- Open the script from that spreadsheet with Extensions, then Apps Script, so the code writes to the right file.
Change these values
- Sheet name
Email logThe tab that receives the rows. It must already exist.
- Gmail search
is:unread newer_than:1dThe same text you would type in the Gmail search box. Change it to choose which mail is logged.
- Conversations per run
25The largest number of conversations one run reads, which keeps each run quick.
- Date format
yyyy-MM-dd HH:mmHow the date is written in column A. Change it to yyyy-MM-dd for the day only.
2 · Review before running
What the script can access and change
Permissions you approve
- Read, compose, send, and permanently delete all your email from Gmail. The code only reads the conversations that match your search. It sends, changes, and deletes nothing.
- See, edit, create, and delete all your Google Sheets spreadsheets. The code only reads and adds rows on the Email log tab of this spreadsheet.
Effects of one run
- Adds one row per new message to the Email log tab, with the date, sender, subject, a link to the conversation, and the message ID.
- Skips any message whose ID is already in column E, so running the function twice does not duplicate rows.
- Does not mark mail as read, delete it, move it, or change its labels.
3 · Copy
Paste this into the Apps Script editor
Create or open a script file, replace the example values marked above, then save the project.
const SHEET_NAME = 'Email log'; // The tab that receives the rows. It must already exist.
const GMAIL_SEARCH = 'is:unread newer_than:1d'; // The same text you would type in the Gmail search box. Change it to choose which mail is logged.
const CONVERSATIONS_PER_RUN = 25; // The largest number of conversations one run reads, which keeps each run quick.
const DATE_FORMAT = 'yyyy-MM-dd HH:mm'; // How the date is written in column A. Change it to yyyy-MM-dd for the day only.
function logNewEmailsToSheet() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
if (!sheet) throw new Error('Add a Sheet tab named ' + SHEET_NAME + ' before running this function.');
const loggedIds = new Set();
const lastRow = sheet.getLastRow();
if (lastRow >= 2) {
sheet.getRange(2, 5, lastRow - 1, 1).getValues().forEach((row) => {
loggedIds.add(String(row[0]));
});
}
/** @type {string[][]} */
const newRows = [];
GmailApp.search(GMAIL_SEARCH, 0, CONVERSATIONS_PER_RUN).forEach((thread) => {
const threadLink = 'https://mail.google.com/mail/u/0/#inbox/' + thread.getId();
thread.getMessages().forEach((message) => {
const messageId = message.getId();
if (loggedIds.has(messageId)) return;
loggedIds.add(messageId);
const sender = message.getFrom();
const subject = message.getSubject();
newRows.push([
Utilities.formatDate(message.getDate(), Session.getScriptTimeZone(), DATE_FORMAT),
sender.startsWith('=') ? "'" + sender : sender,
subject.startsWith('=') ? "'" + subject : subject,
threadLink,
messageId
]);
});
});
if (newRows.length === 0) return;
sheet.getRange(sheet.getLastRow() + 1, 1, newRows.length, 5).setValues(newRows);
Logger.log('Added ' + newRows.length + ' rows to ' + SHEET_NAME + '.');
} 4 · Run
Follow these steps in order
Make the tab and its headers
Add a tab named Email log with the five headers in row 1. Column E holds the message ID, which is how the script knows what it already logged.
Paste and save the function
In the spreadsheet, choose Extensions, then Apps Script. Replace the starter function with this one and save.
Run one small test
Choose logNewEmailsToSheet in the function dropdown above the code, then click Run. Approve access when Google asks. The run reads at most 25 conversations. A search returns whole conversations, so one conversation can add several rows.
Run it a second time
Click Run again without changing anything. No new rows appear, because every message ID is already in column E.
Hide the ID column
Right-click the column E header and choose Hide column. The script still reads it, and the tab stays easy to read.
5 · Confirm
How to tell it worked
The Email log tab holds one row per recent message, and the link in column D opens that conversation in Gmail. A second run in a row adds nothing.
If the result is different
Troubleshooting
- The run stops with "Add a Sheet tab named Email log before running this function."
- Rename the tab to Email log, with a capital E and a lowercase l, or change SHEET_NAME in the code to the name of your tab.
- A message was logged twice.
- Check that column E still holds the message IDs. Sorting the tab is fine, but clearing or deleting that column loses the record of what was already logged. Also check that no one changed the headers so that the IDs moved out of column E.
- The run finishes but no rows are added.
- Type is:unread newer_than:1d into the Gmail search box. If Gmail finds nothing either, change GMAIL_SEARCH in the code, for example to is:unread newer_than:3d.
- The thread link opens the wrong mailbox.
- Change the 0 in u/0 in the code to the position of the account that holds this mail, counting from 0. The link opens the first account signed in to that browser.
Technical details
Names used in this script
Open these details when you want to adapt the example or look up an exact service or method name.
Show Apps Script symbols and service pages
Apps Script symbols
Service pages
Keep going
Related tasks, basics, and apps
- Task guide Label emails from one sender in Gmail
Put a Gmail label on every conversation from one sender, such as a newsletter, a few at a time. The script creates the label if needed and skips conversations that already have it.
- Task guide Send an email for each row in a Google Sheet
Send one email to each person listed on a Customers tab. The script marks each row Sent as it goes, so a later run sends only to rows that are not marked yet.
- Object reference Gmail thread
A GmailThread is one Gmail conversation. Get threads with GmailApp.search() or label.getThreads(), then call getMessages() or addLabel().
- Object reference Gmail message
A GmailMessage is one email inside a Gmail thread. Call getFrom(), getSubject(), getDate(), getPlainBody(), or getAttachments() to inspect that single message.
- Object reference Sheet
A Sheet is one tab inside a Google spreadsheet. Get it with getSheetByName() or getActiveSheet(). Use getLastRow() to find the last row with content, or getRange() to select cells to read.
- Google app Gmail
GmailApp scripts can send messages, search threads, apply labels, and read attachments after you approve Gmail access for the project.
- Google app Google Sheets
SpreadsheetApp lets a Google Sheets script read a Sheet or Range, add a custom menu or formula, and act on each row, such as sending one email per row.