Below are a few stand-alone Apps Scripts you can run from the Apps Script editor in a browser (NOT a mobile app).
/**
* Returns a count of conversations by category (without using the Gmail API).
* Note: You can get the same information from the Gmail panel of your Google Account Dashboard
* @see https://myaccount.google.com/dashboard
*
* Prerequisites: NONE!
*
* Author: Mr Shane
* Version: 2025-07-12
*/
function countAllMyEmailUsingGmailApp() {
Logger.log("🚀 Starting comprehensive email thread count...");
const scriptStartTime = Date.now();
// Call the helper function for each desired category...
countThreadsByGmailAppQuery_('in:anywhere', 'total conversations (including spam/trash)');
countThreadsByGmailAppQuery_('', 'conversations');
countThreadsByGmailAppQuery_('in:inbox', 'in inbox');
countThreadsByGmailAppQuery_('in:sent', 'sent');
countThreadsByGmailAppQuery_('in:drafts', 'drafts');
countThreadsByGmailAppQuery_('in:trash', 'in bin');
countThreadsByGmailAppQuery_('is:starred', 'starred');
countThreadsByGmailAppQuery_('in:spam', 'in spam');
// countThreadsByGmailAppQuery_('label:your-label-name', 'in "your-label-name"'); // For custom labels if configured. NOTE: spaces must be entered as hyphens.
const duration = (Date.now() - scriptStartTime) / 1000;
Logger.log(`\n✨ All counts finished in ${duration.toFixed(2)} seconds.`);
}
/**
* A helper function that counts threads for a specific Gmail query.
* It processes threads in batches to remain fast and avoid timeouts.
* @param {string} query The Gmail search query (e.g., 'in:inbox').
* @param {string} description A human-readable description for the log output.
*/
function countThreadsByGmailAppQuery_(query, description) {
const startTime = Date.now();
const TIME_LIMIT = 300000; // 5-minute time limit for each individual query
const BATCH_SIZE = 500;
let totalThreadCount = 0;
let start = 0;
let threads;
do {
// Check if this specific query is taking too long
if (Date.now() - startTime > TIME_LIMIT) {
Logger.log(`⏸️ Time limit reached for "${description}". Counted so far: ${totalThreadCount}`);
return; // Stop this count and allow the script to proceed to the next one
}
try {
// Perform the paginated search for the given query
threads = GmailApp.search(query, start, BATCH_SIZE);
const batchCount = threads.length;
if (batchCount > 0) {
totalThreadCount += batchCount;
start += BATCH_SIZE; // Move to the next page
}
} catch (e) {
Logger.log(`An error occurred while counting "${description}": ${e.toString()}`);
return; // Stop this count on error
}
} while (threads && threads.length === BATCH_SIZE);
Logger.log(`✅ ${totalThreadCount} ${description}`); // Log the final count for the category
}
/**
* Return counts of email conversations by category
* Note: You can get the same information from the Gmail panel of your Google Account Dashboard
* @see https://myaccount.google.com/dashboard
*
* Prerequisites: The Gmail API must be enabled in your Google Apps Script project.
* To enable it:
* 1. Open the script editor.
* 2. Click on "Services +" in the left-hand menu.
* 3. Find "Gmail API" and click "Add".
*
* Author: Mr Shane
* Version: 2025-06-02
*/
function countAllMyEmailUsingGmailApi() {
countThreadsByGmailApiQuery_('', 'conversations'); // All conversations
countThreadsByGmailApiQuery_('in:inbox', 'in inbox'); // Inbox only
countThreadsByGmailApiQuery_('in:sent', 'sent'); // Sent
countThreadsByGmailApiQuery_('in:drafts', 'drafts'); // Drafts
countThreadsByGmailApiQuery_('in:trash', 'in bin'); // Trash
// countThreadsByGmailApiQuery('label:your-custom-label', 'in custom label'); // For custom labels if configured. NOTE: spaces must be entered as hyphens.
}
/**
* Logs the total number of email threads matching a Gmail search query.
* @param {string} query - Gmail search query (e.g., "in:inbox", "in:sent", "")
* @param {string} labelDescription - Human-readable description for the log output
*/
function countThreadsByGmailApiQuery_(query, description) {
let total = 0;
let pageToken = null;
do {
const response = Gmail.Users.Threads.list("me", {
maxResults: 500,
pageToken: pageToken,
...(query && { q: query }) // Include query only if non-empty
});
const threads = response.threads || [];
total += threads.length;
pageToken = response.nextPageToken;
} while (pageToken);
Logger.log(`${total} ${description}`);
}
/**
* Lists ALL Gmail labels and counts the number of conversations (threads) in each.
* This script combines the functionality of listing all labels and counting emails based on a query.
*
* Prerequisites: The Gmail API must be enabled in your Google Apps Script project.
* To enable it:
* 1. Open the script editor.
* 2. Click on "Services +" in the left-hand menu.
* 3. Find "Gmail API" and click "Add".
*
* Author: Mr Shane
* Version: 2025-09-08
*/
function countEmailsInAllLabels() {
try {
const response = Gmail.Users.Labels.list('me'); // Get the list of all labels from the user's mailbox.
if (!response || !response.labels || response.labels.length === 0) {
Logger.log('No labels found in your Gmail account.');
return;
}
Logger.log('Counting email conversations per label:');
for (const label of response.labels) { // Iterate through each label and count the threads within it.
const query = `label:"${label.name}"`; // The query format `label:"label name"` handles names with spaces or special characters.
countThreadsForLabel_(query, label.name); // Count the threads within each label.
}
} catch (err) {
Logger.log('Failed to list labels. Error: %s', err.toString()); // Handle exceptions that might occur during the API call to list labels.
}
}
/**
* A helper function that logs the total number of email threads for a given query.
* It handles pagination to count all threads, not just the first page.
*
* @param {string} query - The Gmail search query (e.g., 'label:"My Label"').
* @param {string} labelName - The name of the label for the log output.
*/
function countThreadsForLabel_(query, labelName) {
let total = 0;
let pageToken = null;
try {
do {
const response = Gmail.Users.Threads.list("me", {
maxResults: 500, // Use the maximum allowed value per page to reduce API calls.
pageToken: pageToken,
q: query
});
if (response.threads) { // Add the number of threads from the current page to the total.
total += response.threads.length;
}
pageToken = response.nextPageToken; // Get the token for the next page of results.
} while (pageToken); // Continue as long as there is a next page.
Logger.log(`- ${labelName}: ${total}`); // Log the final count for the label.
} catch (err) {
Logger.log(`Could not count emails for label "${labelName}". Error: %s`, err.toString()); // Handle exceptions for individual label counts.
}
}
/**
* Lists all labels in the user's mailbox.
* Prerequisites: Gmail API is enabled.
*
* @see https://developers.google.com/gmail/api/quickstart/apps-script
* @see https://developers.google.com/gmail/api/reference/rest/v1/users.labels/list
*/
function listLabels() {
try {
// Gmail.Users.Labels.list() API returns the list of all Labels in user's mailbox
const response = Gmail.Users.Labels.list('me');
if (!response || response.labels.length === 0) {
// TODO (developer) - No labels are returned from the response
console.log('No labels found.');
return;
}
// Print the Labels that are available.
console.log('Labels:');
for (const label of response.labels ) {
console.log('- %s', label.name);
}
} catch (err) {
// TODO (developer) - Handle exception on Labels.list() API
console.log('Labels.list() API failed with error %s', err.toString());
}
}
/**
* Log the remaining Recipients quota for MailApp
* @see https://developers.google.com/apps-script/guides/services/quotas#current_quotas
* @see https://developers.google.com/apps-script/reference/mail/mail-app
*/
function remainingRecipientsQuotaForMailApp() {
const remainingRecipients = MailApp.getRemainingDailyQuota();
Logger.log("Remaining Recipients Quota: " + remainingRecipients);
}
/**
* Delete old emails from the inbox that are not ⭐ starred and not marked ⏩ as 'important'.
* Author: Mr Shane
* Version: 2025-05-24
* Recommendations: Ensure your Gmail is configured to show the 'important' markers.
* @see https://support.google.com/mail/answer/186543?hl=en#zippy=%2Chide-importance-markers-in-gmail
*/
/*** CONFIGURATION ***/
const AGED = 14; // Configure the number of days before emails are eligible for deletion.
const PAGE_SIZE = 50; // Configure the maximum number of threads to process per run.
/*** SETUP FUNCTIONS ***/
/**
* Creates a daily time-based trigger to run the purge function.
* Execute this function to install the script.
*/
function setPurgeTrigger() {
ScriptApp.newTrigger('purge').timeBased().everyDays(1).create(); // Create the trigger
}
/**
* Deletes all project triggers.
* Execute this function to uninstall the script.
*/
function removeAllTriggers() {
ScriptApp.getProjectTriggers() // Get all triggers for the current project
.forEach(trigger => ScriptApp.deleteTrigger(trigger)); // Delete each trigger
}
/*** MAIN PURGE LOGIC ***/
/**
* Deletes inbox threads older than AGED and not starred.
* If PAGE_SIZE limit is hit, schedules another run in 2 minutes.
* NOTE: This script stars all messages in a thread so that the thread is not deleted.
*/
function purge() {
removePurgeMoreTriggers(); // Remove any existing 'purgeMore' triggers to avoid duplicates
const searchQuery = `in:inbox -in:starred -in:important older_than:${AGED}d`; // Congigure the Gmail search query
const threads = GmailApp.search(searchQuery, 0, PAGE_SIZE); // Search for matching threads
console.log(`Processing ${threads.length} threads...`); // Log how many threads are being processed
if (threads.length === PAGE_SIZE) { // If the result set is at PAGE_SIZE limit
console.log('PAGE_SIZE limit reached. Scheduling another purge in 2 minutes.'); // Log continuation notice
setPurgeMoreTrigger(); // Schedule a follow-up run
}
const cutoff = new Date(); // Get the current date
cutoff.setDate(cutoff.getDate() - AGED); // Calculate the cutoff date
threads.forEach(thread => { // Iterate through each thread
const messages = thread.getMessages(); // Get all messages in the thread
const hasStarredMessage = messages.some(message => message.isStarred()); // Check if any message is starred
if (hasStarredMessage) { // If there's a starred message
messages.forEach(message => message.star()); // Ensure all messages in the thread are starred
} else if (thread.getLastMessageDate() < cutoff) { // If thread is older than cutoff and unstarred
thread.moveToTrash(); // Move thread to trash
}
});
}
/**
* Wrapper for the purge function, used when scheduling additional purges.
*/
function purgeMore() {
purge(); // Simply call the purge function again
}
/*** HELPER FUNCTIONS ***/
/**
* Deletes all triggers that call the 'purgeMore' function.
*/
function removePurgeMoreTriggers() {
ScriptApp.getProjectTriggers() // Get all project triggers
.filter(trigger => trigger.getHandlerFunction() === 'purgeMore') // Find only those tied to 'purgeMore'
.forEach(trigger => ScriptApp.deleteTrigger(trigger)); // Delete each matching trigger
}
/**
* Creates a time-based trigger to run purgeMore in 2 minutes.
*/
function setPurgeMoreTrigger() {
const triggerTime = new Date(Date.now() + 2 * 60 * 1000); // Set a time 2 minutes from now
ScriptApp.newTrigger('purgeMore').timeBased().at(triggerTime).create(); // Create the trigger
}