Search code examples
androidgmail-apimime-message

Sending email with attachments using Gmail API in Android (execute() hangs)


Everything seems to work as long as the attachment is small.
However, as I try to attach a larger file (7MB for example), the execute() method of Send just hangs.
I tried to go over the documentation and if I understand correctly I should use a send API which actually performs upload however, I didn't figure where I should provide these parameters.
Here is the email generation method :

public MimeMessage toMimeMessage(String from, Context context) throws MessagingException {
    Properties props = new Properties();
    Session session = Session.getDefaultInstance(props, null);

    MimeMessage mimeMessage = new MimeMessage(session);

    mimeMessage.setFrom(new InternetAddress(from));
    mimeMessage.addRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress(recipient));
    mimeMessage.setSubject(subject);

    MimeBodyPart mimeBodyText = new MimeBodyPart();
    mimeBodyText.setContent(body, "text/html");
    mimeBodyText.setHeader("Content-Type", "text/plain; charset=\"UTF-8\"");

    Multipart mp = new MimeMultipart();
    mp.addBodyPart(mimeBodyText);

    if (attachments != null && attachments.size() > 0) {
        MimeBodyPart mimeBodyAttachments = new MimeBodyPart();
        for (Uri uri : attachments) {
            String fileName = UriUtils.getFileName(uri, context);
            String mimeType = UriUtils.getMimeType(uri, context);
            Log.d(TAG, "Generating file info, uri=" + uri.getPath() + ", mimeType=" + mimeType);
            FileInputStream is = UriUtils.generateFileInfo(context, uri, mimeType);
            if (is == null) {
                throw new MessagingException("Failed to get file for uri=" + uri.getPath());
            }
            try
            {
                DataSource source = new ByteArrayDataSource(is, mimeType);
                mimeBodyAttachments.setDataHandler(new DataHandler(source));
                mimeBodyAttachments.setFileName(fileName);
                mimeBodyAttachments.setHeader("Content-Type", mimeType + "; name=\"" + fileName + "\"");
            } catch (IOException e) {
                throw new MessagingException(e.getMessage());
            }
        }
        mimeBodyAttachments.setHeader("Content-Transfer-Encoding", "base64");
        mimeBodyAttachments.setDisposition(MimeBodyPart.ATTACHMENT);
        mp.addBodyPart(mimeBodyAttachments);
    }

    mimeMessage.setContent(mp);

    return mimeMessage;
}

.

Message createMessageWithEmail(MimeMessage mimeMessage) throws MessagingException, IOException {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        mimeMessage.writeTo(bytes);
        String encodedEmail = Base64.encodeBase64URLSafeString(bytes.toByteArray());
        Message message = new Message();
        message.setRaw(encodedEmail);
        return message;
    }

Followed by :

MimeMessage mimeMessage = email.toMimeMessage(userId, context);
Message message = createMessageWithEmail(mimeMessage);
Gmail.Users.Messages messages = service.users().messages();
Gmail.Users.Messages.Send send = messages.send(userId, message);
send.execute(); // method hangs when using large attachment

Solution

  • As you mentioned, I think you are hitting the upper limit of allowed message size. I don't think (someone please correct me if I'm wrong) that the Java Gmail API client has any built in support for messages that go over this size, so it is up to you to implement it.

    Under the hood, the messages.send-method results in a regular http POST request:

    POST https://www.googleapis.com/gmail/v1/users/{USER_ID}/messages/send
    Content-Type: application/json
    Authorization: Bearer {ACCESS_TOKEN}
    
    {
      "raw": "{MESSAGE_URL_SAFE_BASE64_ENCODED}"
    }
    

    This only works up to ~5 mb total size as you discovered. If you want to use the max limit of 35 mb you need to do the following:

    POST https://www.googleapis.com/upload/gmail/v1/users/{USER_ID}/messages/send?uploadType=multipart
    Content-Type: message/rfc822
    Authorization: Bearer {ACCESS_TOKEN}
    
    "{MESSAGE_IN_RFC822_FORMAT}"
    

    Notice upload in the URL, the uploadType=multipart URL parameter, message/rfc822 as Content-Type and message non-encoded in the request body. This answer might give some inspiration.

    So you probably need to go around (again, someone correct me if I'm wrong) the Java client and use some other library to make a regular http request yourself.