Google Apps Script to Schedule Gmail API Emails at Later Date & Time Automatically on Composing Full Project With Source Code

 

 

code.gs

 

 

/**********
  Simple Email Scheduler by Wisebot.io
  ------------------------------------
  This add-on checks for scheduling instructions in your draft mails and processes 
  the draft to send the mail in the required time window.
  NOTES:
  * User's default time zone is assumed to interpret datetime strings
  TRIGGERS:
  1. process_jobs - every 30 minutes - This will process all drafts mails as instructed.
  
***********/

var message_retry_max_hours = 50; //Message will be not be sent if 50 hours has passed since it has been scheduled.
var execution_start = new Date();

var WISEBOT = 'WISEBOT';
var WISEBOT_FOLDER = 'WISEBOT_FOLDER';
var IF_NO_REPLY = 'if_no_reply';
var NO_CONDITION = 'no_condition';
var SUBJECTS_TO_CHECK = 'subjects_to_check';
var SUBJECT = 'subject';
var SEND_AFTER = 'send_after';
var SENT_AFTER = 'sent_after';
var SEND_CONDITION = 'send_condition';
var PROCESS_JOBS_TRIGGER = 'process_jobs_trigger';
var SENT = 'sent';
var NOT_SENT = 'not_sent';
var PENDING = 'pending';
var GOOGLE_SPREADSHEET = 'application/vnd.google-apps.spreadsheet';
var INSTALL = 'install';
var UNINSTALL = 'uninstall';
var SUCCESS_REASON_GENERIC = 'All conditions satisfied';
var FAILURE_REASON_GENERIC = 'Could not understand instructions';
var FAILURE_REASON_SEND_WINDOW = 'Could not be sent within ' + message_retry_max_hours + ' hours from scheduled time';
var FAILURE_REASON_REPLY_DETECTED = 'Reply detected';
var FAILURE_REASON_API = 'Some API/quota issue';
var FAILURE_REASON_NOT_YET_TIME = 'Schedued time not reached yet';

var SUBJECT_DELIMITER = '---';
var IMAGE_DELIMITER = '$';


function __delete_trigger__(){
  var user_preferences = PropertiesService.getUserProperties();
  var triggers = ScriptApp.getProjectTriggers();
  for(var i=0; i<triggers.length; i++){
    if(user_preferences.getProperty(PROCESS_JOBS_TRIGGER) == triggers[i].getUniqueId()){
      ScriptApp.deleteTrigger(triggers[i]);
      user_preferences.deleteProperty(PROCESS_JOBS_TRIGGER);
      break;
    }
  }
  
  //cleaning up in case of manual uninstallation of triggers
  if(user_preferences.getProperty(PROCESS_JOBS_TRIGGER)){
     user_preferences.deleteProperty(PROCESS_JOBS_TRIGGER);
  }
}


function __set_trigger__(){
  var user_preferences = PropertiesService.getUserProperties();
  if(!user_preferences.getProperty(PROCESS_JOBS_TRIGGER)){
     var trigger = ScriptApp.newTrigger('process_jobs')
       .timeBased()
       .everyMinutes(30)
       .create()
     ;
    user_preferences.setProperty(PROCESS_JOBS_TRIGGER, trigger.getUniqueId());
  }
}


function __delete_folder__(){
  var user_preferences = PropertiesService.getUserProperties();
  if(user_preferences.getProperty(WISEBOT_FOLDER)){
    Drive.Files.remove(user_preferences.getProperty(WISEBOT_FOLDER));
    user_preferences.deleteProperty(WISEBOT_FOLDER);
  }
}


function __create_folder__(){
  var user_preferences = PropertiesService.getUserProperties();
  if(!user_preferences.getProperty(WISEBOT_FOLDER)){
    var folder = DriveApp.createFolder(WISEBOT);
    user_preferences.setProperty(WISEBOT_FOLDER, folder.getId());
  }
}


function __date_to_string__(x, no_time){
  no_time = no_time || false;
  x = x || execution_start;
  
  var date_string = x.getFullYear() 
    + '-' + ('0' + x.getMonth()).slice(-2)
    + '-' + ('0' + x.getDate()).slice(-2) 
  ;
  if(!no_time){
    date_string = date_string 
      + 'T' + ('0' + x.getHours()).slice(-2) 
      + ':' + ('0' + x.getMinutes()).slice(-2)
    ;
  }
  
  return date_string;
}


function __string_to_date__(x){
  return new Date(x);
}


function __record_execution_log__(to, subject, status, reason, send_instructions){
  var header_row = null;
  var user_preferences = PropertiesService.getUserProperties();
  var folder = DriveApp.getFolderById(user_preferences.getProperty(WISEBOT_FOLDER));
  var file_name = WISEBOT + '-' + __date_to_string__(execution_start, true)
  
  var report_file = folder.getFilesByName(file_name);
  if(report_file.hasNext()){
    report_file = report_file.next();
    var sheet = SpreadsheetApp.open(report_file).getActiveSheet();
  }
  else{
    report_file = {
      parents: [{id: user_preferences.getProperty(WISEBOT_FOLDER)}],
      title: file_name,
      mimeType: GOOGLE_SPREADSHEET
    }
    
    header_row = [
      'email_id', 
      'subject', 
      'scheduled_on', 
      'send_condition', 
      'status', 
      'reason', 
      'processed_on'
    ];
    
    report_file = Drive.Files.insert(report_file);
    var sheet = SpreadsheetApp.openById(report_file.id).getActiveSheet();
  }

  if(folder){ //why? consider removing...
    if(header_row){
      sheet.appendRow(header_row);
    }
    
    sheet.appendRow([
      to, 
      subject, 
      send_instructions[SEND_AFTER],
      send_instructions[SEND_CONDITION],      
      status, 
      reason, 
      execution_start
    ]);
  }
}


function __send_condition_check__(send_instructions, message){
  var send_condition = send_instructions[SEND_CONDITION];
  var send_mail = true;

  if(send_condition == NO_CONDITION){
    send_mail = true;
  }
  else if(send_condition == IF_NO_REPLY){
    var new_to = message.getTo().split(',');
    var from_addresses_to_check = '{';
    for(var i=0; i<new_to.length; i++){
      from_addresses_to_check + 'from:' + new_to[i] + ' ';
    }
    from_addresses_to_check = from_addresses_to_check.trim() + '}';
    
    for(var i=0; i<send_instructions[SUBJECTS_TO_CHECK].length; i++){
      var subject_to_check = send_instructions[SUBJECTS_TO_CHECK][i];
      subject_to_check[SUBJECT] = subject_to_check[SUBJECT].replace(/^(Re|Fwd|Fw)[: ]*\b/ig, '') //:(
      var required_date_epoch = Math.floor(new Date(subject_to_check[SENT_AFTER])/1000);
      var send_condition_check = 'in:inbox '
        + from_addresses_to_check
        + ' subject:"' 
        + subject_to_check[SUBJECT]
        + '" after:' 
        + required_date_epoch
      ;

      var threads = GmailApp.search(send_condition_check);
      if(threads.length){
        send_mail = false;
        break;
      }
    }
  }

  return send_mail;
}


function __process_draft_message__(message, subject, no_send){
  var return_value = null;
  no_send = no_send || false;
  var attachments = message.getAttachments();
  var inline_images = {};
  var msg_attachments = [];
  
  for(var i=0; i<attachments.length; i++){
    var attachment_name = attachments[i].getName();
    var name_parts = attachment_name.split(IMAGE_DELIMITER);

    if(name_parts[0] == WISEBOT){
      inline_images[attachment_name] = attachments[i].getAs(attachments[i].getContentType());
    }
    else{
      msg_attachments.push(attachments[i])
    }
  }
  
  if(no_send){
    var new_draft = message.getRawContent();
    new_draft = new_draft.replace(
      subject[0] + SUBJECT_DELIMITER + subject[1] + SUBJECT_DELIMITER, 
      ''
    );
    new_draft = Utilities.base64EncodeWebSafe(new_draft);
    return_value = Gmail.Users.Drafts.create({"message": {"raw": new_draft}}, 'me');
  }
  else{
    return_value = message.forward(message.getTo(), {
      cc: message.getCc(),
      bcc: message.getBcc(),
      from: message.getFrom(),
      subject: subject[2],
      htmlBody: message.getBody(),
      inlineImages: inline_images,
      attachments: msg_attachments
    });
  }

  Gmail.Users.Messages.remove('me', message.getId());
  return return_value;
}


function __decode_instructions__(instructions){
  var send_instructions = {};
  try{
    send_instructions = Utilities.base64Decode(instructions);
    send_instructions = JSON.parse(Utilities.newBlob(send_instructions).getDataAsString());
    send_instructions[SEND_AFTER] = __string_to_date__(send_instructions[SEND_AFTER]);
  }
  catch(e){
    try{
      send_instructions[SEND_AFTER] = __string_to_date__(instructions);
    }
    catch(e){
      send_instructions[SEND_AFTER] = false;
    }
    
    send_instructions[SEND_CONDITION] = NO_CONDITION;
    send_instructions[SUBJECTS_TO_CHECK] = [];
  }
  
  return send_instructions;
}


function process_jobs(){
  var drafts = GmailApp.getDraftMessages();
  
  for(var i=0; i<drafts.length; i++){
    var message = drafts[i];
    var subject = message.getSubject();
    subject = subject.split(SUBJECT_DELIMITER);
    if(subject[0] == WISEBOT){
      var send_instructions = __decode_instructions__(subject[1]);
      var status = null;
      var status_reason = '';
 
      if(send_instructions[SEND_AFTER] != false){
        var send_window_end = new Date(send_instructions[SEND_AFTER]);
        send_window_end.setHours(send_window_end.getHours() + message_retry_max_hours);
        if(send_instructions[SEND_AFTER] <= execution_start){
          if(execution_start <= send_window_end){
            if(__send_condition_check__(send_instructions, message)){
              if(__process_draft_message__(message, subject)){
                status = SENT;
                status_reason = SUCCESS_REASON_GENERIC;
              }
              else{
                status = PENDING
                status_reason = FAILURE_REASON_API;
              }
            }
            else{
              __process_draft_message__(message, subject, true);
              status = NOT_SENT;
              status_reason = FAILURE_REASON_REPLY_DETECTED;
            }
          }
          else{
            __process_draft_message__(message, subject, true);
            status = NOT_SENT;
            status_reason = FAILURE_REASON_SEND_WINDOW;
          }
        }
        else{
          status = PENDING;
          status_reason = FAILURE_REASON_NOT_YET_TIME;
        }
      }
      else{
        __process_draft_message__(message, subject, true);
        status = NOT_SENT;
        status_reason = FAILURE_REASON_GENERIC;
      }
      
      try{
        if(status != null){
          __record_execution_log__(
            message.getTo(), 
            subject[2], 
            status, 
            status_reason,
            send_instructions
          );
        }
      }
      catch(e){
        //folder not available. Do something here...
      }
    }
  }
}


function doGet(e){
  var output = '';
  
  if(e.parameter[INSTALL] == 1){
    __set_trigger__();
    __create_folder__();
    var reply = '<center><br><br>Wisebot Email Scheduler trigger(s) <b>installed successfully</b>.<br>Please close this tab/window to continue.</center>';
    output = HtmlService.createHtmlOutput(reply);
  }
  else if(e.parameter[UNINSTALL] == 1){
    __delete_trigger__();
    __delete_folder__();
    var reply = '<center><br><br>Wisebot Email Scheduler trigger(s) <b>uninstalled successfully</b>.<br> Please close this tab/window to continue</center>.';
    output = HtmlService.createHtmlOutput(reply);
  }
  else{
    output = HtmlService.createHtmlOutputFromFile('index');
  }
  
  return output;
}

 

See also  Join 1000+ Litecoin Mining Whatsapp Group Invite Links For Crypto Marketers & Traders

 

index.html

 

 

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <title>Simple Email Scheduler by Wisebot.io</title>
  </head>
  <body>
    <h1>Simple Email Scheduler by Wisebot.io</h1>
    <a href="https://wisebot.io/simple-email-scheduler-for-gmail.html">How it works?</a> | 
    <a href="https://github.com/vinay20045/simple-email-scheduler">Source Code on Github</a> | 
    <a href="https://script.google.com/a/macros/wisebot.io/s/AKfycbzlhN8C66VR_JfGd_jYK9EhGNyaDTODjK2fnhHAgU1yE2B_MYo/exec?install=1">Install</a> | 
    <a href="https://script.google.com/a/macros/wisebot.io/s/AKfycbzlhN8C66VR_JfGd_jYK9EhGNyaDTODjK2fnhHAgU1yE2B_MYo/exec?uninstall=1">Uninstall</a>
    <br>
    <p>This is an add-on which helps you schedule your emails by giving simple scheduling instruction in the subject line of your draft mails which will then be sent in the required time window. Please refer to the links given above to understand how this works.</p>
    <p>The script requires access to the following Google services...<br>
    <b>Gmail Access</b><br> 
      <ol>
        <li>To check draft messages for scheduling instructions</li>
        <li>To check replies to not send message if reply is received</li>
        <li>To send mails after the scheduled time has arrived</li>
        <li>To modify/cleanup drafts if message sending fails</li>
      </ol>
    <b>Google Drive & Spreadsheets</b><br>
      <ol>
        <li>To create folder (called WISEBOT) to store your reports</li>
        <li>To write run reports</li>
      </ol>
    <b>Application Data</b><br>
      <ol>
        <li>To store user preferences</li>
      </ol>
    <b>Run in background</b><br>
      <ol>
        <li>To run every 30 minutes and process mails that need to be sent</li>
      </ol>
    <p>Please report any bugs or give suggestions by filing an issue on <a href="https://github.com/vinay20045/simple-email-scheduler/issues" target="_BLANK">Gihtub</a>. You can also write to us at <a href="mailto:support@wisebot.io">support@wisebot.io</a></p>
  </body>
</html>

 

 

simple-email-scheduler

This is an email scheduler for Gmail written using Google Apps Script. It is hosted as a web app on Google Chrome webstore. The script looks for scheduling instruction in you email’s subject line, uses drafts to hold the email till it is time to send and sends the email without your intervention using apps script triggers.

See also  Join 10+ Cisco CCNA Whatsapp Group Invite Links For Network Engineers & System Administrators

You can read more about how it works here

Requirements

Google Account with Access to Gmail, Google Drive and Apps Script

Working

The script sets a trigger which, whenever it runs will check for all the drafts that has WISEBOT send instructions. It will then process all those drafts where it can decode the time and send them after the time has reached or according to send instructions.

If you have installed this from the chrome store, you should see a folder called WISEBOT in your google drive which will have day wise reports of your processed mails.

Install/Uninstall

Manually

  1. Download or clone the repo
  2. Start a project in Google Apps Script
  3. Copy all the code in Code.gs to your project (You can ignore the index.html file)
  4. Add a new trigger manually to run process_jobs function in a schedule that suits you (30 mins recommended)
  5. Simply delete the trigger and the apps script project to uninstall

From Chrome Store (for chrome browser)

  1. Add the app from the chrome web store
  2. In your chrome browser, you can go to chrome://apps and click on the app icon
  3. Click the Install link in the header
  4. Follow 1, 2 and then click Uninstall link to uninstall

For other browsers

  1. Go to app’s main page
  2. Authorize app
  3. Click the Install link in the header
  4. Follow 1, 2 and then click Uninstall link to uninstall

Simple Usage – Scheduling a mail to be sent later

  1. Start composing a mail as you normally would
  2. Once you are done change your subject line to include the time you need to send the mail as shown below
    WISEBOT---<javascript_date_string>---<your_actual_subject_line>
    For example, if you want to send a mail with subject How are you after 9 AM on 20th of September, the subject line should be…
    WISEBOT---2017-09-20T09:00---How are you
  3. Save the mail as draft (DO NOT Send)

Advanced Usage – Scheduling a mail to be sent only if there is no reply from this person to a previous mail

  1. Start composing a mail as you normally would
  2. Once you are done, you need to add a base64 encoded json string to the scheduling instruction in your subject line.
    2.1 Compose the send instructions as follows
    {
        "send_after": "<javascript_date_string>",
        "send_condition": "IF_NO_REPLY",
        "subjects_to_check": [
            {
                "subject": "<previous_email_subject_line>",
                "sent_after": "<when_was_this_mail_sent>"
            }
        ]
    }

For example, let’s say you want to send a follow up to the previous message 2 days later, then the instructions string should look like

    {
        "send_after": "2017-09-22T09:00",
        "send_condition": "IF_NO_REPLY",
        "subjects_to_check": [
            {
                "subject": "How are you",
                "sent_after": "2017-09-20T09:00"
            }
        ]
    }

2.2 Encode this string to base64 (You can use browser console to do this)
2.3 Construct your new subject line like…

    WISEBOT---<base_64_encoded_send_instructions>---<Actual subject line>

For the above example, that is…

    WISEBOT---eyJzZW5kX2FmdGVyIjogIjIwMTctMDktMjJUMDk6MDAiLCJzZW5kX2NvbmRpdGlvbiI6ICJJRl9OT19SRVBMWSIsInN1YmplY3RzX3RvX2NoZWNrIjogW3sic3ViamVjdCI6ICJIb3cgYXJlIHlvdSIsInNlbnRfYWZ0ZXIiOiAiMjAxNy0wOS0yMFQwOTowMCJ9XX0=---Just Following up
  1. Save the mail as draft (DO NOT Send)
See also  Join 1000+ Binance Coin Whatsapp Group Invite Links For Crypto Marketers & Traders

 

Leave a Reply