How to send a draft email using google apps script

The only option I see is to: - open the message - extract body/attachments/title/from/to/cc/bcc - send a new message with the above params - destroy the previous draft

This is the exact topic of this blog by Amit Agarawal. His script does just what you describe, but doesn't handle inline images. For those, you can adapt the code from this article.

But you're right - what's the point of even having a draft message if you can't just send the stupid thing?!

We can use the GMail API Users.drafts: send from Google Apps Script to send a draft. The following stand-alone script does that, and handles the necessary authorization.

Script

The full script is available in this gist.

/*
 * Send all drafts labeled "send-tomorrow".
 */
function sendDayOldDrafts() {
  var threads = GmailApp.search('in:draft label:send-tomorrow');

  for (var i=0; i<threads.length; i++) {
    var msgId = threads[0].getMessages()[0].getId();
    sendDraftMsg( msgId );
  }
}


/**
 * Sends a draft message that matches the given message ID.
 * Throws if unsuccessful.
 * See https://developers.google.com/gmail/api/v1/reference/users/drafts/send.
 *
 * @param {String}     messageId   Immutable Gmail Message ID to send
 *
 * @returns {Object}               Response object if successful, see
 *                                 https://developers.google.com/gmail/api/v1/reference/users/drafts/send#response
 */
function sendDraftMsg( msgId ) {
  // Get draft message.
  var draftMsg = getDraftMsg(msgId,"json");
  if (!getDraftMsg(msgId)) throw new Error( "Unable to get draft with msgId '"+msgId+"'" );

  // see https://developers.google.com/gmail/api/v1/reference/users/drafts/send
  var url = 'https://www.googleapis.com/gmail/v1/users/me/drafts/send'
  var headers = {
    Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
  };
  var params = {
    method: "post",
    contentType: "application/json",
    headers: headers,
    muteHttpExceptions: true,
    payload: JSON.stringify(draftMsg)
  };
  var check = UrlFetchApp.getRequest(url, params)
  var response = UrlFetchApp.fetch(url, params);

  var result = response.getResponseCode();
  if (result == '200') {  // OK
    return JSON.parse(response.getContentText());
  }
  else {
    // This is only needed when muteHttpExceptions == true
    var err = JSON.parse(response.getContentText());
    throw new Error( 'Error (' + result + ") " + err.error.message );
  }
}


/**
 * Gets the current user's draft messages.
 * Throws if unsuccessful.
 * See https://developers.google.com/gmail/api/v1/reference/users/drafts/list.
 *
 * @returns {Object[]}             If successful, returns an array of 
 *                                 Users.drafts resources.
 */
function getDrafts() {
  var url = 'https://www.googleapis.com/gmail/v1/users/me/drafts';
  var headers = {
    Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
  };
  var params = {
    headers: headers,
    muteHttpExceptions: true
  };
  var check = UrlFetchApp.getRequest(url, params)
  var response = UrlFetchApp.fetch(url, params);

  var result = response.getResponseCode();
  if (result == '200') {  // OK
    return JSON.parse(response.getContentText()).drafts;
  }
  else {
    // This is only needed when muteHttpExceptions == true
    var error = JSON.parse(response.getContentText());
    throw new Error( 'Error (' + result + ") " + error.message );
  }
}

/**
 * Gets the draft message ID that corresponds to a given Gmail Message ID.
 *
 * @param {String}     messageId   Immutable Gmail Message ID to search for
 *
 * @returns {String}               Immutable Gmail Draft ID, or null if not found
 */
function getDraftId( messageId ) {
  if (messageId) {
    var drafts = getDrafts();

    for (var i=0; i<drafts.length; i++) {
      if (drafts[i].message.id === messageId) {
        return drafts[i].id;
      }
    }
  }

  // Didn't find the requested message
  return null;
}


/**
 * Gets the draft message content that corresponds to a given Gmail Message ID.
 * Throws if unsuccessful.
 * See https://developers.google.com/gmail/api/v1/reference/users/drafts/get.
 *
 * @param {String}     messageId   Immutable Gmail Message ID to search for
 * @param {String}     optFormat   Optional format; "object" (default) or "json"
 *
 * @returns {Object or String}     If successful, returns a Users.drafts resource.
 */
function getDraftMsg( messageId, optFormat ) {
  var draftId = getDraftId( messageId );

  var url = 'https://www.googleapis.com/gmail/v1/users/me/drafts'+"/"+draftId;
  var headers = {
    Authorization: 'Bearer ' + ScriptApp.getOAuthToken()
  };
  var params = {
    headers: headers,
    muteHttpExceptions: true
  };
  var check = UrlFetchApp.getRequest(url, params)
  var response = UrlFetchApp.fetch(url, params);

  var result = response.getResponseCode();
  if (result == '200') {  // OK
    if (optFormat && optFormat == "JSON") {
      return response.getContentText();
    }
    else {
      return JSON.parse(response.getContentText());
    }
  }
  else {
    // This is only needed when muteHttpExceptions == true
    var error = JSON.parse(response.getContentText());
    throw new Error( 'Error (' + result + ") " + error.message );
  }
}

Authorization

To use Google's APIs, we need to have an OAuth2 token for the current user - just as we do for Advanced Services. This is done using ScriptApp.getOAuthToken().

After copying the code to your own script, open Resources -> Advanced Google Services, open the link for the Google Developers Console, and enable the Gmail API for your project.

As long as the script contains at least one GMailApp method that requires user authority, the authentication scope will be set properly for the OAuthToken. In this example, that's taken care of by GmailApp.search() in sendDayOldDrafts(); but for insurance you could include a non-reachable function call directly in the functions using the API.


I did it using the GmailMessage.forward method.

It works with upload images and attachments, but I had to set the subject to avoid the prefix "Fwd:", and the user name because it only displayed the user email to the recipients.

I didn't find a way to dispose the draft, so I just remove the label to prevent sending it again.

Script:

function getUserFullName(){
  var email = Session.getActiveUser().getEmail();
  var contact = ContactsApp.getContact(email);
  return contact.getFullName();
}

function testSendTomorrow(){
  var threads = GmailApp.search('in:draft label:send-tomorrow');

  if(threads.length == 0){
    return;
  }

  var labelSendTomorrow = GmailApp.getUserLabelByName("send-tomorrow");

  for(var i = 0; i < threads.length; i++){
    var messages = threads[i].getMessages();
    for(var j = 0; j < messages.length; j++){
      var mssg = messages[j];
      if(mssg.isDraft()){
        mssg.forward(mssg.getTo(), {
          cc: mssg.getCc(),
          bcc: mssg.getBcc(),
          subject: mssg.getSubject(),
          name: getUserFullName()
        });
      }
    }
    threads[i].removeLabel(labelSendTomorrow);
  }
}