Search code examples
javascriptemailgoogle-apps-scriptgmailgmail-api

Remove an attachment of a Gmail email with Google Apps Script


Using Google Apps Script (http://script.google.com), I know from the docs, how to send, forward, move to trash messages, etc. but I don't find how to remove a file attachement of an email, i.e.:

  1. keep the text content (either in HTML or just plain text would be fine)
  2. keep the original sender, keep the recipient
  3. keep the original message date/hour (important!)
  4. remove the attachment

If it's not possible via the API, is there a way to resend the message to myself, while keeping 1, 2 and 3?


Note: the GmailAttachment class looks interesting and allows to list recipients:

var threads = GmailApp.getInboxThreads(0, 10);
 var msgs = GmailApp.getMessagesForThreads(threads);
 for (var i = 0 ; i < msgs.length; i++) {
   for (var j = 0; j < msgs[i].length; j++) {
     var attachments = msgs[i][j].getAttachments();
     for (var k = 0; k < attachments.length; k++) {
       Logger.log('Message "%s" contains the attachment "%s" (%s bytes)',
                  msgs[i][j].getSubject(), attachments[k].getName(), attachments[k].getSize());
     }
   }
 }

but I don't find how to remove an attachment.

Note: I've already studied many other solutions for doing this, I've already read nearly every article about this (solutions with dedicated web services, with local clients like Thunderbird + Attachment extractor plugin, etc.), but none of them are really really cool. That's why I was looking for a solution to do it manually via Google Apps Script.


Solution

  • Looks like messages will have to be re-created-ish:

    Messages are immutable: they can only be created and deleted. No message properties can be changed other than the labels applied to a given message.

    Using Advanced Gmail Service with the Gmail API insert() you can hack your way around it using: Gmail.Users.Messages.insert(resource, userId)

    This advanced service must be enabled before use.

    Example: [fill in the EMAIL_ID with an email_id or in whatever way you want to get the email]

    function removeAttachments () {
      // Get the `raw` email
      var email = GmailApp.getMessageById("EMAIL_ID").getRawContent();
    
      // Find the end boundary of html or plain-text email
      var re_html = /(-*\w*)(\r)*(\n)*(?=Content-Type: text\/html;)/.exec(email);
      var re = re_html || /(-*\w*)(\r)*(\n)*(?=Content-Type: text\/plain;)/.exec(email);
    
      // Find the index of the end of message boundary
      var start = re[1].length + re.index;
      var boundary = email.indexOf(re[1], start);
    
      // Remove the attachments & Encode the attachment-free RFC 2822 formatted email string
      var base64_encoded_email = Utilities.base64EncodeWebSafe(email.substr(0, boundary));
      // Set the base64Encoded string to the `raw` required property
      var resource = {'raw': base64_encoded_email}
    
      // Re-insert the email into the user gmail account with the insert time
      /* var response = Gmail.Users.Messages.insert(resource, 'me'); */
    
      // Re-insert the email with the original date/time 
      var response = Gmail.Users.Messages.insert(resource, 'me', 
                          null, {'internalDateSource': 'dateHeader'});
    
      Logger.log("The inserted email id is: %s",response.id)
    }
    

    This will remove the attachments from the email and re-insert it into your mailbox.

    edit/update: New RegExp to work with html&plain-text only emails - should now work on multiple boundary strings