Gmail Snooze Script – We Don’t Need No Stinkin MailboxApp

I’ll admit it. I got caught up in the MailboxApp fever and watched the little counter until I could get my hands on the app. Then I did and realized I didn’t particularly care for the interface or for giving up credentials to yet another service (though I do have 2 factor authentication setup). I like the Gmail interface. I think it’s done well. And I want control. So I asked that question that every engineer asks: “How can I do this better?”. Naturally the answer was smacking me in the face. Maybe a year ago I came across a Gmail snooze script, but didn’t really take the time to understand it or tweak it to my needs. This time was different.

Below you’ll find the setup instructions that will give you MailboxApp like functionality in something you completely control and that you can use from any email client. Just attach labels or move your messages to the any of the snooze labels and let the script do the rest. Personally, I’m using the script with a Dispatch “quick action” to easily triage my inbox.

See the “Setup” instructions in the comment block at the top of the code or refer to the images below…

gdrive-create-script

gdrive-blank-project

Paste the code from below into the script

gdrive-run-function gdrive-trigger-setup

To use the functionality provided by the script just move any email to one of the snooze labels. For example, Move to –> _Z/Tonight to have the email reappear at the time you selected (default: 5pm). These are the suggested options to use, but there are more specific labels you can use for tighter control:

  • _Z/Later
  • _Z/Midnight
  • _Z/Noon
  • _Z/Tonight
  • _Z/Tomorrow
  • _Z/This Weekend
  • _Z/Next Week
  • _Z/Next Month

I hope you like it!


/*
* Author: Mark Jacobsen (http://mjg2.net/code)
*
* This is a massively modified and more powerful GMail snooze script that mirrors much of the functionality of
* the iPhone "Mailbox" app with the added benefit of not having to hand over your credentials, and being able to 
* use from any mail client (web, Android, iPhone, etc).  If you like it or have suggestions, please drop me a line
* from my blog at http://mjg2.net/code
*
* Setup:
* 1) Fill in the values below (or just use the defaults)
* 2) Run the "setup()" function using the method drop down and run arrow
* 3) Set time based triggers for:
*      moveDailySnoozes() - Daily between Midnight to 1 am
*      moveHourlySnoozes() - Every 5 minutes
*
* Changes:
* 2013-05-06  markjacobsen.net  Fixed getLabelForHour to correctly handle am/pm thanks to "benlarge"
* 2014-02-23  markjacobsen.net  Fixed moveDailySnoozes to correctly handle the current DOW thanks to "Jeff Hirsch"
* 2014-03-24  markjacobsen.net  Added additional parseInt()s to moveHourlySnoozes
*/

// =============================================================================
// Set these values according to your needs
// =============================================================================
var TIMEZONE = "America/Detroit";  // Your timezone
var TONIGHT_HOUR = 17;  // Hour (on a 24 hour clock. ex: 17=5pm) representing when something snoozed until "Tonight" should reappear
var HOURS_UNTIL_LATER = 3;  // Number of hours when something snoozed until "Later" should appear
var REAPPEAR_LABEL = "_Action";  // Full label path to apply when a message reappears in the inbox (leave blank "" to not set a label)
var REAPPEAR_UNREAD = true;  // Decide if you want to mark a thread as unread when it reappears in the inbox

// ==========================================================================================
// I wouldn't recommend changing these values but it shouldn't hurt anything if done right :)
// ==========================================================================================
var LABEL_BASE = "_Z";  // The label to create all snooze labels under (it's suggested that you don't change this)
var LABEL_BASE_DOW = LABEL_BASE + "/zDay";
var LABEL_BASE_MO = LABEL_BASE + "/zMonth";
var LABEL_BASE_TOD = LABEL_BASE + "/zTime";

// =============================================================================
// =============================================================================
// Do NOT change anything below here
// =============================================================================
// =============================================================================
var LABEL_LATER = LABEL_BASE + "/Later";
var LABEL_MIDNIGHT = LABEL_BASE + "/Midnight";
var LABEL_NOON = LABEL_BASE + "/Noon";
var LABEL_TONIGHT = LABEL_BASE + "/Tonight";
var LABEL_TOMORROW = LABEL_BASE + "/Tomorrow";
var LABEL_THIS_WEEKEND = LABEL_BASE + "/This Weekend";
var LABEL_NEXT_WEEK = LABEL_BASE + "/Next Week";
var LABEL_NEXT_MONTH = LABEL_BASE + "/Next Month";


function setup() {
  GmailApp.createLabel(LABEL_BASE);
  GmailApp.createLabel(LABEL_BASE_DOW);
  GmailApp.createLabel(LABEL_BASE_MO);
  GmailApp.createLabel(LABEL_BASE_TOD);
  GmailApp.createLabel(LABEL_NEXT_WEEK);
  GmailApp.createLabel(LABEL_NEXT_MONTH);
  GmailApp.createLabel(LABEL_LATER);
  GmailApp.createLabel(LABEL_MIDNIGHT);
  GmailApp.createLabel(LABEL_NOON);
  GmailApp.createLabel(LABEL_TONIGHT);
  GmailApp.createLabel(LABEL_TOMORROW);
  GmailApp.createLabel(LABEL_THIS_WEEKEND);
  GmailApp.createLabel(getLabelForDayOfWeek("Sunday"));
  GmailApp.createLabel(getLabelForDayOfWeek("Monday"));
  GmailApp.createLabel(getLabelForDayOfWeek("Tuesday"));
  GmailApp.createLabel(getLabelForDayOfWeek("Wednesday"));
  GmailApp.createLabel(getLabelForDayOfWeek("Thursday"));
  GmailApp.createLabel(getLabelForDayOfWeek("Friday"));
  GmailApp.createLabel(getLabelForDayOfWeek("Saturday"));
  
  // Time of day Labels
  for (var i = 0; i <= 23; ++i) {
    GmailApp.createLabel(getLabelForHour(i));
  }
  
  // Month Labels
  for (var i = 0; i <= 11; ++i) {
    GmailApp.createLabel(getLabelForMonth(i));
  }
}

function test(){
  Logger.log(getLabelForHour(0));
  Logger.log(getLabelForHour(12));
  Logger.log(getLabelForHour(13));
  Logger.log(getLabelForHour(23));
}

function getLabelForHour(hr){
  var ampm = "am";
  if (hr > 12)
  {
    hr -= 12;
    ampm = "pm";
  }
  else if (hr == 12)
  {
    ampm = "pm";
  }
  else if (hr == 0)
  {
    hr = 12;
    ampm = "am";
  }
  return LABEL_BASE_TOD + "/" + parseInt(hr) + ampm;
}

function getLabelForMonth(mo){
  var tmp;
  
  if (mo == -1){
    tmp = new Date();
  }else{
    tmp = new Date(2000, mo, 1);
  }
  return LABEL_BASE_MO + "/"  + Utilities.formatDate(tmp, TIMEZONE, "MMMMM");
}

function getLabelForDayOfWeek(dow){
  return LABEL_BASE_DOW + "/" + dow;
}


function moveDailySnoozes() {
  var dow, dom, mo;
  
  // Move anything that was snoozed until tomorrow
  moveSnoozesToInbox(LABEL_TOMORROW);
  
  dow = Utilities.formatDate(new Date(), TIMEZONE, "EEEE");
  dom = Utilities.formatDate(new Date(), TIMEZONE, "d");
  
  // Move anything that was snoozed until this DOW
  moveSnoozesToInbox(getLabelForDayOfWeek(dow));
  
  if ((dow == "Sunday") || (dow == "Saturday")){
    // If something was snoozed until this weekend, that should be moved too
    moveSnoozesToInbox(LABEL_THIS_WEEKEND);
  }
           
  // Move anything for "next week" to the DOW it should come off snooze (i.e. the current DOW).
  // This works and doesn't move the messages pre-maturely because we moved
  // msgs for the DOW, prior to this and this functions should only fire
  // once a day
  moveSnoozes(LABEL_NEXT_WEEK, getLabelForDayOfWeek(dow));
  
  if (dom == 1){
    // Move anything snoozed until "next month" or
    // specifically for the current month on the 1st day
    // of a new month
    moveSnoozesToInbox(getLabelForMonth(-1));
    moveSnoozesToInbox(LABEL_NEXT_MONTH);
  }
}

function moveHourlySnoozes() {
  var curHour, curHourLabel, hourForLater, curDateTime;
 
  curHour = Utilities.formatDate(new Date(), TIMEZONE, "H");
  curHourLabel = getLabelForHour(curHour);
  
  moveSnoozes(LABEL_NOON, getLabelForHour(12));
  moveSnoozes(LABEL_MIDNIGHT, getLabelForHour(0));
  
  if (parseInt(curHour) == parseInt(TONIGHT_HOUR)){
    moveSnoozesToInbox(LABEL_TONIGHT);
  }
  
  moveSnoozesToInbox(curHourLabel);
  
  hourForLater = parseInt(parseInt(curHour) + HOURS_UNTIL_LATER);
  if (hourForLater >= 24){
    hourForLater = parseInt(hourForLater - 24);
  }
  moveSnoozes(LABEL_LATER, getLabelForHour(hourForLater));
}


function moveSnoozesToInbox(oldLabelName) {
  var oldLabel, page, reappearLabel;
  oldLabel = GmailApp.getUserLabelByName(oldLabelName);
  page = null;
  // Get threads in "pages" of 100 at a time
  while(!page || page.length == 100) {
    page = oldLabel.getThreads(0, 100);
    if (page.length > 0) {
      GmailApp.moveThreadsToInbox(page);
      oldLabel.removeFromThreads(page);
      if (REAPPEAR_LABEL != ""){
        reappearLabel = GmailApp.getUserLabelByName(REAPPEAR_LABEL);
        reappearLabel.addToThreads(page);
      }
      if (REAPPEAR_UNREAD == true){
        GmailApp.markThreadsUnread(page);
      }
    }     
  }
}


function moveSnoozes(oldLabelName, newLabelName) {
  var oldLabel, newLabel, page;
  oldLabel = GmailApp.getUserLabelByName(oldLabelName);
  newLabel = GmailApp.getUserLabelByName(newLabelName);
  page = null;
  // Get threads in "pages" of 100 at a time
  while(!page || page.length == 100) {
    page = oldLabel.getThreads(0, 100);
    if (page.length > 0) {
      newLabel.addToThreads(page);
      oldLabel.removeFromThreads(page);
    }     
  }
}

21 thoughts on “Gmail Snooze Script – We Don’t Need No Stinkin MailboxApp”

  1. Hello, great upgrades, this is really useful! There is a small bug in how you create the hour labels that leaves off 12PM and causes the “Noon” folder to map to 12AM. Also, your setup needs to create the REAPPEAR_LABEL.

    I might also add a followup label as well so I can quickly see what is scheduled.

    1. Thanks! Totally missed the am/pm thing. Should be fixed now in the post.

      I’m not creating the “REAPPEAR_LABEL” because I was assuming people would use a label they already had. Could see value in adding that too…

  2. This isn’t working… not creating necessary labels. Are you available to try and help?

    1. Did you change anything, and are you getting any errors in particular? Note, the code was just updated too w/ a slight mod but it should have been creating labels previously. Try grabbing the new code and giving it a whirl.

  3. thanks for great scripts mark.
    my case is after gmail category new feature, i seldom check my Action label, so any chance i can move the Action label every night and at the same time keeo the email label Action

  4. thanks for your great script.

    my case is after gmail category new features, i seldom check my Action label, so any chance i can move the Action label every night and at the same time keep the email label Action?

    by the way, is that below is correct format?
    var TIMEZONE = “Hong Kong”

  5. Hi I am getting below error message from Google App script.

    Your script, EmailSnooze, has recently failed to finish successfully. A summary of the failure(s) is shown below. To configure the triggers for this script, or change your setting for receiving future failure notifications, click here.

    Details:

    Start Function Error Message Trigger End
    7/21/13 4:01 AM moveHourlySnoozes TypeError: Cannot call method “addToThreads” of null. (line 176, file “Code”) time-based 7/21/13 4:01 AM
    Sincerely,

  6. This is a great script and one I use a lot. The only issue I get is it seems to error occasionally. I get daily emails from Google with these errors.

    moveHourlySnoozes TypeError: Cannot call method “addToThreads” of null. (line 175, file “Code”)

    Ill try and figure it out, but I’m not a great script monkey.

    1. Yeah, I get that too, but have not yet had an opportunity to research the solution. Will try and post an update if I find it. In the mean time, please do that same if you find a solution.

      1. Max, updated coming soon. In the mean time, see Jeff’s comment below or just come back in a day or so and hopefully I’ll have gotten around to updating the post.

  7. Hi Mark, I’ve been using the Gmail snooze script for some time and just came across your enhanced version which has some updates that I like. One part that didn’t look correct to me is this moveSnoozes line. It seems like it should have two parameters not just one.

    dow = Utilities.formatDate(new Date(), TIMEZONE, “EEEE”);
    dom = Utilities.formatDate(new Date(), TIMEZONE, “d”);

    moveSnoozes(getLabelForDayOfWeek(dow));

    I’m wondering if it should be
    moveSnoozesToInbox(getLabelForDayOfWeek(dow));

      1. Jeff, I think you solved it! I haven’t been getting any errors for days and a few old things I snoozed came back to me 🙂

        I’ll try to update the post soon. In the mean time, thanks again!

  8. Hey Mark,

    Thanks for that script – looks like a nice concept indeed! I’ve been suffering from not being able to perform the 0-inbox actions outside the Mailbox.app for some time now.
    There are several drawbacks to the script as it stands as compared to the Mailbox.app though:

    1. Not possible to specify the day/time mail to be snoozed precisely – say, snooze it to a particular day and time in the future. That could be a major hassle as to workaround this one would need to snooze an email to a particular month, and then on the first day of that month snooze it to either a day in the first week (provided that the “due day” falls on a first week), or snooze it to “Next week” and review it again next Monday. It of course depends on individual processing of emails, for me it is easier not to review emails which as yet not due over and over.
    2. Labels are being sorted alphabetically, so it’s probably worthwhile to rename them, adding numbers to beginning of some names (say 1_Monday, 2_Tuesday and 1_January etc). They will start to look ugly with this approach though
    3. Same sorting issues hold for times – so say 10am and 10pm stand next to each other. I would thus switch to using the 24h format instead (but maybe it’s ok for other people, I don’t know)
    4. No integration of Gmail.app with Gdrive on Gmail for iDevices as yet (and I cannot imagine they will ever implement an integration with Dropbox) – so for me at least it is harder to send emails with attachments, and then I guess Mailbox.app stays on the device for at least that reason 🙂

    Mailbox.app seems to work still way way much faster than the Gmail.app (on iDevices at least).
    With the above in mind, I will probably modify the script to workaround alphabetical sorting and think of introducing a multiple-label match to fix the issue for emails that need to be snoozed until later specific day/time in the future: that will require the use of “Label” actions in addition to the “Move”: say labelling an email with “zMonth/February”,”zDOM/23″, “zTime/10” for it to show up on 23 February at 10:00 am.

    I have already introduced one change for making sure the “weekend”-snoozed emails appear on a user-chosen day: either Sunday or Saturday and working on the same for all emails snoozed for “Next week” to show up on a particular day of week that could be changed as well.
    I think it might be worthwhile putting the script onto Github to allow anyone interested to submit PRs?

    Thank you for the idea and the awesome initial implementation once again!

    With kind regards,
    Roman

    1. Thanks for the ideas Roman. I did just add the script to Github since I had been planning on that for a while, but need to do some maint before I redirect everything there.

      Regarding your suggestions they all seem worthwhile. My logic for the current implementation

      1) Wanted to keep things simple, though if there was a good way to create a popup for selecting dates/times I could see this as a possibility.
      2) Just trying to keep it readable. The way I use it is mainly from the web interface where you can just start typing “mon” and it will give you the label to move to.
      3) Same as #2, but I can see where fans of a 24 hr clock would like that. Should be easy to offer both options for those who want it.

      I do like the possibility of being able to select a day for “next week” or “weekend”, though I would imagine that those most interested in that functionality could just as easily hack the code.

      No matter what, I appreciate you taking the time to look through things and offer up suggestions.

  9. Hi Mark,
    I have an update if you would like. I put a wrapper around the functions to catch errors so if there is a problem connecting to the GMail service the script will sleep for X amount of time and then retry the function Y amount of times. As an example here is your ‘moveSnoozesToInbox’ function with the wrapper. I’ve had a few times when the function had to rerun as the Gmail service wasn’t available on the first try. Just be careful of not putting in a really high retry or sleep count as then you could get an error that your script is using to much processing time. I have it send an email when there is an error and also when successful after the error. ~jeff

    var me = Session.getActiveUser().getEmail();
    var retrycountmax = 5 //number of times that a function will retry if it received an error
    var retrysleep = 4000 // 4 seconds of sleep before function tries to run again

    function moveSnoozesToInbox(oldLabelName) {

    var retryCount = 0;
    var success = false;

    while (!success & retryCount++ 0) {
    GmailApp.moveThreadsToInbox(page);
    oldLabel.removeFromThreads(page);
    if (REAPPEAR_LABEL != “”){
    reappearLabel = GmailApp.getUserLabelByName(REAPPEAR_LABEL);
    reappearLabel.addToThreads(page);
    }
    if (REAPPEAR_UNREAD == true){
    GmailApp.markThreadsUnread(page);
    }
    }
    }
    success = true;
    if (retryCount > 1) {
    MailApp.sendEmail(me, “Success – running moveSnoozesToInbox-” + oldLabelName, “”, {cc:””, bcc:””, htmlBody: “success on ” + retryCount + ” of ” + retrycountmax + ” times”});
    }
    }catch(ex){
    Logger.log(ex);
    MailApp.sendEmail(me, “Error – running moveSnoozesToInbox-” + oldLabelName, “”, {cc:””, bcc:””, htmlBody: ex.message + ” File: ” + ex.fileName + ” Line number: ” + ex.lineNumber + ” trying ” + retryCount + ” of ” + retrycountmax + ” times”});
    }
    Utilities.sleep(retrysleep);
    }
    }

    1. I like the retry idea. Not sure about the notification since you already get notices on script failures. Also, if you note this is part of the reason I have the “hourly” task setup to run every 5 minutes (just in case). The daily one is where this would be most beneficial.

Leave a Reply