MattBagg MattBagg - 3 months ago 58
R Question

Adding an attachment with gmailr

I am having difficulty using the r package

gmailr
(version 0.7.1) to attach a file to my email without obscuring the text/body of the email. What, if anything, is wrong with the resulting MIME email, shown below.

This works and the text/body reading "This is a test..." is visible in the e-mail:

pkgs <- c('purrr','gmailr')
lapply(pkgs, require, character.only = T)

mime() %>%
to("valid@mail.org") %>%
from("me@gmail.com") %>%
text_body("This is a test of attaching a file to a messsage!") %>%
subject("Testing") -> text_msg

send_message(text_msg)


This, on the other hand, will attach the pdf but the text of the message is no longer visible:

mime() %>%
to("valid@mail.org") %>%
from("me@gmail.com") %>%
text_body("This is a test of attaching a file to a messsage!") %>%
subject("Testing") %>%
attach_file("some.pdf") -> text_msg

send_message(text_msg)


Although gmail does not display the text, if I view the source of the email I can see the text/body:

Received: from 955034766742
named unknown
by gmailapi.google.com
with HTTPREST;
Tue, 9 Aug 2016 20:15:25 -0400
MIME-Version: 1.0
Date: Tue, 9 Aug 2016 20:15:25 -0400
To: valid@mail.org
From: me@gmail.com
Subject: Testing
Content-Type: multipart/mixed; boundary=ae1edfec3113c0631c2b52e180caeb61
Content-Disposition: inline
Message-Id: <CAEDF9O5BR5kFYr4hiCmj=LyWPn8NPAAhgPHYyBNpk384H-nyyQ@mail.gmail.com>

MIME-Version: 1.0
Date: Wed, 10 Aug 2016 00:21:29 GMT
Content-Type: text/plain; charset=utf-8; format=flowed
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

This is a test of attaching a file to a messsage!
--ae1edfec3113c0631c2b52e180caeb61
MIME-Version: 1.0
Date: Wed, 10 Aug 2016 00:21:29 GMT
Content-Type: application/pdf; name=some.pdf
Content-Transfer-Encoding: base64
Content-Disposition: inline; filename=some.pdf; modification-date=Mon, 08 Aug 2016 18:40:49 GMT

JVBERi01LjUKJdDUxdgKMTAgMCXvYmoKPDwKL0xlbme0aCA3MTQgICAgICAgCi9WaWx0ZXIgL0Zs
HTtju0D+nl3WDmkxyQzh5JW1enrv7UpiZEkYuRkx/73MRm+nghEuaSSVINmDDEFUyinLSDYnD0EW
etc


Is the MIME somehow malformed? Why does the second MIME section not display?

Answer

The issue seems to be that gmailr is not including an initial MIME boundary line before the body of the text. MIME (Multipurpose Internet Mail Extensions) is the standard for email. When there are multiple parts to an email, as when you attach a file, the standard specifies that each part is preceded by an encapsulation boundary, which is an arbitrary string defined in the header (on the 11th line of the example above).

In the email example above, there is no boundary line before the body begins on line 15. The area before the first boundary line is supposed to be ignored by MIME-compliant clients and is traditionally used to put a message to users of old non-MIME clients. This may be why gmail still finds the body of the text and displays it if there if no attachment but does not display it when an attachment is present.

My ugly fix for this is to alter the gmailr::send_message function to insert the initial boundary line. This is obviously a worse fix than fixing the function with which gmailr inserts boundary lines and for that I apologize.

send_message_fixed <- function (mail, type = c("multipart", "media", "resumable"), 
          thread_id = NULL, user_id = "me") 
{
  mail <- as.character(mail)
  # infer boundary line
  boundary_line <- paste0(stringr::str_replace(stringr::str_extract(mail,"boundary=.{1,}"),"boundary=","--"),"\r\n")
  # find where to insert it
  need_boundary_before_here <- stringr::str_locate_all(mail,"MIME")[[1]][2,1]
  # insert it
  mail <- paste0(substr(mail,1,need_boundary_before_here-1),
                 boundary_line,
                 substr(mail,need_boundary_before_here,stringr::str_length(mail)))
  stopifnot(gmailr:::nullable(gmailr:::is_string)(thread_id), gmailr:::is_string(user_id))
  type <- match.arg(type)
  gmailr:::gmailr_POST(c("messages", "send"), user_id, class = "gmail_message", 
              query = list(uploadType = type), 
              body = jsonlite::toJSON(auto_unbox = TRUE, 
                                      null = "null", c(threadId = thread_id, 
                                                       list(raw = gmailr:::base64url_encode(mail)))), 
                                      httr::add_headers(`Content-Type` = "application/json"))
}