Skip to content

Instantly share code, notes, and snippets.

@adamwolf adamwolf/
Last active Jan 21, 2020

What would you like to do?
Integrate Gmail and Beeminder using Google Apps Script. Beemind the age of your oldest email in your inbox, or the number of unread threads.
/* Beemind Gmail, by Adam Wolf
This is a Google Apps Script that lets you beemind the oldest message in your Gmail inbox in “days old”, as well as the
unread thread count.
You can share it to multiple Gmail accounts of yours, and each one will beemind to a different goal.
This is not user-friendly right now (more on this later), but it does the job for me.
## Getting Started
1. Go to, and setup Google Apps Script if you haven’t. Create a new project. I called
mine BeemindGmail. Copy this script into the file it creates.
2. Get your Beeminder username and auth token. Your username and auth token can be found at from a browser where you have logged into Beeminder. You should see
some JSON, with two keys and two values. The two values are your username and auth token. They do not include the
quotation marks. Don't share your auth token with anyone that you wouldn't give your password.
3. Put the username and auth token in the Script Properties as BEEMINDER_USER and BEEMINDER_TOKEN. You can do this
in File > Project Properties.
4. Run the function copyScriptCredentialsToUserProperties. You can do this in the top bar. Hit Save, and then there’s
a function dropdown. Select copyScriptCredentialsToUserProperties, and then hit the play button. This pulls the
username and token from the Script properties, which get copied along when you share, to User properties, which do not.
(You can delete the Script properties now, if you want, or you can wait until after we’ve done some testing!)
5. Configure the userGoalMapping. This ties the Gmail user to the Beeminder goal, and the type of datapoint.
I use this on multiple gmail accounts, so mine looks like:
var userGoalMapping = {'':
{'oldestMessage': 'oldest_msg_in_gmail_inbox',
'readThreads': 'mail_unread_count'},
{'oldestMessage': 'cbl_oldest_msg_in_inbox',
'readThreads': 'cbl_mail_unread_count'}
If you only want oldestMessage or readThreads, only include those. Remember to create the goal first!
6. Try it out! Run the function BeemindGmail using the top bar. You can see the logs in View > Logs.
7. Set this to run automatically. I have it run hourly. Do this in Edit > Current project's Triggers. I also set
mine to email me immediately if there is a script issue.
8. Delete the Script Properties if you haven’t already. This ensures that if you share this script with a buddy, you
aren’t sharing your auth token by accident.
Feel free to file an issue, contact me with problems, or even submit a fix :)
One idea is to have this submit datapoints via email, rather than the API! It would be much more user friendly.
However, this will never be able to be super user friendly, as Google has really locked down the Gmail API and requires
a pretty strict security audit for anything that goes through all your email like this. (This is good, and I'm not
If lots of people want this, rather than polish this up, we should talk to Beeminder themselves and let them know that
folks are interested in these metrics!
var userGoalMapping = {'': {'oldestMessage': 'oldest_msg_in_gmail_inbox', 'readThreads': 'mail_unread_count'},
'': {'oldestMessage': 'cbl_oldest_msg_in_inbox', 'readThreads': 'cbl_mail_unread_count'}
function getAllInboxThreads()
var out = [];
var index = 0;
const perPage = 50;
do {
page = GmailApp.getInboxThreads(index, perPage);
out = out.concat(page);
index += page.length;
} while (page.length == perPage);
return out;
function getOldestMessageInInbox() {
// may return null, if the inbox is empty.
//Get the oldest thread in the inbox, and get the newest message in it.
var threads = getAllInboxThreads();
if (threads.length == 0)
return null;
var messages = GmailApp.getMessagesForThread(threads[threads.length-1]);
var message = messages[messages.length-1];
return message;
function getReadThreadsInInbox()
var threads = getAllInboxThreads();
var messages = GmailApp.getMessagesForThreads(threads);
var readThreads = []; //list of subjects
for (var thread_index = 0 ; thread_index < messages.length; thread_index++) {
for (var message_index = 0; message_index < messages[thread_index].length; message_index++) {
var message = messages[thread_index][message_index];
var subject = message.getSubject();
var inInbox = message.isInInbox();
var isUnread = message.isUnread();
if (inInbox && !isUnread)
break; // go to next thread
return readThreads;
function beemindGmail()
var userEmail = Session.getEffectiveUser().getEmail();
Logger.log("Beeminding Gmail for: "+ userEmail);
var userTimeZone = CalendarApp.getDefaultCalendar().getTimeZone();
if (!(userEmail in userGoalMapping)) {
throw new Error( "User " + userEmail + " not in goal mapping." );
} else
if ('oldestMessage' in userGoalMapping[userEmail])
Logger.log("Beeminding oldest message.");
oldestMessage = getOldestMessageInInbox();
var days_old = 0;
var comment;
if (oldestMessage)
Logger.log("Oldest message in inbox: " + oldestMessage.getDate() + ", " + oldestMessage.getSubject());
var d = oldestMessage.getDate().getTime();
var now =;
var diff = now - d;
comment = oldestMessage.getSubject();
days_old = ((now - d)/ 1000 / 60 / 60 / 24);
} else
Logger.log("No messages in inbox. Nice work.");
comment = "";
createDatapoint(userGoalMapping[userEmail]['oldestMessage'], days_old, comment);
if ('readThreads' in userGoalMapping[userEmail])
Logger.log("Beeminding read threads.");
readThreads = getReadThreadsInInbox();
Logger.log("Read threads in inbox: " + readThreads.length + " (" + readThreads + ")");
createDatapoint(userGoalMapping[userEmail]['readThreads'], readThreads.length);
function copyScriptCredentialsToUserProperties()
//Run this function manually to copy the proper
var userProperties = PropertiesService.getUserProperties();
var scriptProperties = PropertiesService.getScriptProperties();
userProperties.setProperty("BEEMINDER_USER", scriptProperties.getProperty("BEEMINDER_USER"));
userProperties.setProperty("BEEMINDER_TOKEN", scriptProperties.getProperty("BEEMINDER_TOKEN"));
function createDatapoint(slug, value, comment) {
//TODO don't post duplicates!
var userProperties = PropertiesService.getUserProperties();
var url = "" + userProperties.getProperty("BEEMINDER_USER") + "/goals/" + slug + "/datapoints.json";
var data = {"value": value}
if (comment)
data["comment"] = comment;
Logger.log("Posting datapoint with data: " + JSON.stringify(data) + "to URL: " + url);
data["auth_token"] = userProperties.getProperty('BEEMINDER_TOKEN');
var payload = JSON.stringify(data);
var headers = { "Accept":"application/json",
var options = {"method":"POST",
"contentType" : "application/json",
"headers": headers,
"payload" : payload
var response = UrlFetchApp.fetch(url, options);

This comment has been minimized.

Copy link

lawrenceevalyn commented Jul 30, 2019

I love it! I love it so much, I'd love to be able to use it to beemind the oldest emails that have particular tags or are in particular folders! I'm not sure how to add an "issue" but this comment is the official proof of my desire :)


This comment has been minimized.

Copy link
Owner Author

adamwolf commented Jan 14, 2020

Sorry! I am not sure Github ever alerted me of this, and I'm just seeing this now. Is this still something you're interested in?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.