Daily Inbox Digest Setup Guide

Decorative

What this does

This Google Apps Script emails you a digest of your inbox every morning between 6 and 7 AM.

The digest has two sections. 'Look at these' first holds anything from a VIP sender, anything Gmail marked important or you starred, and anything with keywords like deadline, approval, or a question. 'Everything else' holds the rest. Each row shows the sender, time, a clickable subject that opens the thread, and a short snippet. Unread threads are bolded, and flagged items show why they were flagged.

It filters out promotions, social, updates, forums, automated noreply senders, newsletters, calendar invitations, and accept or decline replies.

Setup takes about 15 minutes. You need a UC Berkeley Google account and nothing else.

Decorative

What you'll need

Make sure you are logged in to your UC Berkeley gMail account.

Follow the instructions below or view and download the Daily Inbox Digest: Setup Guide PDF

Decorative

Step 1 - Create the project

  1. Go to script.google.com while signed in with your Berkeley Google account. Click New project in the top left. Click "Untitled project" at the top and rename it Daily Inbox Digest.

Step 2 - Paste in the code

  1. Delete the placeholder code in Code.gs, paste in everything below, and press Ctrl+S (Cmd+S on Mac) to save.
/**
* Daily Inbox Digest (no AI)
* Pulls Gmail from the last 24 hours and emails you a sorted digest.
*/

const CONFIG = {
QUERY: 'newer_than:1d -from:me -category:promotions -category:social -category:updates -category:forums ' +
'-from:noreply -from:no-reply -from:donotreply ' +
'-subject:invitation -filename:ics -subject:accepted -subject:declined -subject:tentative',
MAX_THREADS: 100,
SNIPPET_LENGTH: 160,

// Always flagged, and never filtered out as a newsletter
VIP_SENDERS: [
'name@berkeley.edu',
'vcf@berkeley.edu'
],

// Subject or body containing any of these gets flagged as priority
FLAG_KEYWORDS: [
'urgent', 'asap', 'deadline', 'due', 'by eod', 'by friday',
'action required', 'approval', 'approve', 'decision', 'sign off', '?'
]
};

function sendDailyDigest() {
const cutoff = new Date(Date.now() - 24 * 60 * 60 * 1000);
const threads = GmailApp.search(CONFIG.QUERY, 0, CONFIG.MAX_THREADS);

const priority = [];
const everythingElse = [];

threads.forEach(thread => {
const recent = thread.getMessages().filter(m => m.getDate() > cutoff);
if (recent.length === 0) return;

const latest = recent[recent.length - 1];
const from = latest.getFrom();

// Skip newsletters and mailing lists unless the sender is a VIP
const isVip = CONFIG.VIP_SENDERS.some(v => from.toLowerCase().includes(v.toLowerCase()));
if (latest.getHeader('List-Unsubscribe') && !isVip) return;

const subject = latest.getSubject() || '(no subject)';
const body = latest.getPlainBody().replace(/\s+/g, ' ').trim();
const reasons = flagReasons(thread, isVip, subject, body);

const item = {
from: cleanSender(from),
subject: subject,
time: Utilities.formatDate(latest.getDate(), Session.getScriptTimeZone(), 'h:mm a'),
snippet: body.substring(0, CONFIG.SNIPPET_LENGTH),
count: recent.length,
unread: thread.isUnread(),
reasons: reasons,
link: 'https://mail.google.com/mail/u/0/#all/' + thread.getId()
};

(reasons.length > 0 ? priority : everythingElse).push(item);
});

const me = Session.getActiveUser().getEmail();
const dateLabel = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'EEE MMM d');
const total = priority.length + everythingElse.length;

if (total === 0) {
MailApp.sendEmail(me, 'Inbox digest ' + dateLabel + ': nothing new', 'No new email in the last 24 hours.');
return;
}

const html =
'<div style="font-family:Arial,sans-serif;font-size:14px;color:#222;">' +
'<p><b>' + total + ' threads</b> in the last 24 hours. ' +
priority.length + ' flagged for a closer look.</p>' +
section('Look at these first', priority, true) +
section('Everything else', everythingElse, false) +
'</div>';

MailApp.sendEmail({
to: me,
subject: 'Inbox digest ' + dateLabel + ' (' + priority.length + ' flagged / ' + total + ' total)',
htmlBody: html
});
}

function flagReasons(thread, isVip, subject, body) {
const reasons = [];
const text = (subject + ' ' + body.substring(0, 2000)).toLowerCase();

if (isVip) reasons.push('VIP');
if (thread.isImportant()) reasons.push('Gmail important');
if (thread.hasStarredMessages()) reasons.push('Starred');

const hits = CONFIG.FLAG_KEYWORDS.filter(k => k !== '?' && text.includes(k));
if (hits.length) reasons.push('Keyword: ' + hits.slice(0, 2).join(', '));
if (CONFIG.FLAG_KEYWORDS.includes('?') && body.substring(0, 1500).includes('?')) reasons.push('Question');

return reasons;
}

function section(title, items, showReasons) {
if (items.length === 0) return '';
const rows = items.map(i =>
'<tr>' +
'<td style="padding:6px 8px;border-bottom:1px solid #eee;vertical-align:top;white-space:nowrap;">' +
(i.unread ? '<b>' : '') + esc(i.from) + (i.unread ? '</b>' : '') +
'<br><span style="color:#888;font-size:12px;">' + i.time +
(i.count > 1 ? ' &middot; ' + i.count + ' msgs' : '') + '</span></td>' +
'<td style="padding:6px 8px;border-bottom:1px solid #eee;vertical-align:top;">' +
'<a href="https://bpm.berkeley.edu/%27%20%2B%20i.link%20%2B%20%27" style="color:#1a4d8f;text-decoration:none;"><b>' + esc(i.subject) + '</b></a>' +
(showReasons ? ' <span style="color:#b35c00;font-size:12px;">[' + esc(i.reasons.join(' | ')) + ']</span>' : '') +
'<br><span style="color:#555;">' + esc(i.snippet) + '</span></td>' +
'</tr>'
).join('');

return '<h3 style="margin:18px 0 6px;">' + title + ' (' + items.length + ')</h3>' +
'<table style="border-collapse:collapse;width:100%;">' + rows + '</table>';
}

function cleanSender(from) {
const match = from.match(/^"?([^"<]+)"?\s*</);
return match ? match[1].trim() : from;
}

function esc(s) {
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}

/** Run once to schedule the digest every morning between 6 and 7 AM. */
function createDailyTrigger() {
ScriptApp.getProjectTriggers()
.filter(t => t.getHandlerFunction() === 'sendDailyDigest')
.forEach(t => ScriptApp.deleteTrigger(t));

ScriptApp.newTrigger('sendDailyDigest')
.timeBased()
.everyDays(1)
.atHour(6)
.create();
}

Step 3 - Replace the manifest

  1. Click the gear icon (Project Settings) in the left sidebar and check the box for Show "appsscript.json" manifest file in editor. 
  2. Go back to the Editor (the < > icon), open appsscript.json, delete its contents, paste this in, and save.
{
"timeZone": "America/Los_Angeles",
"runtimeVersion": "V8",
"exceptionLogging": "STACKDRIVER",
"oauthScopes": [
"https://www.googleapis.com/auth/gmail.readonly",
"https://www.googleapis.com/auth/script.send_mail",
"https://www.googleapis.com/auth/script.scriptapp",
"https://www.googleapis.com/auth/userinfo.email"
]
}

Step 4 - Add your VIPs and keywords

  1. Near the top of Code.gs, replace the BOLDED placeholder addresses in VIP_SENDERS with the people whose email you never want to miss. Each goes in quotes, separated by commas. VIPs always get flagged, and their mail never gets filtered as a newsletter, so this is also where you add any campus list you want to keep. 
  2. Adjust the BOLDED FLAG_KEYWORDS if you like, then save.

Step 5 - Run a test and authorize

  1. In the toolbar dropdown next to Debug, pick sendDailyDigest and click Run. 
  2. Click Review permissions and choose your Berkeley account. If you see "Google hasn't verified this app," click Advanced, then Go to Daily Inbox Digest. That warning is normal for a script you wrote yourself. 
  3. Click Allow, then check your inbox for the digest.

Step 6 - Schedule it

  1. Change the dropdown to createDailyTrigger and click Run once.
  2. Click the clock icon (Triggers) in the left sidebar and confirm a daily trigger for sendDailyDigest is listed. It now runs every morning between 6 and 7 AM Pacific.

Step 7 - Tune it after a few days

  1. Watch what lands in the flagged section for the first week, then adjust. The next section covers the common fixes.

Tuning and troubleshooting

Most fixes are a one-line edit near the top of Code.gs. Save after any change. The trigger picks it up on the next run, so there's no need to recreate it.

ProblemFix
Routine emails keep getting flaggedRemove the noisy keyword from FLAG_KEYWORDS. The question mark is the usual culprit.
A repeat sender clutters the digestAdd -from:theiraddress to the QUERY line.
Something important went missingAdd that sender to VIP_SENDERS. VIPs are never filtered as newsletters.
A campus list you want disappearedAdd the list's address to VIP_SENDERS.
You want real invitations backRemove -subject:invitation from QUERY. -filename:ics still blocks calendar invites.
You want it at a different timeChange .atHour(6) in createDailyTrigger, then run createDailyTrigger once more.
"Access blocked by your administrator" during Step 5This is a Berkeley Workspace policy. Submit an IT ticket asking for Apps Script access to Gmail.
No digest arrivedOpen Executions (the list icon in the left sidebar) to see whether the run failed and why.

Note: To turn it off, open Triggers and delete the sendDailyDigest trigger.