Categories
Code

Download Gmail Attachments to Google Drive with Apps Script

The Save Gmail Attachments add-on for Google Drive downloads file attachments from an email message to a specific folder in your Google Drive. Internally, it uses Google Apps Script and the Advanced Drive Service to fetch the attachments and save them to Drive.

Here’s a sample snippet from the Gmail add-on that shows how you can use GmailApp service with Google Drive in Apps Script to create a copy of any Gmail attachment inside Google Drive.

We’ve set the includeAttachments option to true and includeInlineImages to false to only download regular (non-inline) attachments and not inline images.

export const saveGmailtoGoogleDrive = () => {
  const folderId = 'Google_Drive_Folder_Id';
  const searchQuery = 'has:attachments';
  const threads = GmailApp.search(searchQuery, 0, 10);
  threads.forEach(thread => {
    const messages = thread.getMessages();
    messages.forEach(message => {
      const attachments = message.getAttachments({
          includeInlineImages: false,
          includeAttachments: true
      });
      attachments.forEach(attachment => {
        Drive.Files.insert(
          {
            title: attachment.getName(),
            mimeType: attachment.getContentType(),
            parents: [{ id: folderId }]
          },
          attachment.copyBlob()
        );
      });
    });
  });
};
Categories
Code

Connect to Google API with PHP and OAuth2 – Sample Code

This sample application describes how your PHP application can connect to the user’s Gmail account using the Google PHP client library and OAuth2. You’ll need to create the application inside Google Console.

The Client ID and secret are stored in a separate JSON while the access token and refresh token are also stored in the local file system.

setApplicationName("ctrlq.org Application");
  $client->setAuthConfig($KEY_LOCATION);
  
  // Incremental authorization
  $client->setIncludeGrantedScopes(true);
  
  // Allow access to Google API when the user is not present. 
  $client->setAccessType('offline');
  $client->setRedirectUri($REDIRECT_URI);
  $client->setScopes($SCOPES);
  
  if (isset($_GET['code']) && !empty($_GET['code'])) {
      try {
          // Exchange the one-time authorization code for an access token
          $accessToken = $client->fetchAccessTokenWithAuthCode($_GET['code']);
          
          // Save the access token and refresh token in local filesystem
          file_put_contents($TOKEN_FILE, json_encode($accessToken));
          
          $_SESSION['accessToken'] = $accessToken;
          header('Location: ' . filter_var($REDIRECT_URI, FILTER_SANITIZE_URL));
          exit();
      }
      catch (\Google_Service_Exception $e) {
          print_r($e);
      }
  }
  
  if (!isset($_SESSION['accessToken'])) {
      
      $token = @file_get_contents($TOKEN_FILE);
      
      if ($token == null) {
          
          // Generate a URL to request access from Google's OAuth 2.0 server:
          $authUrl = $client->createAuthUrl();
          
          // Redirect the user to Google's OAuth server
          header('Location: ' . filter_var($authUrl, FILTER_SANITIZE_URL));
          exit();
          
      } else {
          
          $_SESSION['accessToken'] = json_decode($token, true);
          
      }
  }
  
  $client->setAccessToken($_SESSION['accessToken']);
  
  /* Refresh token when expired */
  if ($client->isAccessTokenExpired()) {
      // the new access token comes with a refresh token as well
      $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
      file_put_contents($TOKEN_FILE, json_encode($client->getAccessToken()));
  }
  
  $gmail = new Google_Service_Gmail($client);
  
  $opt_param               = array();
  $opt_param['maxResults'] = 10;
  
  $threads = $gmail->users_threads->listUsersThreads("amit@labnol.org", $opt_param);
  
  foreach ($threads as $thread) {
      print $thread->getId() . " - " . $thread->getSnippet() . '
'; } ?>
Categories
Code

Bounced Email Parsing with Google Scripts

The Google Script scans your Gmail mailbox for messages from mailer-daemon@gmail.com and prepares a bounce email report logging the failed deliveries in a Google Spreadsheet. See sample Gmail bounce report

function getBouncedEmails() {

    /* Written by Amit Agarwal */
    /* Email: amit@labnol.org  */

    // Write the bounced email report to a Google SpreadsheetApp
    var sheet = SpreadsheetApp.getActiveSheet();
    sheet.getRange(2, 1, sheet.getLastRow(), sheet.getLastColumn()).clearContent();

    // Find all emails returned via Gmail Mailer Maemon
    var query = "from:(mailer-daemon@google.com OR mailer-daemon@googlemail.com)";

    // Get the most recent 500 bounced email messages in Gmail
    GmailApp.search(query, 0, 500).forEach(function(thread) {
        thread.getMessages().forEach(function(message) {
            if (message.getFrom().indexOf("mailer-daemon") !== -1) {
                var body = message.getPlainBody();
                // Get the bounced email address from the body
                var matches = body.match(/Delivery to[\s\S]+?(\S+\@\S+)\s([\s\S]+?)----- Original Message/);
                if (matches) {
                    // Get the exact reason for the email bounce
                    var reason = matches[2].match(/The error.+:\s+(.+)/) || matches[2].match(/Technical details.+:\s+(.+)/);
                    if (reason) {
                        // Save the data in a Google Spreadsheet
                        sheet.appendRow([
                            thread.getLastMessageDate(),
                            matches[1],
                            reason[1].replace(/ (Please|Learn|See).*$/, ""),
                            thread.getPermalink(),
                            thread.getFirstMessageSubject()
                        ]);
                    }
                }
            }
        });
    });
}
Categories
Code

Get List of Email Aliases with Gmail API

Gmail users can send emails of behalf of any other email address that is set as an alias in their primary account. The Gmail API also supports these alias addresses for sending emails and the FROM address can either be set to your primary Gmail account or any alias.

The GmailApp service of Google Apps Script offers a simple getAliases() method that returns all the aliases of a Gmail account in an array.

However, this may not return aliases that are not set as “Treat as Alias” in the users’ Gmail setttings. If you would like to fetch all Gmail aliases, included those that are not using the ‘Treat as an alias’ setting, you need to use the Gmail API.

You should also check the verification status of an alias. If the status is set to “pending”, or any value other than “accepted”, you should not use it as it indicates that the user initiated the process for setting that email as an alias but did not complete the verification process.

function getGmailAliases() {

  // Get list of email aliases via the Gmail API  
  // Author Amit Agarwal Website: www.ctrlq.org
  
  var aliases = [];
  
  Gmail.Users.Settings.SendAs.list('me').sendAs.forEach(function(e) {
    if (e.verificationStatus === "accepted") {
      aliases.push({
        email: e.sendAsEmail,
        replyto: e.replyToAddress,
        name: e.displayName,
        alias: e.treatAsAlias  
      })
    }
  });
  
  return aliases;

}

The same method can be used to fetch the Gmail signature of authorized user. You would need to enable the Gmail API under Advanced Google Service for the code to work.

Here’s an alternate approach that uses the Gmail but without the Advanced Gmail Service of Apps Script.

    JSON.parse(UrlFetchApp.fetch("https://www.googleapis.com/gmail/v1/users/me/settings/sendAs", {
      contentType: "application/json",
      headers: {"Authorization": "Bearer " + ScriptApp.getOAuthToken()}
    }).getContentText()).sendAs.forEach(function(alias) {
      if (alias.verificationStatus === "accepted") {
        aliases.push(alias.sendAsEmail);
      }
    });

Also see: Unable to Add Gmail Aliases

Categories
Docs

Why Gmail is not Sending my Email Messages?

Gmail has certain limits in place and you only send or receive a limited number of emails per day. The daily limits are 2000 email messages for Google Apps (G Suite) accounts and 500 for consumer Gmail accounts.

It is important to know that Gmail sending limits are per user and are shared by all of the user’s email clients, including web apps, software programs, SMTP clients and add-ons that could be sending emails through your Gmail account.

For instance, if you have sent 300 emails through the Gmail website, 50 emails through Microsoft Outlook linked to your Gmail account via IMAP or POP, and another 100 messages through an email alias on a different domain, the maximum number of emails you can send through the Mail Merge addon will be just 50 in that 24 hour period.

If you exceed the sending limit, you’ll get an error message – like “you’ve reached a Gmail limit for sending email” or “Oops.. the system encountered a problem” – and Gmail may sometimes temporarily block you from sending new messages. The limits are not applied at any set time of the day and the user may have to wait up to 24 hours before the limits are reset automatically.

[*] The daily sending limit is even lower – 100 total recipients per day – if you are using Microsoft Exchange or any non-Google SMTP service for routing messages through Gmail email servers.

Email Bounced – Your Message Was Not Sent

If you get a bounced email from nobody@gmail.com – with the message saying “An error occurred, your message was not sent” or “You have reached a limit for sending email, your message was not sent” – it indicates that you have reached the Gmail sending limit for the day.

There’s no workaround to this problem and you’ll have to wait until Gmail resets your email quota. After the quota is reset, you can resend the message to the same recipients and they should be delivered as normal.

Your messages can also bounce if you are sending too many emails to non-existent email addresses. It is thus important to clean up your mailing lists and remove invalid email addresses before running another Mail Merge campaign.

Gmail Error – Message Rejected

This happens when Gmail classifies your outgoing email message as spam. This happens when you have spam text in the message body or if you have a large number of recipients in the CC or BCC list.

Categories
Code

Copy Email Messages in Gmail with Apps Script

This example shows how to create email copies in Gmail with Google Apps Script and the Gmail API. The requires requires authorization with the scope https://mail.google.com/ for reading Gmail drafts and composing new ones.

The uploadType is set to media and that will create the Gmail draft in a single HTTP request. However, if you are create a draft that includes big attachment, you may have to set the upload type as resumable to create the draft in at least two requests.

function duplicateGmailDrafts(count, draftId) {

    try {

        var draft = GmailApp.getMessageById(draftId);

        if (draft === null) {
            return "Sorry, the draft message was not found. Please reload this page and try again.";
        }

        var gmailAPI = "https://www.googleapis.com/upload/gmail/v1/users/me/drafts?uploadType=media";

        var params = {
            method: "POST",
            contentType: "message/rfc822",
            muteHttpExceptions: true,
            headers: {
                "Authorization": "Bearer " + ScriptApp.getOAuthToken()
            },
            payload: draft.getRawContent()
        };

        for (var i = 0; i < count; i++) {
            UrlFetchApp.fetch(gmailAPI, params);
        }

        return "Drafts Created";

    } catch (f) {

        return "Error: " + f.toString();

    }

}
Categories
Code

Use the Gmail API to Send Emails with Attachments

This example shows how you can easily send email messages with file attachment using the Gmail API. The attachments can be stored in Google Drive and you need to specify the file IDs that are to included in the outgoing messages.

We begin by creating a MIME message that complies with RFC 2822 standard and call the Gmail API to sends the specified message to the recipients in the To, Cc, and Bcc headers. We use the /upload URI with the messages/send method for uploading the files with the message and the uploadType is set to media for uploading the files without any metadata.

The code is written in Google Apps Script but Google also offers Gmail API Client libraries for PHP, Python, JavaScript and NodeJS. If you wish to include images, upload them to a site like imgur and include them in the email’s HTML body using img tags.

// Written by Amit Agarwal
// Web: ctrlq.org
// Email: amit@labnol.org

function sendEmailWithAttachments() {

  var attachments = ["File_ID_1", "File_ID_2"];

  var message = {
    to: {
      name: "Google Scripts",
      email: "amit@labnol.org"
    },
    from: {
      name: "Amit Agarwal",
      email: "amit@labnol.org"
    },
    body: {
      text: "Mr hänn is schon lang nümme g'she.",
      html: "Mr hänn is schon lang nümme g'she."
    },
    subject: "ctrlq, tech à la carte",
    files: getAttachments_(attachments)
  };

  // Compose Gmail message and send immediately
  callGmailAPI_(message);

}


function callGmailAPI_(message) {

  var payload = createMimeMessage_(message);

  var response = UrlFetchApp.fetch(
    "https://www.googleapis.com/upload/gmail/v1/users/me/messages/send?uploadType=media", {
      method: "POST",
      headers: {
        "Authorization": "Bearer " + ScriptApp.getOAuthToken(),
        "Content-Type": "message/rfc822",
      },
      muteHttpExceptions: true,
      payload: payload
    });

  Logger.log(response.getResponseCode());
  Logger.log(response.getContentText());

}

// UTF-8 characters in names and subject
function encode_(subject) {
  var enc_subject = Utilities.base64Encode(subject, Utilities.Charset.UTF_8);
  return '=?utf-8?B?' + enc_subject + '?=';
}

// Insert file attachments from Google Drive
function getAttachments_(ids) {
  var att = [];
  for (var i in ids) {
    var file = DriveApp.getFileById(ids[i]);
    att.push({
      mimeType: file.getMimeType(),
      fileName: file.getName(),
      bytes: Utilities.base64Encode(file.getBlob().getBytes())
    });
  }
  return att;
}

// Create a MIME message that complies with RFC 2822
function createMimeMessage_(msg) {

  var nl = "\n";
  var boundary = "__ctrlq_dot_org__";

  var mimeBody = [

    "MIME-Version: 1.0",
    "To: "      + encode_(msg.to.name) + "<" + msg.to.email + ">",
    "From: "    + encode_(msg.from.name) + "<" + msg.from.email + ">",
    "Subject: " + encode_(msg.subject), // takes care of accented characters

    "Content-Type: multipart/alternative; boundary=" + boundary + nl,
    "--" + boundary,

    "Content-Type: text/plain; charset=UTF-8",
    "Content-Transfer-Encoding: base64" + nl,
    Utilities.base64Encode(msg.body.text, Utilities.Charset.UTF_8) + nl,
    "--" + boundary,

    "Content-Type: text/html; charset=UTF-8",
    "Content-Transfer-Encoding: base64" + nl,
    Utilities.base64Encode(msg.body.html, Utilities.Charset.UTF_8) + nl

  ];

  for (var i = 0; i < msg.files.length; i++) {

    var attachment = [
      "--" + boundary,
      "Content-Type: " + msg.files[i].mimeType + '; name="' + msg.files[i].fileName + '"',
      'Content-Disposition: attachment; filename="' + msg.files[i].fileName + '"',
      "Content-Transfer-Encoding: base64" + nl,
      msg.files[i].bytes
    ];

    mimeBody.push(attachment.join(nl));

  }

  mimeBody.push("--" + boundary + "--");

  return mimeBody.join(nl);

}
Categories
Code

Update Gmail Picture of Google Apps Users with Google Script

An organisation is running on Google Apps and, as part of a branding exercise, they need to update the profile pictures of all Gmail users. The Google Apps admin can use the Admin Directory API with Google Apps Script to update Gmail photos of all users in the domain. The files can be placed in Google Drive and the script can automatically make it the user’s photo.

The Apps Admin Directory API accepts images in JPEG, PNG, GIF, BMP and TIFF formats. The user is identified by their primary email address and the photo’s upload data should be encoded in web-safe Base64 format.

function updateGmailPhotoFromDrive() {
  
  try {
    
    var userEmail = 'amit@labnol.org';
    var fileName  = userEmail + ".jpg";
    
    var blob = DriveApp.getFilesByName(fileName).next().getBlob();
    var data = Utilities.base64EncodeWebSafe(blob.getBytes());
    
    AdminDirectory.Users.Photos.update({photoData: data}, userEmail);
    
  }
  
  catch (err) {
    Logger.log(err.toString());
  }
  
}

If an image is on the web (and not on Google Drive), the URL Fetch service can be used to make it the user’s photo.

function updateGmailPhotoFromWeb() {
  
  try {
    
    var userEmail = 'amit@labnol.org';
    var webURL    = 'https://ctrlq.org/assets/ctrlq.org.png';
    
    var blob = UrlFetchApp.fetch(webURL).getBlob();
    var data = Utilities.base64EncodeWebSafe(blob.getBytes());
    
    AdminDirectory.Users.Photos.update({photoData: data}, userEmail);
    
  }
  
  catch (err) {
    Logger.log(err.toString());
  }
  
}
Categories
Code

Update Gmail Signatures of Employees with Google Apps Script

Google Apps allows domain administrators to update the Gmail signatures programatically. This helps you maintain a standard email signature for all users of your organisation but certain fields like employee’s name, email address, title or phone number can be variable.

The Email Settings API is used for creating or retrieving Gmail Signatures and it is only available for Google Apps for Work accounts. Also, you need to have administrator privileges to update company-wide Gmail settings such as signatures. You can use both plain text and rich HTML signatures.

1. Create a HTML file with the signature template. The entry tag contains the apps:property tag for signature.



    

2. Paste this in the code.gs file. If you would like to update the signature of all Google Apps users, use the Google Admin SDK (AdminDirectory.Users.list()) to get a list of all users and loop through the list.

function updateGmailSignature() {
    
  var email = "amit@labnol.org"; // User's email address
  var html = "Howdy! My email signature!"; // HTML signature
  
  setEmailSignature(email, html);
  
}

// Create an HTML encoded string
function createPayload_(html) {
  
  var str = html.replace(/&/g, "&")
  .replace(//g, ">")
  .replace(/'/g, "'")
  .replace(/"/g, """);
  
  return HtmlService.createHtmlOutputFromFile("template")
  .getContent()
  .replace("SIGNATURE", str)
  
}

function getAPI_(email) {
  
  var scope = 'https://apps-apis.google.com/a/feeds/emailsettings/2.0/',
      user = email.split("@");
  
  return Utilities.formatString('%s%s/%s/signature', scope, user[1], user[0]);  
  
}

function updateEmailSignature(email, html) {
  
  var response = UrlFetchApp.fetch(getAPI_(email), {
    method: "PUT",
    muteHttpExceptions: true,
    contentType: "application/atom+xml",
    payload: createPayload_(html),
    headers: {
      Authorization: 'Bearer ' + getSignaturesService_().getAccessToken()
    }    
  });
  
  if (response.getResponseCode() !== 200) {
    Logger.log("ERROR: " + response.getContentText());
  } else {
    Logger.log("Signature updated");
  }
   
}

You would also need to include the Oauth2 library with the scope set as https://apps-apis.google.com/a/feeds/emailsettings/2.0/ for the email signature service.

You can also append standard legal disclaimers to the email signatures with this technique. First retrieve the existing Gmail signature of a Google App user, append the text and update the signature.


/* Retrieve existing Gmail signature for any Google Apps user */

function getEmailSignature(email) {
  
  var response = UrlFetchApp.fetch(getAPI_(email), {
    method: "GET",
    muteHttpExceptions: true,
    headers: {
      Authorization: 'Bearer ' + getSignaturesService_().getAccessToken()
    }    
  });
  
  if (response.getResponseCode() !== 200) {
    Logger.log("ERROR: " + response.getContentText());
  }
  
  return response.getContentText();
  
}
Categories
Code

Send Emails with Attachments with Google Apps Script and Mandrill

Gmail sending limits are restricted especially when you are sending emails programatically as in the case of Mail Merge. You can only send a few hundred emails per day and then you’ve to wait the entire day for Google to reset your limit.

If you would like to send thousands of emails but without the daily restrictions, you can consider using an email sending service like Mandrill. You need a web domain and need to verify the ownership of that domain with Mandrill so that you are allowed to send emails via Mandrill. Once verified, you get an API key from Mandrill that you can use with Apps Script to send emails in bulk without restrictions.

Here’s a sample snippet that sends emails from Google Scripts via Mandrill. You also have to option to include file attachments in your emails and these files can be placed in a Google Drive folder. The sendEmail() method accepts both plain text and HTML Mail.

function sendEmail() {
  
  var MANDRILL_API_KEY = "<>";
  
  var files = [
    "<>",  
    "<>",  
    "<>"  
  ];

  var recipients = [
    {
      "email": "ctrlq+to@labnol.org",
      "name": "Amit Agarwal",
      "type": "to"
    }, {
      "email": "ctrlq+cc@labnol.org",
      "type": "cc"
    }, {
      "email": "ctrlq+bcc@gmail.com",
      "type": "bcc"
    }
  ];
  
  var attachments = [];
  
  for (var f in files) {
    var file = DriveApp.getFileById(files[f]);
    attachments.push({
      "type": file.getMimeType(),
      "name": file.getName(),
      "content": Utilities.base64Encode(file.getBlob().getBytes())
    });
  }
  
  var params = {
    "key": MANDRILL_API_KEY,
    "message": {
      "from_email": "<>",
      "from_name": "<>",
      "to": recipients,
      "attachments": attachments,
      "headers": {
        "Reply-To": "reply@example.com"
      },
      "subject": "Enter email subject",
      "text"   : "Enter email body in plain text",
      "html"   : "Enter HTML content with tags"
    }
  };

  var response = UrlFetchApp.fetch(
    "https://mandrillapp.com/api/1.0/messages/send.json", {
      'method': 'POST',
      'payload': JSON.stringify(params),
      'contentType': 'application/json'
    });

  Logger.log(response.getContentText());
}

It may take some time to build your mail domain reputation and hence the emails are queued and not sent instantly. Go to Mandrill Dashboard – Outbound Emails – Activity to see the current status of your sent Emails.

Mandrill Email Open Report

Also, it is no longer possible to send emails from generic addresses like @gmail.com or @outlook.com since Mandrill requires domain ownership verification to reduce spam emails.

Categories
Code

Create Google Contacts from Google Voice Mail

Google Voice sends you an email notification where there’s a missed call or a voicemail for you. The email includes the caller’s name, the caller’s phone number and, in case of voicemails, the text transcript of the message. The message also includes a link to download the Google Voice MP3.

The Google Scripts scans all your Google Voice emails, parses the content uses regex and creates a new Google contact. The transcript of the voice message is set to the notes field of the new Google contact.

Link: Save Google Voice Messages to Google Drive

// Written by Amit Agarwal www.ctrlq.org
// Email: amit@labnol.org

// Search Google Voicemail emails
function searchGoogleVoiceEmails() {

  var threads = GmailApp.search("from:voice-noreply@google.com", 0, 100);

  for (var t = 0; t < threads.length; t++) {

    var response = extractVoicemail_(threads[t].getMessages()[0]);

    if (response) {
      createContact_(response);
    }

  }
}

// Extract the caller's name, phone number and voice message transcription
function extractVoicemail_(msg) {

  var result = {
    "Message Date": msg.getDate(),
    "Message Subject": msg.getSubject(),
    "Message Body": msg.getPlainBody().replace(/<[^>]+>/g, "").replace(/\s+/g, " "),
    "Transcription": msg.getPlainBody()
  };

  var trans = /transcript:(.*)?play message/i.exec(result["Message Body"]);

  if (trans) result.Transcription = trans[1];

  //Voicemail from: John Q Public (202) 123-456 at 6:08 PM

  var match = /(Missed Call|Voicemail) from:([\*\+\s\w]+)([\d\-\s\(\)\+]*)? at [\d\:\s]+[ap]m/i.exec(result["Message Body"]);

  if (match) {
    result["Call Type"] = match[1];
    result["Contact Name"] = match[2].replace(/^\+/, "");
    result["Contact Number"] = match[3].replace(/^\+/, "");
  } else {
    return null;
  }

  return result;
}

// Create a new Google contact from Voicemail
function createContact_(result) {

  var contacts = ContactsApp.getContactsByPhone(result["Contact Number"], ContactsApp.Field.WORK_PHONE);

  if (contacts.length > 0) {

    return "Contact Exists";

  } else {

    var contact = ContactsApp.createContact(result["Contact Name"], result["Contact Name"], result["Call Type"]);

    contact.addPhone(ContactsApp.Field.WORK_PHONE, result["Contact Number"]);
    contact.setNotes(result["Transcription"]);

    return contact.getId();

  }
}
Categories
Code

Save Paypal Email Receipts in Google Spreadsheet

When you make a purchase through PayPal, the payment company sends you an email receipt with the details of the transaction. The Google script will scan your Gmail mailbox for all Paypal receipts, extracts the details from the message body using regex and saves them in a Google Spreadsheet for quick reference. The script extracts the transaction ID, the item purchased, the shipping cost and other details.

Also see: Sell Digital Goods with Paypal and Google Drive

function searchGmail() {
  
  var threads = GmailApp.search("from:paypal", 0, 10);
  
  var sheet = SpreadsheetApp.getActiveSheet();
  
  var header = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
  
  for (var t=0; t(.*?)<\/td>(.*?)<\/a><\/td>(.*?)<\/td>(.*?)<\/td>(.*?)<\/td><\/tr>/g.exec(html);  
    if (match) {
      result["Item #"] = match[1];
      result["Item Title"] = match[2];
      result["Quantity"] = match[3];
      result["Price"] = match[4];
      result["Subtotal"] = match[5];
    }
    
    match = /Shipping & Handling:\s+\(.*?\)(.*?)\s+Shipping/g.exec(body);  
    if (match) 
      result["Shipping and Handling"] = match[1];
    
    
    match = /Shipping Insurance.*?:(.*?)\s+Total:\s*(.*? .*?)\s+/g.exec(body);  
    if (match) {
      result["Shipping Insurance"] = match[1];
      result["Total"] = match[2];
    }
    
    match = /credit card statement as "(.*?)".*?purchased from:(.*?)\s+Receipt id:([\d\-]+)/gi.exec(body);  
    if (match) {
      result["Name in Statement"] = match[1];
      result["Purchased From"] = match[2];
      result["Receipt ID"] = match[3];
    }
    
    match = /international shipping.*?Total:(.*?)\s+.*credit card statement as "(.*?)"/gi.exec(body);  
    if (match) {
      result["International Shipping Total"] = match[1];
      result["International Name in Statement"] = match[2];
    }
    
  return result;
  
}
Categories
Code

Autoreply to Filtered Gmail Messages with Google Scripts

This Google Script, originally published by @rboyd, scans for lengthy emails in your Gmail mailbox (messages that have more than 100 words) and sends an auto-reply requesting the sender to resend the email after editing to less than 100 words. The long email is archived (moved away from the Inbox) after the auto-reply has been sent. You can set it up as a trigger to auto-run every 15 minutes.

Also see: Email Auto-responder for Gmail


// Credit: https://gist.github.com/rboyd/5027691

function deleteLongEmails() {

    var WORDLIMIT = 100;
    
    // Process only new emails in inbox that have not been read and receive in the last day
    var threads = GmailApp.search("to:me is:unread in:inbox newer_than:1d");

    for (var i = 0; i < threads.length; i++) {

        // Ignore email conversations
        if (threads[i].getMessageCount() == 1) {

            var msg = threads[i].getMessages()[0];
            var word_count = msg.getPlainBody().split(' ').length;

            if (word_count > WORDLIMIT) {
                msg.reply("Your email is too long. Please resend after editing it to less than 100 words.");
                GmailApp.moveThreadToArchive(threads[i]);
            }
        }
    }
}

Categories
Code

Create Draft Messages with Attachments using Gmail API

The GmailApp service of Google Apps Script doesn’t support drafts creation but you use the Gmail API to programatically create drafts in your mailbox.

You can create basic HTML drafts or you can also create drafts with attachments as shown in the following example. It can also be extended to create duplicate drafts in Gmail – you fetch the source draft using the Gmail API itself, parse the MIME message, edit the text or html if required, and save it as a new draft.

// Written by Amit Agarwal
// Web: ctrlq.org
// Email: amit@labnol.org

function createDraftMessage() {

  var attachments = ["File_ID_1", "File_ID_2"];

  var message = {
    to: {
      name: "Google Scripts",
      email: "amit@labnol.org"
    },
    from: {
      name: "Amit Agarwal",
      email: "amit@labnol.org"
    },
    body: {
      text: "Mr hänn is schon lang nümme g'she.",
      html: "Mr hänn is schon lang nümme g'she."
    },
    subject: "ctrlq, tech à la carte",
    files: getAttachments_(attachments)
  };

  // Compose Gmail message and send immediately
  callGmailAPI_(message);

}


function callGmailAPI_(message) {

  var payload = createMimeMessage_(message);

  var response = UrlFetchApp.fetch(
    "https://www.googleapis.com/upload/gmail/v1/users/me/drafts?uploadType=media", {
      method: "POST",
      headers: {
        "Authorization": "Bearer " + ScriptApp.getOAuthToken(),
        "Content-Type": "message/rfc822",
      },
      muteHttpExceptions: true,
      payload: payload
    });

  Logger.log(response.getResponseCode());
  Logger.log(response.getContentText());

}

// UTF-8 characters in names and subject
function encode_(subject) {
  var enc_subject = Utilities.base64Encode(subject, Utilities.Charset.UTF_8);
  return '=?utf-8?B?' + enc_subject + '?=';
}

// Insert file attachments from Google Drive
function getAttachments_(ids) {
  var att = [];
  for (var i in ids) {
    var file = DriveApp.getFileById(ids[i]);
    att.push({
      mimeType: file.getMimeType(),
      fileName: file.getName(),
      bytes: Utilities.base64Encode(file.getBlob().getBytes())
    });
  }
  return att;
}

// Create a MIME message that complies with RFC 2822
function createMimeMessage_(msg) {

  var nl = "\n";
  var boundary = "__ctrlq_dot_org__";

  var mimeBody = [

    "MIME-Version: 1.0",
    "To: "      + encode_(msg.to.name) + "<" + msg.to.email + ">",
    "From: "    + encode_(msg.from.name) + "<" + msg.from.email + ">",
    "Subject: " + encode_(msg.subject), // takes care of accented characters

    "Content-Type: multipart/alternative; boundary=" + boundary + nl,
    "--" + boundary,

    "Content-Type: text/plain; charset=UTF-8",
    "Content-Transfer-Encoding: base64" + nl,
    Utilities.base64Encode(msg.body.text, Utilities.Charset.UTF_8) + nl,
    "--" + boundary,

    "Content-Type: text/html; charset=UTF-8",
    "Content-Transfer-Encoding: base64" + nl,
    Utilities.base64Encode(msg.body.html, Utilities.Charset.UTF_8) + nl

  ];

  for (var i = 0; i < msg.files.length; i++) {

    var attachment = [
      "--" + boundary,
      "Content-Type: " + msg.files[i].mimeType + '; name="' + msg.files[i].fileName + '"',
      'Content-Disposition: attachment; filename="' + msg.files[i].fileName + '"',
      "Content-Transfer-Encoding: base64" + nl,
      msg.files[i].bytes
    ];

    mimeBody.push(attachment.join(nl));

  }

  mimeBody.push("--" + boundary + "--");

  return mimeBody.join(nl);

}
Categories
Code

Gmail Tracking with Google Analytics and Apps Script

This Google Script inserts the Google Analytics code in your outgoing email messages to help you track delivery and opens. When the email is opened, the tracking image (__utm.gif) – a 1×1 pixels transparent GIF file – is downloaded on the visitor’s computer and the visit gets logged as an event in Google Analytics.

You can use this technique to track email newsletters and other platforms where the standard JavaScript based Analytics code cannot be added.

Google Analytics tracking URL supports several parameters but this Apps Script function only uses only the essential ones that are required for logging an event (utmt is set as event and not page which is the default) into Google Analytics.

Also see: Tracking Email Clicks with Analytics


/* Written by Amit Agarwal */

function getTrackingGIF(account, email, subject) {

  var imgURL = "https://ssl.google-analytics.com/collect?"
    + "v=1&t=event"
    + "&tid=" + account
    + "&z="   + Math.round((new Date()).getTime()/1000).toString()
    + "&cid=" + Utilities.getUuid()
    + "&ec=" + encodeURIComponent("Email Open")
    + "&ea=" + encodeURIComponent(subject.replace(/'/g, ""))
    + "&el=" + encodeURIComponent(email);

  return "";

}

function sendMail(draftId, analyticsID) {

  var draft = GmailApp.getMessageById(draftId);
  
  var body = draft.getBody();
  body += getTrackingGIF(analyticsID, draft.getTo(), draft.getSubject());  
  
  GmailApp.sendEmail(draft.getTo(), draft.getSubject(), email, {
    htmlBody: email, cc: draft.getCc(), bcc: draft.getBcc()
  });          
}

Categories
Code

Google Apps Script Library for Gmail API

The Gmail API offers extra functionality not available inside the Gmail service of Google Apps Script. For instance, the ability to purge Gmail folders is available in Apps Script but there’s no option to empty the trash. This can be easily done with the Gmail API. It also support draft creation that is used in Gmail Merge.

Spencer has published a wrapper library that makes it easy to use the Gmail API inside Google Apps Script.

/**
* Google Apps Script Library for the gmail API
*
* Credit: +SpencerEaston 
* Source: https://drive.google.com/folderview?id=0B_j9_-NbJQQDcUNEckk2WGhETms
* 
* OAuth2 Scopes
* https://mail.google.com/
* https://www.googleapis.com/auth/gmail.compose
* https://www.googleapis.com/auth/gmail.insert
* https://www.googleapis.com/auth/gmail.labels
* https://www.googleapis.com/auth/gmail.modify
* https://www.googleapis.com/auth/gmail.readonly
* https://www.googleapis.com/auth/gmail.send
*/

var BASEURL_="https://www.googleapis.com/gmail/v1/users/";
var tokenService_;

/*
* Stores the function passed that is invoked to get a OAuth2 token;
* @param {function} service The function used to get the OAuth2 token;
*
*/
function setTokenService(service){
  tokenService_ = service;
}

/*
* Returns an OAuth2 token from your TokenService as a test
* @return {string} An OAuth2 token
*
*/
function testTokenService(){
 return tokenService_();
}

/**
 * Performs a Fetch
 * @param {string} url The endpoint for the URL with parameters
 * @param {Object.} options Options to override default fetch options
 * @returns {Object.} the fetch results
 * @private
 */
function CALL_(path,options){
  var fetchOptions = {method:"",muteHttpExceptions:true, contentType:"application/json", headers:{Authorization:"Bearer "+tokenService_()}}
  var url = BASEURL_ + path;
  
  for(option in options){
    fetchOptions[option] = options[option];
  }
  
  var response = UrlFetchApp.fetch(url, fetchOptions)
  if(response.getResponseCode() != 200){
    throw new Error(response.getContentText())
  }else{
    return JSON.parse(response.getContentText());
  }
}

/**
 * Performs a Fetch and accumulation using pageToken parameter of the returned results
 * @param {string} url The endpoint for the URL with parameters
 * @param {Object.} options Options to override default fetch options
 * @param {string} returnParamPath The path of the parameter to be accumulated
 * @returns {Array.Object.} An array of objects
 * @private
 */
function CALLPAGE_(path,options, returnParamPath){
  var fetchOptions = {method:"",muteHttpExceptions:true, contentType:"application/json", headers:{Authorization:"Bearer "+tokenService_()}}
  for(option in options){
    fetchOptions[option] = options[option];
  }
  var url = BASEURL_ + path;
  var returnArray = [];
  var nextPageToken;
  do{
    if(nextPageToken){
      url += "?pageToken=" + nextPageToken;
    }
    var results = UrlFetchApp.fetch(url, fetchOptions);
    if(results.getResponseCode() != 200){
      throw new Error(results.getContentText());
    }else{
      var resp = JSON.parse(results.getContentText())
      nextPageToken = resp.nextPageToken;
      returnArray  = returnArray.concat(resp[returnParamPath])
    }
  }while(nextPageToken);
  return returnArray;
}

/**
 * Builds a complete URL from a base URL and a map of URL parameters.
 * @param {string} url The base URL.
 * @param {Object.} params The URL parameters and values.
 * @returns {string} The complete URL.
 * @private
 */
function buildUrl_(url, params) {
  var params = params || {}; //allow for NULL options
  var paramString = Object.keys(params).map(function(key) {
    return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
  }).join('&');
  return url + (url.indexOf('?') >= 0 ? '&' : '?') + paramString;
}

/**
* Gets the current user's Gmail profile.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned ProfileResource object
*/
function usersGetProfile(userId,options){
  var path = buildUrl_(""+userId+"/profile",options);
  var callOptions = {method:"GET"};
  var ProfileResource = CALL_(path,callOptions);
  return ProfileResource;
}

/**
* Stop receiving push notifications for the given user mailbox.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {object} options Keypair of all optional parameters for this call
*/
function usersStop(userId,options){
  var path = buildUrl_(""+userId+"/stop",options);
  var callOptions = {method:"POST"};
  var removeResource = CALL_(path,callOptions);
  return removeResource;
}

/**
* Set up or update a push notification watch on the given user mailbox.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {object} WatchRequestResource An object containing the WatchRequestResource for this method
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned WatchResponseResource object
*/
function usersWatch(userId,WatchRequestResource,options){
  var path = buildUrl_(""+userId+"/watch",options);
  var callOptions = {method:"POST",payload:JSON.stringify(WatchRequestResource)};
  var WatchResponseResource = CALL_(path,callOptions);
  return WatchResponseResource;
}

/**
* Creates a new draft with the DRAFT label.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {object} DraftResource An object containing the DraftResource for this method
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned DraftResource object
*/
function usersDraftsCreate(userId,DraftResource,options){
  var path = buildUrl_(""+userId+"/drafts",options);
  var callOptions = {method:"POST",payload:JSON.stringify(DraftResource)};
  var DraftResource = CALL_(path,callOptions);
  return DraftResource;
}

/**
* Immediately and permanently deletes the specified draft. Does not simply trash it.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {string} id The ID of the draft to delete.
* @param {object} options Keypair of all optional parameters for this call
*/
function usersDraftsDelete(userId,id,options){
  var path = buildUrl_(""+userId+"/drafts/"+id,options);
  var callOptions = {method:"DELETE"};
  var removeResource = CALL_(path,callOptions);
  return removeResource;
}

/**
* Gets the specified draft.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {string} id The ID of the draft to retrieve.
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned DraftResource object
*/
function usersDraftsGet(userId,id,options){
  var path = buildUrl_(""+userId+"/drafts/"+id,options);
  var callOptions = {method:"GET"};
  var DraftResource = CALL_(path,callOptions);
  return DraftResource;
}

/**
* Lists the drafts in the user's mailbox.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned ListDraftsResponseResource object
*/
function usersDraftsList(userId,options){
  var path = buildUrl_(""+userId+"/drafts",options);
  var callOptions = {method:"GET"};
  var ListDraftsResponseItems = CALLPAGE_(path,callOptions,"items");
  return ListDraftsResponseItems;
}

/**
* Sends the specified, existing draft to the recipients in the To, Cc, and Bcc headers.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {object} DraftResource An object containing the DraftResource for this method
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned MessageResource object
*/
function usersDraftsSend(userId,DraftResource,options){
  var path = buildUrl_(""+userId+"/drafts/send",options);
  var callOptions = {method:"POST",payload:JSON.stringify(DraftResource)};
  var MessageResource = CALL_(path,callOptions);
  return MessageResource;
}

/**
* Replaces a draft's content.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {string} id The ID of the draft to update.
* @param {object} DraftResource An object containing the DraftResource for this method
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned DraftResource object
*/
function usersDraftsUpdate(userId,id,DraftResource,options){
  var path = buildUrl_(""+userId+"/drafts/"+id,options);
  var callOptions = {method:"PUT",payload:JSON.stringify(DraftResource)};
  var DraftResource = CALL_(path,callOptions);
  return DraftResource;
}

/**
* Lists the history of all changes to the given mailbox. History results are returned in chronological order (increasing historyId).
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned ListHistoryResponseResource object
*/
function usersHistoryList(userId,options){
  var path = buildUrl_(""+userId+"/history",options);
  var callOptions = {method:"GET"};
  var ListHistoryResponseItems = CALLPAGE_(path,callOptions,"items");
  return ListHistoryResponseItems;
}

/**
* Creates a new label.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {object} LabelResource An object containing the LabelResource for this method
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned LabelResource object
*/
function usersLabelsCreate(userId,LabelResource,options){
  var path = buildUrl_(""+userId+"/labels",options);
  var callOptions = {method:"POST",payload:JSON.stringify(LabelResource)};
  var LabelResource = CALL_(path,callOptions);
  return LabelResource;
}

/**
* Immediately and permanently deletes the specified label and removes it from any messages and threads that it is applied to.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {string} id The ID of the label to delete.
* @param {object} options Keypair of all optional parameters for this call
*/
function usersLabelsDelete(userId,id,options){
  var path = buildUrl_(""+userId+"/labels/"+id,options);
  var callOptions = {method:"DELETE"};
  var removeResource = CALL_(path,callOptions);
  return removeResource;
}

/**
* Gets the specified label.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {string} id The ID of the label to retrieve.
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned LabelResource object
*/
function usersLabelsGet(userId,id,options){
  var path = buildUrl_(""+userId+"/labels/"+id,options);
  var callOptions = {method:"GET"};
  var LabelResource = CALL_(path,callOptions);
  return LabelResource;
}

/**
* Lists all labels in the user's mailbox.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned ListLabelsResponseResource object
*/
function usersLabelsList(userId,options){
  var path = buildUrl_(""+userId+"/labels",options);
  var callOptions = {method:"GET"};
  var ListLabelsResponseResource = CALL_(path,callOptions);
  return ListLabelsResponseResource;
}

/**
* Updates the specified label. This method supports patch semantics.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {string} id The ID of the label to update.
* @param {object} LabelResource An object containing the LabelResource for this method
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned LabelResource object
*/
function usersLabelsPatch(userId,id,LabelResource,options){
  var path = buildUrl_(""+userId+"/labels/"+id,options);
  var callOptions = {method:"PATCH",payload:JSON.stringify(LabelResource)};
  var LabelResource = CALL_(path,callOptions);
  return LabelResource;
}

/**
* Updates the specified label.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {string} id The ID of the label to update.
* @param {object} LabelResource An object containing the LabelResource for this method
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned LabelResource object
*/
function usersLabelsUpdate(userId,id,LabelResource,options){
  var path = buildUrl_(""+userId+"/labels/"+id,options);
  var callOptions = {method:"PUT",payload:JSON.stringify(LabelResource)};
  var LabelResource = CALL_(path,callOptions);
  return LabelResource;
}

/**
* Immediately and permanently deletes the specified message. This operation cannot be undone. Prefer messages.trash instead.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {string} id The ID of the message to delete.
* @param {object} options Keypair of all optional parameters for this call
*/
function usersMessagesDelete(userId,id,options){
  var path = buildUrl_(""+userId+"/messages/"+id,options);
  var callOptions = {method:"DELETE"};
  var removeResource = CALL_(path,callOptions);
  return removeResource;
}

/**
* Gets the specified message.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {string} id The ID of the message to retrieve.
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned MessageResource object
*/
function usersMessagesGet(userId,id,options){
  var path = buildUrl_(""+userId+"/messages/"+id,options);
  var callOptions = {method:"GET"};
  var MessageResource = CALL_(path,callOptions);
  return MessageResource;
}

/**
* Imports a message into only this user's mailbox, with standard email delivery scanning and classification similar to receiving via SMTP. Does not send a message.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {object} MessageResource An object containing the MessageResource for this method
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned MessageResource object
*/
function usersMessagesImport(userId,MessageResource,options){
  var path = buildUrl_(""+userId+"/messages/import",options);
  var callOptions = {method:"POST",payload:JSON.stringify(MessageResource)};
  var MessageResource = CALL_(path,callOptions);
  return MessageResource;
}

/**
* Directly inserts a message into only this user's mailbox similar to IMAP APPEND, bypassing most scanning and classification. Does not send a message.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {object} MessageResource An object containing the MessageResource for this method
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned MessageResource object
*/
function usersMessagesInsert(userId,MessageResource,options){
  var path = buildUrl_(""+userId+"/messages",options);
  var callOptions = {method:"POST",payload:JSON.stringify(MessageResource)};
  var MessageResource = CALL_(path,callOptions);
  return MessageResource;
}

/**
* Lists the messages in the user's mailbox.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned ListMessagesResponseResource object
*/
function usersMessagesList(userId,options){
  var path = buildUrl_(""+userId+"/messages",options);
  var callOptions = {method:"GET"};
  var ListMessagesResponseItems = CALLPAGE_(path,callOptions,"items");
  return ListMessagesResponseItems;
}

/**
* Modifies the labels on the specified message.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {string} id The ID of the message to modify.
* @param {object} ModifyMessageRequestResource An object containing the ModifyMessageRequestResource for this method
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned MessageResource object
*/
function usersMessagesModify(userId,id,ModifyMessageRequestResource,options){
  var path = buildUrl_(""+userId+"/messages/"+id+"/modify",options);
  var callOptions = {method:"POST",payload:JSON.stringify(ModifyMessageRequestResource)};
  var MessageResource = CALL_(path,callOptions);
  return MessageResource;
}

/**
* Sends the specified message to the recipients in the To, Cc, and Bcc headers.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {object} MessageResource An object containing the MessageResource for this method
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned MessageResource object
*/
function usersMessagesSend(userId,MessageResource,options){
  var path = buildUrl_(""+userId+"/messages/send",options);
  var callOptions = {method:"POST",payload:JSON.stringify(MessageResource)};
  var MessageResource = CALL_(path,callOptions);
  return MessageResource;
}

/**
* Moves the specified message to the trash.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {string} id The ID of the message to Trash.
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned MessageResource object
*/
function usersMessagesTrash(userId,id,options){
  var path = buildUrl_(""+userId+"/messages/"+id+"/trash",options);
  var callOptions = {method:"POST"};
  var MessageResource = CALL_(path,callOptions);
  return MessageResource;
}

/**
* Removes the specified message from the trash.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {string} id The ID of the message to remove from Trash.
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned MessageResource object
*/
function usersMessagesUntrash(userId,id,options){
  var path = buildUrl_(""+userId+"/messages/"+id+"/untrash",options);
  var callOptions = {method:"POST"};
  var MessageResource = CALL_(path,callOptions);
  return MessageResource;
}

/**
* Gets the specified message attachment.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {string} messageId The ID of the message containing the attachment.
* @param {string} id The ID of the attachment.
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned MessagePartBodyResource object
*/
function usersMessagesAttachmentsGet(userId,messageId,id,options){
  var path = buildUrl_(""+userId+"/messages/"+messageId+"/attachments/"+id,options);
  var callOptions = {method:"GET"};
  var MessagePartBodyResource = CALL_(path,callOptions);
  return MessagePartBodyResource;
}

/**
* Immediately and permanently deletes the specified thread. This operation cannot be undone. Prefer threads.trash instead.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {string} id ID of the Thread to delete.
* @param {object} options Keypair of all optional parameters for this call
*/
function usersThreadsDelete(userId,id,options){
  var path = buildUrl_(""+userId+"/threads/"+id,options);
  var callOptions = {method:"DELETE"};
  var removeResource = CALL_(path,callOptions);
  return removeResource;
}

/**
* Gets the specified thread.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {string} id The ID of the thread to retrieve.
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned ThreadResource object
*/
function usersThreadsGet(userId,id,options){
  var path = buildUrl_(""+userId+"/threads/"+id,options);
  var callOptions = {method:"GET"};
  var ThreadResource = CALL_(path,callOptions);
  return ThreadResource;
}

/**
* Lists the threads in the user's mailbox.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned ListThreadsResponseResource object
*/
function usersThreadsList(userId,options){
  var path = buildUrl_(""+userId+"/threads",options);
  var callOptions = {method:"GET"};
  var ListThreadsResponseItems = CALLPAGE_(path,callOptions,"items");
  return ListThreadsResponseItems;
}

/**
* Modifies the labels applied to the thread. This applies to all messages in the thread.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {string} id The ID of the thread to modify.
* @param {object} ModifyThreadRequestResource An object containing the ModifyThreadRequestResource for this method
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned ThreadResource object
*/
function usersThreadsModify(userId,id,ModifyThreadRequestResource,options){
  var path = buildUrl_(""+userId+"/threads/"+id+"/modify",options);
  var callOptions = {method:"POST",payload:JSON.stringify(ModifyThreadRequestResource)};
  var ThreadResource = CALL_(path,callOptions);
  return ThreadResource;
}

/**
* Moves the specified thread to the trash.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {string} id The ID of the thread to Trash.
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned ThreadResource object
*/
function usersThreadsTrash(userId,id,options){
  var path = buildUrl_(""+userId+"/threads/"+id+"/trash",options);
  var callOptions = {method:"POST"};
  var ThreadResource = CALL_(path,callOptions);
  return ThreadResource;
}

/**
* Removes the specified thread from the trash.
*
* @param {string} userId The user's email address. The special value me can be used to indicate the authenticated user.
* @param {string} id The ID of the thread to remove from Trash.
* @param {object} options Keypair of all optional parameters for this call
* @return {object} The returned ThreadResource object
*/
function usersThreadsUntrash(userId,id,options){
  var path = buildUrl_(""+userId+"/threads/"+id+"/untrash",options);
  var callOptions = {method:"POST"};
  var ThreadResource = CALL_(path,callOptions);
  return ThreadResource;
}

Categories
Code

Parse Gmail Messages to Extract Data

A mailbox has 1000s of email messages sent though legacy contact forms that contain data like the name, email and address of the senders. The business owner would like to parse these email messages, extract the relevant bits and save them to a Google Spreadsheet.

The script can be run in batches of 100 thread, to avoid exceeding the time limit, and the parsing rules can be written in Regular Expressions. Snippet by @Ferrari.

The code can extended to parse emails and extract other structured data from the message body including events information, order details, travel itineraries, shipping & tracking information, customer records and more.

/* Based on https://gist.github.com/Ferrari/9678772 */

function parseEmailMessages(start) {

  start = start || 0;

  var threads = GmailApp.getInboxThreads(start, 100);
  var sheet = SpreadsheetApp.getActiveSheet();

  for (var i = 0; i < threads.length; i++) {

    // Get the first email message of a threads
    var tmp,
      message = threads[i].getMessages()[0],
      subject = message.getSubject(),
      content = message.getPlainBody();

    // Get the plain text body of the email message
    // You may also use getRawContent() for parsing HTML

    // Implement Parsing rules using regular expressions
    if (content) {

      tmp = content.match(/Name:\s*([A-Za-z0-9\s]+)(\r?\n)/);
      var username = (tmp && tmp[1]) ? tmp[1].trim() : 'No username';

      tmp = content.match(/Email:\s*([A-Za-z0-9@.]+)/);
      var email = (tmp && tmp[1]) ? tmp[1].trim() : 'No email';

      tmp = content.match(/Comments:\s*([\s\S]+)/);
      var comment = (tmp && tmp[1]) ? tmp[1] : 'No comment';

      sheet.appendRow([username, email, subject, comment]);

    } // End if

  } // End for loop
}

Categories
Code

Send Email Messages from Gmail to Slack Channel

The Google Apps Script, courtesy @andrewwilson, uses incoming webhooks to post email messages from your Gmail mailbox into Slack channel. The email body is extracted using the GmailApp service and posted to Slack through an HTTP request with a JSON payload. You need to create a ToSlack gmail label and apply this label to message that you wish to post to your Slack channel.

A time-based trigger may also be added for sending labeled message from Gmail to Slack in near real-time.


/* Credit: gist.github.com/andrewmwilson */

function sendEmailsToSlack() {
    var label = GmailApp.getUserLabelByName('ToSlack');
    var messages = [];
    var threads = label.getThreads();
  
    for (var i = 0; i < threads.length; i++) {
        messages = messages.concat(threads[i].getMessages())
    }

    for (var i = 0; i < messages.length; i++) {
        var message = messages[i];
        Logger.log(message);

        var output = '*New Email*';
        output += '\n*from:* ' + message.getFrom();
        output += '\n*to:* ' + message.getTo();
        output += '\n*cc:* ' + message.getCc();
        output += '\n*date:* ' + message.getDate();
        output += '\n*subject:* ' + message.getSubject();
        output += '\n*body:* ' + message.getPlainBody();
        Logger.log(output);

        var payload = {
            'username': 'gmail-bot',
            'text': output,
            'channel' : '#some-channel',
            'icon_emoji': ':hear_no_evil:',
        };

        var options = {
            'method' : 'post',
            'payload' : Utilities.jsonStringify(payload),
        };

        // replace this with your own Slack webhook URL
        // https://crowdscores.slack.com/services
        var webhookUrl = 'https://hooks.slack.com/services/****/****/****';
        UrlFetchApp.fetch(webhookUrl, options);
   }

   // remove the label from these threads so we don't send them to
   // slack again next time the script is run
   label.removeFromThreads(threads);
}
Categories
Code

Save Gmail Messages to a Google Spreadsheet

The Google Script from @oshliaer will save the body of email messages from Gmail to the currently active worksheet inside Google Spreadsheet. You need to specify the Gmail search query and the sheet ID where the matching messages are to be exported. It saves the text content of the message sans any HTML tags or images.

To get started, paste the code in the script editor of a Google Spreadsheet and run SaveEmail from the Run menu.

Also see: Save Gmail Attachment to Google Drive


var SEARCH_QUERY = "label:inbox is:unread to:me";
 
// Credit: https://gist.github.com/oshliaer/70e04a67f1f5fd96a708

function getEmails_(q) {
    var emails = [];
    var threads = GmailApp.search(q);
    for (var i in threads) {
        var msgs = threads[i].getMessages();
        for (var j in msgs) {
            emails.push([msgs[j].getBody().replace(/<.*?>/g, '\n')
                .replace(/^\s*\n/gm, '').replace(/^\s*/gm, '').replace(/\s*\n/gm, '\n')
            ]);
        }
    }
    return emails;
}

function appendData_(sheet, array2d) {
    sheet.getRange(sheet.getLastRow() + 1, 1, array2d.length, array2d[0].length).setValues(array2d);
}

function saveEmails() {
    var array2d = getEmails_(SEARCH_QUERY);
    if (array2d) {
        appendData_(SpreadsheetApp.getActiveSheet(), array2d);
    }
}

Categories
Code

Find Location & IP Address of Email Sender

If you would like to know the IP Address and Location of email sender in Gmail, Google Scripts can help. It extracts the raw headers of the email message and then pull the IP address of the sender using regular expressions.

The IP address is then passed to a free GeoIP service that gives the geographic location of an IP address. The JSON response may contain the country name, city and even the zip code in some cases.

Also see: Find Person by Email Address


function getEmailCountry(messageID) {
  
  var message = GmailApp.getMessageById(messageID);
  var raw = message.getRawContent().split("\n");
  
  for (var j=0; j < raw.length; j++) {
    
    var ipaddress = /Received:\s+from.*?\[((?:[0-9]{1,3}\.){3}[0-9]{1,3})\]/i.exec(raw[j]); 
    
    if (ipaddress != null) {
      
      var json, reply = {country_name: "Unknown"};
      
      try {          
        json = UrlFetchApp.fetch("http://freegeoip.net/json/" + ipaddress[1]);
        reply = JSON.parse(json);
      } catch (e) {}
      
      Logger.log("The email is sent from " + ipaddress[1] + " in " + reply.country_name);        
      break;
      
    }
    
  }
}
Categories
Code

Archive Old Email Messages in Gmail Automatically

The Google Script will archive all email threads in a specific Gmail label that have been inactive for more than a week. It takes the date of the last message in a thread and if it is older than a week, the thread is archived. The current labels is removed as well.

function ArchiveOldEmails(gmailLabelName){
  
  var gmailLabel = GmailApp.getUserLabelByName(gmailLabelName);
  
  var ONE_WEEK = 60 * 60 * 24 * 7 * 1000;
  
  var threads = label.getThreads();
  var now     = new Date();
  
  for (var j=0; j ONE_WEEK){
      threads[j].moveToArchive().removeLabel(gmailLabel);
    }
  }
  
}
Categories
Code

Save Gmail Messages as Google Documents

The Google Script will save any Gmail message (or thread) in your Google Drive as a native Google Document with proper formatting. Unlike the Save Gmail as PDF script that downloads the email threads as PDF files in your Google Drive, this Google Script create a Google Docs file for your Gmail message and these do not count against the storage quota.


function saveGmail(msgID) {
    
  // Based on Drive Scoop
  // Available at https://github.com/google/gfw-deployments

  var message = GmailApp.getMessageById(msgID);
  
  // Grab the message's headers.
  var from = message.getFrom();
  var subject = message.getSubject();
  var to = message.getTo();
  var cc = message.getCc();
  var date = message.getDate();
  var body = message.getBody();
  
  // Begin creating a doc.
  var document = DocumentApp.create(subject);  
  var document_title = document.appendParagraph(subject);  
  document_title.setHeading(DocumentApp.ParagraphHeading.HEADING1);
  
  var style = {};
  style[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = (DocumentApp.HorizontalAlignment.CENTER);
  document_title.setAttributes(style);
  
  var headers_heading = (document.appendParagraph("Gmail Message Headers"));
  headers_heading.setHeading(DocumentApp.ParagraphHeading.HEADING2);
  
  AddGmailHeaderToDoc(document, "From", from);
  AddGmailHeaderToDoc(document, "To", to);
  AddGmailHeaderToDoc(document, "Cc", cc);
  AddGmailHeaderToDoc(document, "Date", date);
  AddGmailHeaderToDoc(document, "Subject", subject);
  
  var body_heading = (
    document.appendParagraph("Body (without Markup)"));
  body_heading.setHeading(DocumentApp.ParagraphHeading.HEADING2);
  
  var sanitized_body = body.replace(/<\/div>/, "\r\r");
  sanitized_body = sanitized_body.replace(//g, "\r");
  sanitized_body = sanitized_body.replace(/<\/p>/g, "\r\r");
  sanitized_body = sanitized_body.replace(/<.*?>/g, "");
  sanitized_body = sanitized_body.replace(/'/g, "'");
  sanitized_body = sanitized_body.replace(/"/g, '"');
  sanitized_body = sanitized_body.replace(/&/g, "&");
  sanitized_body = sanitized_body.replace(/\r\r\r/g, "\r\r");
  
  var paragraph = document.appendParagraph(sanitized_body);
  
  document.saveAndClose();
  
  return document.getUrl();
  
}

function AddGmailHeaderToDoc(document, header_name, header_value) {
  if (header_value === "") return;
  var paragraph = document.appendParagraph("");
  paragraph.setIndentStart(72.0);
  paragraph.setIndentFirstLine(36.0);
  paragraph.setSpacingBefore(0.0);
  paragraph.setSpacingAfter(0.0);    
  var name = paragraph.appendText(header_name + ": ");
  name.setBold(false);
  var value = paragraph.appendText(header_value);
  value.setBold(true);
}
Categories
Code

Save Gmail Drafts with Google Apps Script

The Mail Merge Add-on uses the new Gmail API to create and save draft messages Gmail programmatically, something which cannot be done using the standard GmailApp or MailApp service of Google Apps Script. The Gmail API can create draft messages in rich-text which you can generate using the HTML Mail app.

You’ll have to enable the Gmail API under the Google Services console for this code to work. Credit @oshliaer.


// Credit: https://gist.github.com/oshliaer/8db2131bf7357247bc2b

function createHTMLDraftInGmail() {

  var forScope = GmailApp.getInboxUnreadCount(); // needed for auth scope
  var htmlBody = "

Hello, I am an HTML message


"; var raw = 'From: Me \r\n' + 'To: You \r\n' + 'Subject: Save Draft Message\r\n' + 'Content-Type: text/html; charset=UTF-8\r\n' + '\r\n' + htmlBody; var draftBody = Utilities.base64Encode(raw, Utilities.Charset.UTF_8).replace(/\//g,'_').replace(/\+/g,'-'); var params = { method : "post", contentType : "application/json", headers : {"Authorization": "Bearer " + ScriptApp.getOAuthToken()}, muteHttpExceptions:true, payload:JSON.stringify({ "message": { "raw": draftBody } }) }; var resp = UrlFetchApp.fetch("https://www.googleapis.com/gmail/v1/users/me/drafts", params); Logger.log(resp.getContentText()); }
Categories
Code

Gmail to Evernote with Google Scripts

The Gmail to Evernote program will automatically send your Gmail message to your Evernote account using Google Scripts. It reads the various parameters from a Google sheet (like the default tag name and Evernote notebook name) and forwards the matching email threads to Evernote using the GmailApp service.

    var sheet = SpreadsheetApp.getActiveSpreadsheet();
    
    var params = sheet.getRange("D3:D7").getValues();
    
    // Gmail Label name to Monitor
    var label = GmailApp.getLabelByName(params[1][0].trim().replace(/\s+/g, "-"));
    
    var threads = label.getThreads();
    
    for (var t in threads) {
      
      var messages = threads[t].getMessages();
      
        // Forward the latest message in the thread to Evernote
        var message = messages[messages.length-1];
        
        // Append the Evernote notebook and tag to the subject
        var subject = [message.getSubject(), params[2][0], params[3][0]].join(" ");
        
        try {
          message.forward(params[0][0], {subject: subject});
        } catch (f) {
          Logger.log(f.toString());
        }
        
        // Trash the message after forwarding to Evernote
        if (params[2][0].match(/y/i)) {
          threads[t].moveToTrash();
        } else {
          threads[t].removeLabel(label);
        }
      
    }

Categories
Code

Automatically Unsubscribe from Newsletters in Gmail

The bulk email messages in Gmail have an “unsubscribe” link that you can click to remove your email address from the mailing list. However, if you wish to unsubscribe from multiple email newsletters in one go, you can use the Gmail Unsubscriber script.

Apply the label “Unsubscribe” to all the emails from which you wish to unsubscribe and call this apps script. It extracts the unsubscribe link from the raw message header and fetches the link to unsubscribe you. Press Cmd+Enter to see the list of all mailing lists from which you have been unsubscribed.

Joshua Peak has done the groundwork but it only works if the email message contains the unsubscribe link in the List Unsubscribe header.

function main() {

  var label = GmailApp.getUserLabelByName("Unsubscribe");
  var threads = label.getThreads();

  threads.forEach(function(thread) {

    var message = thread.getMessages()[0];
    var value = message.getRawContent()
                       .match(/^List-Unsubscribe: ((.|\r\n\s)+)\r\n/m)[1];

    if (value) {
      var url = value.match(/<(https?:\/\/[^>]+)>/)[1];
      if (url) {
        var status = UrlFetchApp.fetch(url).getResponseCode();
        Logger.log("Unsubscribe " + status + " " + url);
      }
    }

    thread.removeLabel(label);
  });
}

I extended this to unsubscribe from mailing lists where the link may be in the message body or messages that may require your to unsubscribe by sending an email to a specific email address.


function Gmail_Unsubscribe() {
  
  var threads = GmailApp.search("label:Unsubscribe");
  
  for (var t in threads)  {
    
    var message = threads[t].getMessages()[0];
    
    var raw = message.getRawContent();
    
    // Search for the List Unsubscribe header in the Email Header
    var urls = raw.match(/^list\-unsubscribe:(.|\r\n\s)+<(https?:\/\/[^>]+)>/im);

    // thanks josh/list-unsubscribe @github

    if (urls) {
      
      // Click the unsubscribe link
      UrlFetchApp.fetch(urls[2], {muteHttpExceptions: true});
      
    } else {
      
      // Find the unsubscribe email
      urls = raw.match(/^list\-unsubscribe:(.|\r\n\s)+]+)>/im);
      
      if (urls) {
        
        // Send blank email to unsubscribe
        GmailApp.sendEmail(urls[2], "Unsubscribe", "Unsubscribe");
        
      } else {
        
        // Get the HTML of the email 
        var body = message.getBody().replace(/\s/g, "");
        
        // Regex to find all hyperlinks
        var hrefs = new RegExp(/]*href=["'](https?:\/\/[^"']+)["'][^>]*>(.*?)<\/a>/gi);
        
        // Iterate through all hyperlinks inside the message
        while ( urls = hrefs.exec(body) ) {
          
          // Does the anchor text or hyperlink contain words like unusbcribe or optout
          if (urls[1].match(/unsubscribe|optout|opt\-out|remove/i)
              || urls[2].match(/unsubscribe|optout|opt\-out|remove/i)) {
            
            // Click the unsubscribe link
            UrlFetchApp.fetch(urls[1], {muteHttpExceptions: true});
            break;
            
          }
        }
      } 
    }
  }
}
Categories
Code

Publish Email Messages to Slack with Google Scripts

You can easily publish important email messages coming into your Gmail to a Slack channel using Google Apps Script. Your Slack channel has a Webhook URL and you can send a POST HTTP request to this URL with the email message in JSON format as the payload parameter.

You can archive the message, or apply a specific Gmail label, to prevent duplicate posts in Slack for the same message. Credit: @matsuyoro


var postUrl = "https://hooks.slack.com/services/xxxxxxxxxxxxxxxx";
var postChannel = "#labnol";
  
function postGmailToSlack() {
 
  var threads = GmailApp.getInboxThreads();

  for(var m in threads) {
      sendHttpPost(threads[m].getFirstMessageSubject() + " link:" + threads[m].getPermalink(), "GMAIL");
  }
}

function sendHttpPost(message, username)
{
  var jsonData =
  {
     "channel" : postChannel,
     "username" : username,
     "text" : message
  };
  var payload = JSON.stringify(jsonData);
  var options =
  {
    "method" : "post",
    "contentType" : "application/json",
    "payload" : payload
  };

  UrlFetchApp.fetch(postUrl, options);
}
Categories
Code

Extract Name and Email Address from Gmail Header

The Gmail Extractor will soon let you extract both the name and the email address of the sender from the email message. Here’s a JavaScript regex that parses the name (if available) and the email address from the sender / to field of an email message.

The email addresses can be available in the email message header fields in multiple formats. If the name is present, the email is enclosed in angle brackets. There is also an alternate simple form, specified in RFC 2822 spec, where the email address appears alone, without the recipient’s name or the angle brackets. The regex takes care of them both.

function parseEmailHeader(message) {
  
  var header = message.getFrom().trim();
  
  // 1. John Miranda 
  // 2. john@gmail.com
  
  var extract = { name: "", email: "" };
  
  var emails = header.match(/[^@<\s]+@[^@\s>]+/g);
  
  if (emails) {
    extract.email = emails[0];
  }
  
  var names = header.split(/\s+/);
  
  if (names.length > 1) {
    names.pop();
    extract.name = names.join(" ").replace(/"/g, "");
  }
  
  Logger.log(extract);
  
}
Categories
Code

Send Gmail Drafts containing Inline Images with Google Scripts

The Gmail Scheduler takes your Gmail drafts and send them at the schedule date and time. If a draft email message contains inline images, the ones that that are not hosted on the Internet but have been dragged on to Gmail at the time of composing the message, it uses this routine to parse and include them in the outgoing message.

The getBody() method retrieves the HTML content of a Gmail message but the base64 encoded inline images inside a Gmail draft need to be retrieved using the getRawContent() method. An alternative approach is here.


/* Source: https://code.google.com/p/google-apps-script-issues/issues/detail?id=3934 */
function sendInlineImages() {
  
  var message    = GmailApp.getDraftMessages()[0];
  var bodyText   = message.getBody();
  var rawContent = message.getRawContent();
  
  // Retrieve the multi-part boundary
  var multipartBoundary = rawContent.match(/multipart\/related; boundary=([\S]*)\s/i);
  
  // Skip for plain text or HTML emails without inline images
  if (multipartBoundary) {
    
    // Split the raw content on the multipart boundary to retrieve the inline content.
    var contentParts = rawContent.split("--" + multipartBoundary[1]);
    
    // Array of base64 encoded inline images
    var inlineImages = {};
    
    for ( var i in contentParts )
    {
      var contentPart = contentParts[i].trim();
      
      // If this content part represents base64 encoded inline content?
      if ( contentPart.match(/content-disposition: inline;/i) 
          && contentPart.match(/content-transfer-encoding: base64/i) ) {
        
        // Extract the mime-type and name.
        var contentType = contentPart.match(/content-type: ([^;]+);\s*name="([^"]+)"/i);
        var mimeType = contentType[1];
        var name = contentType[2];
        
        // Extract the content-id
        var contentID = contentPart.match(/content-id: <([^>]+)>/i)[1];
        
        // Split the content part into its header and base64 encoded data.
        // The header and base64 encoded part should be separated by a blank line.
        var subParts = contentPart.split(/^[^\S]+$/m); 
        // Regex says split on lines that don't start with a non-space character
        
        // The 2nd part is the base64 encoded data.
        var base64enc = subParts[1].trim();
        
        // Create an image blob for the inline content.
        var blob = Utilities.newBlob(Utilities.base64Decode(base64enc), mimeType, contentID);
        
        inlineImages[contentID] = blob;
        
        // Replace the image source in the body text with the reference to the inline content.
        var regExp = new RegExp( "src=\"[^\"]+realattid=" + contentID + "[^\"]+\"", "g" );
        bodyText = bodyText.replace(regExp, "src=\"cid:" + contentID + "\"" );
        
      }
    }
    
  }
  
  var subject = message.getSubject();
  var attachments = message.getAttachments();
  var bcc = message.getBcc();
  var cc = message.getCc();  

  GmailApp.sendEmail("hello@labnol.org", "Inline Image Test", "labnol", 
       { attachments:attachments, htmlBody:bodyText, cc:cc, bcc:bcc, inlineImages:inlineImages});
}
Categories
Code

Send Draft Emails with the Gmail API

The Gmail API, like the GmailApp service, can help you interact with your Gmail mailbox through Google Apps Script.

The first sample snippets fetches a list of draft emails residing in your Gmail mailbox while the other one will send one of these draft messages via the Gmail API itself. All you have to specify is the Gmail Message ID.


/* See https://gist.github.com/mogsdad/6515581 and Joseph Devietti's gist */

function getGmailDrafts() {

  var params = { 
    method: "get", 
    muteHttpExceptions: true, 
    headers: { "Authorization": "Bearer " + ScriptApp.getOAuthToken() }
  };

  var resp = UrlFetchApp.fetch("https://www.googleapis.com/gmail/v1/users/me/drafts", params);

  var drafts = JSON.parse(resp.getContentText()).drafts;

  for (var i=0; i

		
Categories
Code

Show Trimmed Content in Gmail

The Gmail Bookmarklet will add a unique string to the email signature to prevent Gmail from trimming the content. The hex color code for the appended text is #444 so it will not be prominently displayed in Gmail.

/* Gmail Trim Bookmarklet */

/* Are the ellipsis shown in the Gmail message */
var showtrim = document.querySelector(".aH1");
if (showtrim) {
    /* If yes, click to expand the trimmed content */
    showtrim.click();
}

/* Does your email message have a signature */
var gmail = document.querySelector("div.gmail_signature");

/* Get the unique message ID assigned by Gmail */
var id = document.querySelector("input[name='composeid']");

if (gmail && id) {
    /* Append the current Date and ID to the signature */    
    gmail.innerHTML += "
Message #" + id.value + " sent on " + new Date().toString() + ""; } /* Written by Amit Agarwal amit@labnol.org */ /* For updates, see http://www.labnol.org */
Categories
Code

Save Gmail Messages as PDF – Google Apps Script

The Save Gmail to Google Drive program using Google Scripts to save Gmail message as PDF using the built-in PDF converter of Google Docs (now Google Drive).

The Google Apps Script reads the HTML body of the Gmail threads, strips the inline images, saves the thread as an HTML file and then converts the HTML into a PDF. If there are any file attachments in the email thread, they are saved as well. The downloads links of the attachments are appended to the PDF as well.

function saveGmailAsPDF() { 
  
  var gmailLabels  = "PDF";  
  var driveFolder  = "My Gmail";
  
  var threads = GmailApp.search("in:" + gmailLabels, 0, 5);  
  
  if (threads.length > 0) {
    
    /* Google Drive folder where the Files would be saved */
    var folders = DriveApp.getFoldersByName(driveFolder);
    var folder = folders.hasNext() ? 
        folders.next() : DriveApp.createFolder(driveFolder);
    
    /* Gmail Label that contains the queue */
    var label = GmailApp.getUserLabelByName(gmailLabels) ?     
        GmailApp.getUserLabelByName(gmailLabels) : GmailApp.createLabel(driveFolder);
    
    for (var t=0; t";  
        html += "To: " + msg.getTo() + "
"; html += "Date: " + msg.getDate() + "
"; html += "Subject: " + msg.getSubject() + "
"; html += "
"; html += msg.getBody().replace(/]*>/g,""); html += "
"; var atts = msg.getAttachments(); for (var a=0; a 0) { var footer = "Attachments:
    "; for (var z=0; z" + file.getName() + ""; } html += footer + "
"; } /* Conver the Email Thread into a PDF File */ var tempFile = DriveApp.createFile("temp.html", html, "text/html"); folder.createFile(tempFile.getAs("application/pdf")).setName(subject + ".pdf"); tempFile.setTrashed(true); } } }
Categories
Code

Create Nested Labels in Gmail with Apps Script

You can create nested labels in Gmail with the help Google Apps Script using the createLabel() method of the GmailApp service. The important thing to note is that the parent label should exist before a child label is created.

You can specify the label hierarchy in this format – Parent/Child/Grandchild/GreatGrandChild. Also avoid using dashes (-) in the name as they are seen as label separators in Gmail.

function createNestedGmailLabel() {
  
  var name   = "Parent Label/Child Label/Grandchild Label";
  var labels = name.split("/");
  var gmail, label = "";
  
  for (var i=0; i

		
Categories
Code

Using Gmail API to Send Rich Text Emails

The new HTML Mail app uses the Gmail API to send rich-text emails to anyone on your behalf. You can sign-in using your Google / Gmail account via OAuth and are presented with a HTML5 form to send emails. Unlike the Apps Script based solutions that require full access to your Gmail account, the new Gmail API only needs permissions to compose and send messages and does not have access to your Gmail messages and folders.

The app uses the Google PHP library to connect to the Gmail API though the can be easily ported to JavaScript, Python or Java. The text editor of the HTML Mail app is powered by TinyMCE.

setClientId($client_id);
  $client->setClientSecret($client_secret);
  $client->setRedirectUri($redirect_uri);

  // We only need permissions to compose and send emails
  $client->addScope("https://www.googleapis.com/auth/gmail.compose");
  $service = new Google_Service_Gmail($client);

  // Redirect the URL after OAuth
  if (isset($_GET['code'])) {
    $client->authenticate($_GET['code']);
    $_SESSION['access_token'] = $client->getAccessToken();
    $redirect = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
    header('Location: ' . filter_var($redirect, FILTER_SANITIZE_URL));
  }

  // If Access Toket is not set, show the OAuth URL
  if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
    $client->setAccessToken($_SESSION['access_token']);
  } else {
    $authUrl = $client->createAuthUrl();
  }

  if ($client->getAccessToken() && isset($_POST['message'])) {

    $_SESSION['access_token'] = $client->getAccessToken();

    // Prepare the message in message/rfc822
    try {

        // The message needs to be encoded in Base64URL
        $mime = rtrim(strtr(base64_encode($_POST["message"]), '+/', '-_'), '=');
        $msg = new Google_Service_Gmail_Message();
        $msg->setRaw($mime);
        $service->users_messages->send("me", $msg);

    } catch (Exception $e) {
        print($e->getMessage());
        unset($_SESSION['access_token']);
    }

  } ?>

 
  
 
  
Send Mail
Categories
Code

Sell Digital Downloads with Google Scripts

You can set up your own digital shop on the Internet with the help of PayPal and Google Scripts. You upload your file on Google Drive, the buyer makes a purchase through PayPal and Google Apps Script will deliver the file to the buyer through Gmail.

See: Sell Digital Products Online

/* PayPal Shop with Apps Script */
/* Written by Amit Agarwal - ctrlq.org */


/* Add the PayPal Item Ids and Google Drive Files names here */
PAYPAL = [
  ["product-001", "useful-websites-book.pdf"],
  ["product-002", "linux-training-course.mp4"],
  ["product-003", "labnol-audio-book.mp3"],
  ["product-004", "presentation-template.ppt"]
];

/* The script will scan your Gmail inbox every 5 minutes for PayPal emails */
function PayPal() {
  ScriptApp.newTrigger("myShop").timeBased().everyMinutes(5).create();
}

function myShop() {
  
  var file, size, files, threads;
  
  for (var p in PAYPAL) {
    
    threads = GmailApp.search("is:unread from:paypal " + PAYPAL[p][0]);
    
    if (threads.length > 0) {
      
      /* Find the file in Google Drive */
      files = DriveApp.searchFiles('title contains "' + PAYPAL[p][1] + '"');
      
      if (files.hasNext()) {
        
        file = files.next();
        size = file.getSize()/(1024*1024);
        
        for (var i=0; i 20) {
            file.addViewer(buyer); /* For big files, share the file with the buyer */
            GmailApp.sendEmail(buyer, subject, body + file.getUrl());
          } else {
            /* Else attach the file in the email message itself */
            GmailApp.sendEmail(buyer, subject, body, {attachments: file.getBlob()});
          }
          
          /* Move the PayPal email to Archive and Mark it as Read */
          threads[i].markRead().moveToArchive();
        }
      }
    }
  }
}
Categories
Code

Cleanup your Gmail Inbox with Apps Script

The Google Script will automatically archive all the messages in your Gmail inbox that are older than 2 days. You can either use a Gmail search filter to find read messages or you can compare the message date with the current date through the script and archive messages accordingly.

function cleanInbox() {
  
  var delayDays = 2;
  
  var maxDate = new Date();
  maxDate.setDate(maxDate.getDate()-delayDays);
  
  var threads = GmailApp.getInboxThreads();
  
  for (var i = 0; i < threads.length; i++) {
    if (threads[i].getLastMessageDate()

		
Categories
Code

Write Gmail Filters with Google Apps Script

The Google Script uses regular expressions to create advanced Gmail filters that works just like the native filters of Gmail but are more powerful at parsing email.

/* Perform case-sensitive search for Gmail */
function rule0(thread, rule) {
  var msg = thread.getMessages()[0];
  var body = stripTags([msg.getSubject(), msg.getBody()].join());
  var regex = new RegExp(rule[2], "g");  
  if (body.match(regex)) {
    thread.addLabel(getGmailLabel(rule[1]));
  }
}

/* Is the email sent to several people at once */
function rule1(thread, rule) {
  var msg = thread.getMessages()[0];
  var to = [msg.getTo(), msg.getCc()].join();
  if (to.match(/@/g).length >= rule[2]) {
    thread.addLabel(getGmailLabel(rule[1]));
  }
}

/* Is the email extremely long (count words) */
function rule3(thread, rule) {
  var msg = thread.getMessages()[0]; 
  var body = stripTags(msg.getBody());
  if (body.match(/\s+/g).length >= rule[2]) {
    thread.addLabel(getGmailLabel(rule[1]));
  }
}

/* Does the message have too many attachments? */
function rule5(thread, rule) {
  var msg = thread.getMessages()[0];
  var att = msg.getAttachments();
  if (att.length > rule[2]) {
    thread.addLabel(getGmailLabel(rule[1]));
  }
}

/* Does a message contain too many links? */
function rule7(thread, rule) {
  var msg = thread.getMessages()[0];
  var body = msg.getBody();
  if (body.match(/\https?:\/\//g).length > rule[2]) {
    thread.addLabel(getGmailLabel(rule[1]));
  }
}

/* Does a message contain too many images? */
function rule8(thread, rule) {
  var msg = thread.getMessages()[0];
  var body = msg.getBody();
  if ( ( body.match(/]+>/g) || [] ).length > rule[2]) {
    thread.addLabel(getGmailLabel(rule[1]));
  }
}
Categories
Code

Find Gmail Messages that are Awaiting Response

Google Apps Script for Gmail, creates a label that matches all threads where you are the last sender. Useful for finding email messages in your mailbox that are still awaiting a response from the customer. Credit @GSAPU.

function label_last_sent_message() {
  var emailAddress = Session.getEffectiveUser().getEmail();
  Logger.log(emailAddress);
  var EMAIL_REGEX = /[a-zA-Z0-9\._\-]+@[a-zA-Z0-9\.\-]+\.[a-z\.A-Z]+/g;
  var label = GmailApp.createLabel("LastSentMessage");
  var d = new Date();
  d.setDate(d.getDate() - 7); 
  var dateString = d.getFullYear() + "/" + (d.getMonth() + 1) + "/" + d.getDate();
  threads = GmailApp.search("in:Sent after:" + dateString);
  for (var i = 0; i < threads.length; i++)
  {
    var thread = threads[i];
    var lastMessage = thread.getMessages()[thread.getMessageCount()-1];
    var lastMessageSender = lastMessage.getFrom().match(EMAIL_REGEX)[0];
    if ((thread.getMessageCount() > 1) && (lastMessageSender == emailAddress))
    {
        thread.addLabel(label);
    }
    
  }
}
Categories
Code

Send Google Document as HTML via Gmail

The Google Script extracts the content of a Google Documents, converts the styles as inline CSS using the MailChimp API and send the document via Gmail as HTML Mail.


/* Credit: https://gist.github.com/erickoledadevrel/11143648 */
/**
 * Sends an email using the contents of a Google Document as the body.
 */
function sendDocument(documentId, recipient, subject) {
  var html = convertToHtml(documentId);
  html = inlineCss(html);
  GmailApp.sendEmail(recipient, subject, null, {
    htmlBody: html
  });
}
 
/**
 * Converts a file to HTML. The Advanced Drive service must be enabled to use
 * this function.
 */
function convertToHtml(fileId) {
  var file = Drive.Files.get(fileId);
  var htmlExportLink = file.exportLinks['text/html'];
  if (!htmlExportLink) {
    throw 'File cannot be converted to HTML.';
  }
  var oAuthToken = ScriptApp.getOAuthToken();
  var response = UrlFetchApp.fetch(htmlExportLink, {
    headers:{
      'Authorization': 'Bearer ' + oAuthToken
    },
    muteHttpExceptions: true
  });
  if (!response.getResponseCode() == 200) {
    throw 'Error converting to HTML: ' + response.getContentText();
  }
  return response.getContentText();
}
 
/**
 * Inlines CSS within an HTML file using the MailChimp API. To use the API you must
 * register for an account and then copy your API key into the script property
 * "mailchimp.apikey". See here for information on how to find your API key:
 * http://kb.mailchimp.com/article/where-can-i-find-my-api-key/.
 */
function inlineCss(html) {
  var apikey = CacheService.getPublicCache().get('mailchimp.apikey');
  if (!apikey) {
    apikey = PropertiesService.getScriptProperties().getProperty('mailchimp.apikey');
    CacheService.getPublicCache().put('mailchimp.apikey', apikey);
  }
  var datacenter = apikey.split('-')[1];
  var url = Utilities.formatString('https://%s.api.mailchimp.com/2.0/helper/inline-css', datacenter);
  var response = UrlFetchApp.fetch(url, {
    method: 'post',
    payload: {
      apikey: apikey,
      html: html,
      strip_css: true
    }
  });
  var output = JSON.parse(response.getContentText());
  if (!response.getResponseCode() == 200) {
    throw 'Error inlining CSS: ' + output['error'];
  }
  return output['html'];
}
Categories
Code

Mark Google Contacts with no Gmail Conversations

Your contacts list in Gmail is huge and you would like to trim it to only have email addresses with whom you have interacted at least once.

This Google Script, courtesy @crosenblub, will scan your entire address book and marks addresses with who you have never exchanged a single email conversation.

The contacts are put in a separate Google Contacts group making it easier for you to scan and clean the list.

function GetGmailContacts() {
  
  // get all contacts
  var contacts = ContactsApp.getContacts();
  
  // assign object for contact group    
  var group = ContactsApp.getContactGroup('ToBeDeleted');
  
  // loop through each contact
  for (var i=0; i

		
Categories
Code

Insert Inline Images in Gmail with Apps Script

The GmailApp service of Google Apps Script lets you easily read and process email messages from Gmail. You can use this to forward Gmail messages, to save email to your Gmail Drive in HTML or PDF formats, Mail merge, or even for transferring emails from one Gmail account to another email account.

While GmailApp offers simple methods like sendEmail and forward() to forward existing Gmail messages, you need to update the HTML Mail if the message body contains inline images. This is only required for inline images and not externally hosted image files that have been embedded into Gmail.

function sendEmailMessage() {
  
  var messageID = "ABC"; // Put the Gmail Message ID here
  var message = GmailApp.getMessageById(messageID);
  var options = getInlineImages(message);
  
  GmailApp.sendEmail(Session.getActiveUser().getEmail(), message.getSubject(), "", options)
  
}

/* Credit: https://code.google.com/p/google-apps-script-issues/issues/detail?id=3532 */
function getInlineImages(message) {
  
  var body = message.getBody();
  var attachments = message.getAttachments();
  var rawc = message.getRawContent();
  var inlineImages = {};
  var imgTags = body.match(/]+>/g) || []; // all image tags, embedded or by url
  
  for(var i = 0; i < imgTags.length; i++) {
    var realattid = imgTags[i].match(/realattid=(.*?)&/i); // extract the image cid if embedded
    if (realattid) { // image is inline and embedded
      var cid = realattid[1];
      var imgTagNew = imgTags[i].replace(/src="[^\"]+\"/,"src=\"cid:"+cid+"\""); // replace the long-source with just the cid
      body = body.replace(imgTags[i], imgTagNew); // update embedded image tag in message body
      var b64c1 = rawc.lastIndexOf(cid) + cid.length + 3; // first character in image base64
      var b64cn = rawc.substr(b64c1).indexOf("--") - 3; // last character in image base64
      var imgb64 = rawc.substring(b64c1, b64c1 + b64cn + 1); // is this fragile or safe enough?
      var imgblob = Utilities.newBlob(Utilities.base64Decode(imgb64), "image/jpeg", cid); // decode and blob
      inlineImages[cid] = imgblob;
    }
  }
  
  return { "htmlBody": body, "inlineImages": inlineImages, "attachments": attachments }
}
Categories
Code

Use Google Analytics without JavaScript

The tutorial — tracking Gmail opens with Google Analytics – — uses Google Script to insert the Google Analytics tracking code into the outgoing email messages without requiring JavaScript.

The script creates a 1×1 tracking GIF that creates an event in your Google Analytics when a recipient opens your email. Since Gmail now automatically downloads images, the email open activity is very likely to be tracked.

Apps Script can track email opens using both Classic Google Analytics (utm) and the new Universal Analytics and requires no JavaScript.

/* 
  Email Tracking with Google Analytics
  ====================================
  Credit: http://labnol.org/?p=8082
*/

function s4() {
    return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
};

function guid() {
    return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}

function emailTrackingGIF(version, email_address, email_subject, event_name) {

    /* Google Analytics Account ID like UA-1234-56 */
    var account = "UA-12345-XX";

    /* Random ID to prevent browser caching */
    var z = (Math.round((new Date()).getTime() / 1000)).toString();

    var imageURL = "";
    
    if (version == "universal") {
        
        imageURL = "https://ssl.google-analytics.com/collect?v=1&t=event"
                 + "&ec=" + encodeURIComponent(event_name)
                 + "&tid=" + account 
                 + "&z=" + z 
                 + "&cid=" + guid() 
                 + "&ea=" + encodeURIComponent(email_subject.replace(/'/g, "")) 
                 + "&el=" + email_address;

    } else if (version == "classic") {

        /* Relative path of the Web Page to be tracked */
        var utmp = "/inbox/" + email_address.replace("@", ".");

        var utme = encodeURIComponent("5(" + event_name + "*" + email_address + "*" + email_subject + ")");

        var request = "http://www.google-analytics.com/__utm.gif?" 
                    + "utmac=" + account 
                    + "&utmn=" + z 
                    + "&utmp=" + utmp 
                    + "&utmdt=" + encodeURIComponent(email_subject) 
                    + "&utme=" + utme 
                    + "&utmr=0&utmwv=5.4.5&utmhn=labnol.org&utmt=event&utmcc=__utma%3D999.999.999.999.999.1%3B";
    }
}
Categories
Code

Schedule Gmail Emails with Google Script

You can compose an email message in Gmail now, let it stay there in the Drafts folder and have it sent automatically at a specified data and time with the help of Google Scripts.

The Schedule GMail script imports the draft message in a spreadsheet, you specify the send data and time and it creates corresponding time-driven triggers to automate delivery.

/* Written by Amit Agarwal of labnol.org */

function initialize() {

    /* Clear the current sheet */
    var sheet = SpreadsheetApp.getActiveSheet();
    sheet.getRange(2, 1, sheet.getLastRow() + 1, 5).clearContent();

    /* Delete all existing triggers */
    var triggers = ScriptApp.getProjectTriggers();
    for (var i = 0; i < triggers.length; i++) {
        if (triggers[i].getHandlerFunction() === "sendMails") {
            ScriptApp.deleteTrigger(triggers[i]);
        }
    }

    /* Import Gmail Draft Messages into the Spreadsheet */
    var drafts = GmailApp.getDraftMessages();
    if (drafts.length > 0) {
        var rows = [];
        for (var i = 0; i < drafts.length; i++) {
            if (drafts[i].getTo() !== "") {
                rows.push([drafts[i].getId(), drafts[i].getTo(), drafts[i].getSubject(), "", ""]);
            }
        }
        sheet.getRange(2, 1, rows.length, 5).setValues(rows);
    }
}

/* Create time-driven triggers based on Gmail send schedule */
function setSchedule() {
    var sheet = SpreadsheetApp.getActiveSheet();
    var data = sheet.getDataRange().getValues();
    var time = new Date().getTime();
    var code = [];
    for (var row in data) {
        if (row != 0) {
            var schedule = data[row][3];
            if (schedule !== "") {
                if (schedule.getTime() > time) {
                    ScriptApp.newTrigger("sendMails")
                        .timeBased()
                        .at(schedule)
                        .inTimezone(SpreadsheetApp.getActiveSpreadsheet().getSpreadsheetTimeZone())
                        .create();
                    code.push("Scheduled");
                } else {
                    code.push("Date is in the past");
                }
            } else {
                code.push("Not Scheduled");
            }
        }
    }
    for (var i = 0; i < code.length; i++) {
        sheet.getRange("E" + (i + 2)).setValue(code[i]);
    }
}


function sendMails() {
    var sheet = SpreadsheetApp.getActiveSheet();
    var data = sheet.getDataRange().getValues();
    var time = new Date().getTime();
    for (var row = 1; row < data.length; row++) {
        if (data[row][4] == "Scheduled") {
            var schedule = data[row][3];
            if ((schedule != "") && (schedule.getTime() <= time)) {
                var message = GmailApp.getMessageById(data[row][0]);
                var body = message.getBody();
                var options = {
                    cc: message.getCc(),
                    bcc: message.getBcc(),
                    htmlBody: body,
                    replyTo: message.getReplyTo(),
                    attachments: message.getAttachments()
                }

                /* Send a copy of the draft message and move it to Gmail trash */
                GmailApp.sendEmail(message.getTo(), message.getSubject(), body, options);
                message.moveToTrash();
                sheet.getRange("E" + (row + 1)).setValue("Delivered");
            }
        }
    }
}

See video tutorial.

Categories
Code

Mark Archived Gmail Messages as Read Automatically

You can use Google Script to mark all archived unread emails messages as read automatically. The script runs every hour and looks for email messages in your Gmail mailbox that are unread and are not in your inbox. If there no Gmail label assigned to a thread, it is marked as Read.

To install this Gmail script, go to script.google.com and copy-paste this snippet. Next choose Run -> createTrigger to set the time-driven trigger that runs every hour automatically.

function markGmailAsRead() {
  // Find Gmail Messages that are unread but not in inbox
  var threads = GmailApp.search('is:unread -in:inbox', 0, 50);
  for (var i=0; i

		
Categories
Code

Find Emails in Gmail that are Awaiting a Reply

This Google Script scans the Sent Items folder of your Gmail mailbox and creates a list of messages that are still awaiting a reply from the recipient.

It looks at the sender’s address of the last message in a Gmail thread that is older than 7 days and, if it is different from the email address of the user running the Google Script, logs that message.


/* Credit: https://gist.github.com/cjbarber */
function label_waiting_for_reply() {

  // Get the gmail address of the current user
  var emailAddress = Session.getEffectiveUser().getEmail();

  var EMAIL_REGEX = /[a-zA-Z0-9\._\-]+@[a-zA-Z0-9\.\-]+\.[a-z\.A-Z]+/g;
  
  // Check if the Gmail label exists, else create it
  var label = GmailApp.getUserLabelByName("[Waiting For]") ? 
        GmailApp.getUserLabelByName("[Waiting For]") : GmailApp.createLabel("[Waiting For]");
  
  // Find Gmail Sent Items that are older than a week
  var d = new Date();
  d.setDate(d.getDate() - 7);
  var dateString = d.getFullYear() + "/" + (d.getMonth() + 1) + "/" + d.getDate();
  threads = GmailApp.search("in:sent after:" + dateString);
  
  for (var i = 0; i < threads.length; i++)
  {
    var thread = threads[i];
    // Find the senders email address of the last message in the Gmail thread
    var lastMessage = thread.getMessages()[thread.getMessageCount()-1];
    lastMessageSender = lastMessage.getFrom().match(EMAIL_REGEX)[0];
 
    // If the sender's email address is the same as the user, reply not received
    if (lastMessageSender == emailAddress && thread.getMessageCount() == 1)
    {
      thread.addLabel(label);
      Logger.log(lastMessageSender);
    }
  }
}
 
// Publish this Google Script as a web app
function doGet(e) { 
  label_waiting_for_reply();
}

Categories
Code

Archive Old Gmail Messages Automatically

This Google Script will scan the inbox of your Gmail and archive any message that you’ve read (seen) and is older than a month. It skips messages that are either starred or marked with a particular label like toReply.

function archiveInbox() {
  var query = 'label:inbox is:read older_than:30d -in:starred -label:toreply';
  var batchSize = 100;
  
  while(GmailApp.search(query, 0, 1).length == 1) {
    GmailApp.moveThreadsToArchive(GmailApp.search(query, 0, batchSize));
  }
}

Here’s an alternate way to deal with the same issue. It checks for individual messages in a Gmail thread before moving them to the Archive.

function cleanInbox() {
  var threads = GmailApp.getInboxThreads();
  for (var i = 0; i < threads.length; i++) {
    var thread=threads[i];
    if (!thread.hasStarredMessages() && !thread.isUnread()) {
       GmailApp.moveThreadToArchive(threads[i]);
    }
  }
}
Categories
Code

Gmail Search by Size with Google Apps Script

The following Google Apps Script will help you find all Gmail messages that have file attachments greater than 1 MB. Should be useful when you are running out of space in Gmail.

Also see: Sort Gmail Messages by Size


function Scanning_Gmail_Mailbox() {    
  
  if (!UserProperties.getProperty("start")) {
    UserProperties.setProperty("start", "0");    
  }
  
  var start   = parseInt(UserProperties.getProperty("start"));
  var sheet   = SpreadsheetApp.getActiveSheet();
  var row     = getFirstRow();
  var ss      = SpreadsheetApp.getActiveSpreadsheet()
  
  for (;;) {
  
  ss.toast("Now finding all the big emails in your Gmail mailbox. Please wait..", "Scan Started", -1);
  
  // Find all Gmail messages that have attachments
  var threads = GmailApp.search('has:attachment larger:1m', start, 100);
  
  if (threads.length == 0) {
    ss.toast("Processed " + start + " messages.", "Scanning Done", -1); 
    return;
  }
    
  for (var i=0; i 1 MB, log the messages
     // You can change this value as per requirement.
      
      if (size >= 1) {      
        sheet.getRange(row,1).setValue(Utilities.formatDate(messages[m].getDate(),"GMT", "yyyy-MM-dd"));
        sheet.getRange(row,2).setValue(messages[m].getFrom());        
        sheet.getRange(row,3).setValue(messages[m].getSubject());
        sheet.getRange(row,4).setValue(size);        
        var id = "https://mail.google.com/mail/u/0/#all/" + messages[m].getId();
        sheet.getRange(row,5).setFormula('=hyperlink("' + id + '", "View")'); 
        row++;
      }
    }            
  }    
  }
}


// Compute the size of email attachments in MB

function getMessageSize(att) {
  var size = 0;
  for (var i=0; i

		
Categories
Code

The 43 Folders System to Organize your Gmail

43 Folders is a ticker file system that is used to file time-sensitive documents, appointments and emails in such a manner that they are labeled according to a date on which the document needs to be acted upon. There are 12 folders for every month and 31 folders for every day and hence the name 43 folders.

This useful Google Script lets you organize your Gmail messages around the concept of 43 folders. It was originally published by Facundo Bromberg but the domain hosting the script had gone offline. Luckily, Google Cache had a copy.

I present here an adaptation of Google’s snooze script that works under the 43folders methodology. The original script posted by Corey Goldfeder allows you to snooze an email a certain amount of days by labeling it with a label indicating the number of days to snooze it, e.g., Snooze1day. After archiving, the email would re-appear in your inbox after 1 day. Inspired by this script I created the 43folders script that implements a tickler file with the 43 folders methodology to postpone tasks (for a detailed explanation of the 43 folder methodology see for example here).

The 43 methodology considers 43 folders labeled 01 through 31 and JAN,FEB,MAR,…,DEC, i.e., 31+12 = 43 folders. The idea of the 43 folders is to give 1 day granularity for the next 31 days, otherwise postpone it to the beginning of a month. For instance, if today is August 22nd and I put something in folder 24, I am postponing it to the 24th of August, if I put it in folder 04 I am postponing it until September 4th. If I need to postpone something to September 22nd onwards I put it in a month folder, for instance, if I put it in folder FEB, I am postponing it to next February 2012. The idea is that at the beginning of each month I should re-organize whatever is in the month’s folder.

How does 43folders for Gmail work

  1. There are 43 labels, one per folder, i.e., 01-31 and JAN-DEC.
  2. To “put” an email in a folder simply label it and archive.
  3. If you labeled it with a day, e.g. 24, the email will automatically show back in your inbox on that day, i.e., the 24th. If you labeled it with a month, e.g. FEB, it will show back in your inbox the first day of that month, i.e., February 1st 2012.
  4. The script has two shortcuts labels: “a_tomorrow” and “a_nextweek” (the a’s to have them above all others in gmail). These labels works as expected, an email labeled a_ tomorrow will show back in your email, well, tomorrow; and an email labeled next_week will show back in your inbox next Monday.

How to Setup 43 Folders for Gmail

Go to Google Docs and create a new spreadsheet, then choose “Script Editor” from the “Tools” menu. Paste in the following code:

//-----------------------------------------------
//  SETTINGS:
//  
//  1. Choose your prefered base and unsnoozed labels.
//  2. Choose whether you want the unsnoozed emails to be marked unread and
//     labeled UNSNOOZED_LABEL
//  3. Set a daily trigger for method process().
//-----------------------------------------------

var MARK_UNREAD = true;
var MARK_UNSNOOZED = true;
var UNSNOOZED_LABEL = "A/UNSNZD";
var BASE_LABEL = "C/z43f"

//-----------------------------------------------
//-----------------------------------------------
//-----------------------------------------------

var TOMORROW_LABEL = BASE_LABEL + "/a_tomorrow";
var NEXTWEEK_LABEL = BASE_LABEL + "/a_nextWeek";
var SNOOZED_LABEL = "A/SNZD";
var TODAY_LABEL = BASE_LABEL + "/today";
var THISWEEK_LABEL = BASE_LABEL + "/thisWeek";
var THISMONTH_LABEL = BASE_LABEL + "/thisMonth";

//-----------------------------------------------
function getDayLabelName(i) {
    if (i <= 9) return BASE_LABEL + "/0" + i;
    else return BASE_LABEL + "/" + i;
}

//-----------------------------------------------
function getMonthLabelName(i) {
    if (i == 1) return BASE_LABEL + "/JAN";
    if (i == 2) return BASE_LABEL + "/FEB";
    if (i == 3) return BASE_LABEL + "/MAR";
    if (i == 4) return BASE_LABEL + "/APR";
    if (i == 5) return BASE_LABEL + "/MAY";
    if (i == 6) return BASE_LABEL + "/JUN";
    if (i == 7) return BASE_LABEL + "/JUL";
    if (i == 8) return BASE_LABEL + "/AUG";
    if (i == 9) return BASE_LABEL + "/SEP";
    if (i == 10) return BASE_LABEL + "/OCT";
    if (i == 11) return BASE_LABEL + "/NOV";
    if (i == 12) return BASE_LABEL + "/DEC";
}

//-----------------------------------------------
function setup() {
    // Create the labels we’ll need for snoozing
    GmailApp.createLabel(BASE_LABEL);
    GmailApp.createLabel(TOMORROW_LABEL);
    GmailApp.createLabel(NEXTWEEK_LABEL);
    GmailApp.createLabel(SNOOZED_LABEL);
    GmailApp.createLabel(UNSNOOZED_LABEL);

    for (var i = 1; i <= 31; ++i) {
        GmailApp.createLabel(getDayLabelName(i));
    }
    for (var i = 1; i <= 12; ++i) {
        GmailApp.createLabel(getMonthLabelName(i));
    }

    GmailApp.createLabel(TODAY_LABEL);
    GmailApp.createLabel(THISMONTH_LABEL);

}

//-----------------------------------------------
function process() {
    var today = new Date();
    var weekday = today.getDay();
    var monthday = today.getDate();
    var month = today.getMonth() + 1;
    var year = today.getYear();

    moveToInbox(GmailApp.getUserLabelByName(getDayLabelName(monthday)), TODAY_LABEL);
    moveToInbox(GmailApp.getUserLabelByName(TOMORROW_LABEL), TODAY_LABEL);
    moveToInbox(GmailApp.getUserLabelByName(getMonthLabelName(month)), THISMONTH_LABEL);
    if (weekday == 1) {
        moveToInbox(GmailApp.getUserLabelByName(NEXTWEEK_LABEL), THISWEEK_LABEL);
    }
}


//-----------------------------------------------
function moveToInbox(label, newLabelName) {
    page = null;
    // Get threads in "pages" of 100 at a time
    while (!page || page.length == 100) {
        page = label.getThreads(0, 100);

        if (page.length > 0) {
            GmailApp.moveThreadsToInbox(page);
            if (MARK_UNREAD) {
                GmailApp.markThreadsUnread(page);
            }

            //Mark with new label
            //GmailApp.getUserLabelByName(newLabelName).addToThreads(page);


            //Adds UNSNOOZED_LABEL
            if (MARK_UNSNOOZED) {
                GmailApp.getUserLabelByName(UNSNOOZED_LABEL).addToThreads(page);
            }

            // Removes label
            label.removeFromThreads(page);

            //Removes SNOOZED_LABEL in case it was added by maintenance (addBASE_LABEL script) or manually
            GmailApp.getUserLabelByName(SNOOZED_LABEL).removeFromThreads(page);
        }
    }
}

//-----------------------------------------------
// Adds the label to all emails with some 43f sublabel. This is for easy hiding in multiple-inbox.
function addLABEL() {
    var labelName = SNOOZED_LABEL
    addLabel(GmailApp.getUserLabelByName(NEXTWEEK_LABEL), labelName);
    addLabel(GmailApp.getUserLabelByName(TOMORROW_LABEL), labelName);
    for (var i = 1; i <= 31; ++i) {
        addLabel(GmailApp.getUserLabelByName(getDayLabelName(i)), labelName);
    }
    for (var i = 1; i <= 12; ++i) {
        addLabel(GmailApp.getUserLabelByName(getMonthLabelName(i)), labelName);
    }
} 

//-----------------------------------------------
function addLabel(label, newLabelName) {
    page = null;
    // Get threads in "pages" of 100 at a time
    while (!page || page.length == 100) {
        page = label.getThreads(0, 100);

        if (page.length > 0) GmailApp.getUserLabelByName(newLabelName).addToThreads(page);
    }
}

Then click the “Save” button and give it a name. In the dropdown labeled “Select a function to run.” choose “setup” and click the blue run arrow to the left of it. This will ask you to authorize the script, and will create the necessary labels in your Gmail. Then go to the “Triggers” menu and choose “current script’s triggers”. Click the link to set up a new trigger, choose the “process” function, a “time-driven” event, “day timer”, and then “midnight to 1am.” Click save and you’re done.

Categories
Code

Send to Google Drive with Google Apps Script

This Google Apps Script will automatically save Gmail attachments to Google Drive. It also has a premium version that supports nested Gmail labels, it can monitor multiple Gmail label and saves attachments in logical Google Drive folders.


function sendToGoogleDrive() { 
  
  var sheet   = SpreadsheetApp.getActiveSheet();
  
  var gmailLabels  = sheet.getRange("D4:D4").getValue();  
  var driveFolder  = sheet.getRange("D5:D5").getValue();  
  var archiveLabel = sheet.getRange("D6:D6").getValue();
  
  var moveToLabel =  GmailApp.getUserLabelByName(archiveLabel);
  
  if ( ! moveToLabel ) {    
    moveToLabel = GmailApp.createLabel(archiveLabel);    
  }

  var filter = "has:attachment -label:" + archiveLabel + " label:" + gmailLabels;
  
  var threads = GmailApp.search(filter, 0, 5);  

  var folder = DriveApp.getFoldersByName(driveFolder);
  
  if (folder.hasNext()) {
    folder = folder.next();
  } else {
    folder = DriveApp.createFolder(driveFolder);
  }

  for (var x=0; x

		
Categories
Code

Encryption with Google Apps Script

This Google Script will encrypt Gmail using the powerful and secure AES encryption. It then converts the JSON response to Base64 and sends the encrypted message via GMailApp service. See video tutorial.

The password for encryption is supplied via the input field in the form. The recipient will need this password to decrypt messages.


function encryptMail(e) {
  
  try {

    // get the first message from the drafts folder in Gmail
    var draft = GmailApp.search("in:drafts")[0].getMessages()[0];

    // retrieve the email body in plain text
    // ( you can also do getBody() for HTML Mail )
    var body = draft.getPlainBody();
    var to = draft.getTo();
    var subject = draft.getSubject();
    
    // The encrypted message is in JSON format
    var json = sjcl.encrypt(e.parameter.password, body);
    // Convert the JSON to base64 (easier to copy-paste)
    var msg  = Utilities.base64Encode(Utilities.jsonStringify(json));    

    // Send the encrypted message via Gmail
    GmailApp.sendEmail(to, subject, msg);  
  } catch (e) {
    Logger.log(e.toString());
  }  
}
Categories
Code

Gmail Counter with Google Apps Script

The following Google Apps Script will display the unread Gmail counter for your mailbox to anyone on the Internet with a simple URL.

The count is published as text but you may use other formats (ContentService.MimeType) like RSS, JSON or even CSV and feed this data into other system – like tweets or even another spreadsheet.


 

/*    Gmail Counter by Amit Agarwal   */
/*    Published on 06/17/2013         */

function doGet() {
  
  var cache = CacheService.getPublicCache();
  var counter = cache.get("gmail-counter");
  
  // Cache the counter to stay within the Apps Script quota
  if (!counter) {
    counter = GmailApp.getInboxUnreadCount();
    // Cache the counter value for 20 minutes
    cache.put("gmail-counter", counter, 1200); 
  }
  
  // You can customize the output message
  counter = "There are " + counter + " unread messages in my Gmail Inbox."
    
  // Output the result in plain text format
  return ContentService.createTextOutput(counter)
    .setMimeType(ContentService.MimeType.TEXT);
  
}

 

Categories
Code

Google Script for Extracting Email Addresses

This Google Apps Script will sift through your Gmail account and extract email addresses of senders which are then saved in a Google Sheet. Useful for email marketing and mail merge.

 
/** This script will extract email address from your Gmail mailbox **/
/**   Written by Amit Agarwal on 06/13/2013    **/

function extractEmailAddresses() {
   
  var ss = SpreadsheetApp.getActiveSpreadsheet();   
  var sheet = ss.getSheets()[0];
  
  var monitor = sheet.getRange("A2").getValue();
  var processed = sheet.getRange("B2").getValue();
  
  var label = GmailApp.getUserLabelByName(processed);         
  var search = "in:" + monitor + " -in:" + processed;

  // Process 50 Gmail threads in a batch to prevent script execution errors
  var threads = GmailApp.search(search, 0, 50);      
  
  var row, messages, from, email;
  
  try {
    
    for (var x=0; x", "");
        email = email.replace("<", "");
        
        row   = sheet.getLastRow() + 1;        
        // If an email address if found, add it to the sheet
        sheet.getRange(row,1).setValue(email);
      }
      
      threads[x].addLabel(label);
      
    }
    
  }

  catch (e) {
    Logger.log(e.toString());
    Utilities.sleep(5000);
  }
    
  // All messages in the label have been processed?
  if ( threads.length === 0 ) {
    GmailApp.sendEmail(Session.getActiveUser().getEmail(), "Extraction Done", 
                       "Download the sheet from " + ss.getUrl());
  }  
}


// Remove Duplicate Email addresses
function cleanList() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var data = sheet.getRange(4, 1, sheet.getLastRow()).getValues();
  var newData = new Array();
  for(i in data){
    var row = data[i];
    var duplicate = false;
    for(j in newData){
      if(row[0] == newData[j][0]){
        duplicate = true;
      }
    }
    if(!duplicate){
      newData.push(row);
    }
  }

  // Put the unique email addresses in the Google sheet
  sheet.getRange(4, 2, newData.length, newData[0].length).setValues(newData);
}

 
Categories
Code

Gmail OCR with Google Apps Script

The script will save Gmail attachments to Google Drive and because Drive supports OCR for images, you can then search for text inside these images.



/*

    Auto-Save your Gmail Image Attachments to Google Drive
    ======================================================

    Written by Amit Agarwal on 05/28/2013 
    
    To get started, choose Run -> Authorize and grant the 
    necessary permissions. Then choose Run -> StartProgram. 
    
    The default Google Drive folder for saving the image
    attachments is "Gmail Images" and once the message has
    been processed, Gmail applies the label "Processed" to 
    that message. You can change the defaults in line 26 & 26.

*/


// Authorize the Google Apps Script
function Authorize() {
  StartProgram();
}

// Initialize the Script
function StartProgram() {
  
  var DRIVE_FOLDER = "Gmail Images";  
  var GMAIL_LABEL  = "Processed";
  
  createGmailLabel(GMAIL_LABEL);
  createDriveFolder(DRIVE_FOLDER);  
  createTrigger();

}

// The script will check your Gmail mailbox every minute
// with the help of a CLOCK based trigger.
function createTrigger() {
  
  var triggers = ScriptApp.getScriptTriggers();
  
  for(var i in triggers) {
    ScriptApp.deleteTrigger(triggers[i]);
  }
  
  ScriptApp.newTrigger('saveGmailAttachments')
   .timeBased()
   .everyMinutes(1)
   .create();   

}

// If the Gmail label is unavailable, create one.
function createGmailLabel(name) {
  
  if ( ! GmailApp.getUserLabelByName(name) ) {
    GmailApp.createLabel(name);
  }
  
  ScriptProperties.setProperty("LABEL", name); 
}


// If the Google Drive folder is not present, create one.
function createDriveFolder(name) {
  
  var folders = DriveApp.getFolders();
  var folder, found = false;

  while (folders.hasNext()) {
    folder = folders.next();
    if (folder.getName() === name) {
      found = true;
      break;
    }
  }
  
  if ( ! found ) {
    DriveApp.createFolder(name);      
  }
  
  ScriptProperties.setProperty("FOLDER_ID", folder.getId()); 
}

// This will auto-save the image attachments from Gmail to Google Drive
function saveGmailAttachments() {
  
  var label_name = ScriptProperties.getProperty("LABEL");  
  var label = GmailApp.getUserLabelByName(label_name);   
  
  var folderID  = DriveApp.getFolderById(ScriptProperties.getProperty("FOLDER_ID"));
  
  // Scan for threads that have image attachments
  var threads = GmailApp.search("in:all -in:spam -in:trash -in:" + label_name 
              + " has:attachment filename:jpg OR filename:png OR filename:gif", 0, 10);      
  
  try {
    
    for (var x=0; x

		
Categories
Code

Gmail Search with Google Apps Script

You can use Regular Expressions to search Gmail with the help of Google App Script. The script scans the mailbox, compares the message body with the search pattern and prints any matching messages. Google Apps Script using standard JavaScript functions to perform regex search.

function Search() {

var sheet   = SpreadsheetApp.getActiveSheet();
var row     = 2;
  
// Clear existing search results
sheet.getRange(2, 1, sheet.getMaxRows() - 1, 4).clearContent();

// Which Gmail Label should be searched?
var label   = sheet.getRange("F3").getValue();

// Get the Regular Expression Search Pattern
var pattern = sheet.getRange("F4").getValue();
  
// Retrieve all threads of the specified label
var threads = GmailApp.search("in:" + label);
    
for (var i = 0; i < threads.length; i++) {
    
  var messages = threads[i].getMessages();
    
  for (var m = 0; m < messages.length; m++) {
    var msg = messages[m].getBody();

    // Does the message content match the search pattern?
    if (msg.search(pattern) !== -1) {

     // Format and print the date of the matching message
     sheet.getRange(row,1).setValue(
      Utilities.formatDate(messages[m].getDate(),"GMT","yyyy-MM-dd"));

     // Print the sender's name and email address
     sheet.getRange(row,2).setValue(messages[m].getFrom());        

     // Print the message subject
     sheet.getRange(row,3).setValue(messages[m].getSubject());

     // Print the unique URL of the Gmail message
     var id = "https://mail.google.com/mail/u/0/#all/"
       + messages[m].getId();
     sheet.getRange(row,4).setFormula(
       '=hyperlink("' + id + '", "View")'); 

     // Move to the next row
     row++;
   }
  }
 }
}