0

Google App Engine's Email service uses JavaMail JARs included in the AppEngine SDK. It specifically asks us not to use Oracle's JARs.

But in order to use Google OAuth 2.0 for sending emails using SMTP, we need Oracle's JavaMail JARs.

Any idea on how to achieve this? I'm stuck. Our software's internal features use AppEngine mails to send emails within our team, and our clients need Google OAuth SMTP to send mails on behalf of their email address.

After including Oracle's JARs, I get this exception while trying to use normal AppEngine Mail:

javax.mail.SendFailedException: Send failure (com.sun.mail.util.MailConnectException: Couldn't connect to host, port: localhost, 25; timeout -1 (java.net.SocketException: Permission denied: Attempt to access a blocked recipient without permission.))
at javax.mail.Transport.send(Transport.java:163)
at javax.mail.Transport.send(Transport.java:48)
at app.util.EmailUtil.sendMail_AppEngine(EmailUtil.java:948)

Update: Here is the code I use to send mails using OAuth

        Properties props = new Properties();
        props.put("mail.smtp.starttls.enable", "true");
        props.put("mail.smtp.starttls.required", "true");
        props.put("mail.smtp.sasl.enable", "false");
        Session session = Session.getInstance(props);
        session.setDebug(false);

        final URLName unusedUrlName = null;
        SMTPTransport transport = new SMTPTransport(session, unusedUrlName);
        transport.connect("smtp.gmail.com", 587, fromAddress, null);

 byte[] response = String.format("user=%s\1auth=Bearer %s\1\1", lFrom, oauthToken).getBytes();
        response = BASE64EncoderStream.encode(response);

        transport.issueCommand("AUTH XOAUTH2 " + new String(response),
                235);

        MimeMessage msg = new MimeMessage( session );

        msg.setFrom( new InternetAddress( fromAddress , fromName ) );
        msg.setRecipients( Message.RecipientType.TO , InternetAddress.parse( toAddress , false ) );

        msg.setSubject( emailSubject );
        MimeBodyPart htmlPart = new MimeBodyPart();
        Multipart multiPart = new MimeMultipart();

        htmlPart.setContent( "<html>email content</html>" , "text/html; charset=UTF-8" );
        multiPart.addBodyPart( htmlPart );
        msg.setContent( multiPart );
        msg.saveChanges();

        transport.sendMessage(msg, msg.getAllRecipients());

I hope someone helps me out here.

Kumar
  • 1,023
  • 1
  • 10
  • 23

1 Answers1

0

This code works for me :

import java.security.Provider;
import java.security.Security;
import java.util.Properties;
import java.util.logging.Logger;

import javax.mail.Session;
import javax.mail.URLName;

import com.sun.mail.gimap.GmailSSLStore;
import com.sun.mail.smtp.SMTPTransport;

/**
 * Performs OAuth2 authentication.
 * 
 * <p>
 * Before using this class, you must call {@code initialize} to install the
 * OAuth2 SASL provider.
 */
public class XOAuth2Authenticator {

    private static final Logger logger = Logger.getLogger(XOAuth2Authenticator.class.getName());

    public static final class OAuth2Provider extends Provider {
        private static final long serialVersionUID = 1L;

        public OAuth2Provider() {
            super("Google OAuth2 Provider", 1.0, "Provides the XOAUTH2 SASL Mechanism");
            put("SaslClientFactory.XOAUTH2", "be.vsko.davidgadget.server.gmail.OAuth2SaslClientFactory");
        }
    }

    /**
     * Installs the OAuth2 SASL provider. This must be called exactly once
     * before calling other methods on this class.
     */
    public static void initialize() {
        Security.addProvider(new OAuth2Provider());
    }

    /**
     * Connects and authenticates to an IMAP server with OAuth2. You must have
     * called {@code initialize}.
     * 
     * @param host
     *            Hostname of the imap server, for example
     *            {@code imap.googlemail.com}.
     * @param port
     *            Port of the imap server, for example 993.
     * @param userEmail
     *            Email address of the user to authenticate, for example
     *            {@code oauth@gmail.com}.
     * @param oauthToken
     *            The user's OAuth token.
     * @param debug
     *            Whether to enable debug logging on the IMAP connection.
     * 
     * @return An authenticated GmailSSLStore that can be used for IMAP operations.
     */
    public static GmailSSLStore connectToImap(String host, int port, String userEmail, String oauthToken, boolean debug)
            throws Exception {

        logger.info("connecting to IMAP for user " + userEmail);

        Properties props = new Properties();
        // props.put("mail.imaps.sasl.enable", "true");
        // props.put("mail.imaps.sasl.mechanisms", "XOAUTH2");
        props.put("mail.gimaps.sasl.enable", "true");
        props.put("mail.gimaps.sasl.mechanisms", "XOAUTH2");
        props.put(OAuth2SaslClientFactory.OAUTH_TOKEN_PROP, oauthToken);
        props.put("mail.store.protocol", "gimaps");
        Session session = Session.getInstance(props);
        props = session.getProperties();
        // props.put("mail.imaps.sasl.enable", "true");
        // props.put("mail.imaps.sasl.mechanisms", "XOAUTH2");
        props.put("mail.gimaps.sasl.enable", "true");
        props.put("mail.gimaps.sasl.mechanisms", "XOAUTH2");
        props.put(OAuth2SaslClientFactory.OAUTH_TOKEN_PROP, oauthToken);
        props.put("mail.store.protocol", "gimaps");
        session.setDebug(debug);

        final URLName unusedUrlName = null;
        final String emptyPassword = "";

        GmailSSLStore gmailStore = new GmailSSLStore(session, unusedUrlName);
        gmailStore.connect(host, port, userEmail, emptyPassword);

        logger.info("SUCCESS connecting to IMAP for user " + userEmail);

        return gmailStore;

        // IMAPSSLStore store = new IMAPSSLStore(session, unusedUrlName);
        // store.connect(host, port, userEmail, emptyPassword);
        // return store;
    }

    /**
     * Connects and authenticates to an SMTP server with OAuth2. You must have
     * called {@code initialize}.
     * 
     * @param host
     *            Hostname of the smtp server, for example
     *            {@code smtp.googlemail.com}.
     * @param port
     *            Port of the smtp server, for example 587.
     * @param userEmail
     *            Email address of the user to authenticate, for example
     *            {@code oauth@gmail.com}.
     * @param oauthToken
     *            The user's OAuth token.
     * @param debug
     *            Whether to enable debug logging on the connection.
     * 
     * @return An authenticated SMTPTransport that can be used for SMTP
     *         operations.
     */
    public static SMTPTransport connectToSmtp(Session session, String host, int port, String userEmail) throws Exception {

        logger.info("connecting to SMTP for user " + userEmail);

        final URLName unusedUrlName = null;
        SMTPTransport transport = new SMTPTransport(session, unusedUrlName);
        // If the password is non-null, SMTP tries to do AUTH LOGIN.
        final String emptyPassword = "";
        transport.connect(host, port, userEmail, emptyPassword);

        logger.info("SUCCESS connecting to SMTP for user " + userEmail);

        return transport;
    }

    public static Session getSmtpSession(String oauthToken, boolean debug) {
        Properties props = new Properties();
        props.put("mail.smtp.starttls.enable", "true");
        props.put("mail.smtp.starttls.required", "true");
        props.put("mail.smtp.sasl.enable", "true");
        props.put("mail.smtp.sasl.mechanisms", "XOAUTH2");
        props.put(OAuth2SaslClientFactory.OAUTH_TOKEN_PROP, oauthToken);
        Session session = Session.getInstance(props);
        props = session.getProperties();
        props.put("mail.smtp.starttls.enable", "true");
        props.put("mail.smtp.starttls.required", "true");
        props.put("mail.smtp.sasl.enable", "true");
        props.put("mail.smtp.sasl.mechanisms", "XOAUTH2");
        props.put(OAuth2SaslClientFactory.OAUTH_TOKEN_PROP, oauthToken);
        session.setDebug(debug);
        return session;
    }
}

With this in place, you can :

XOAuth2Authenticator.initialize();
Session smtpSession = XOAuth2Authenticator.getSmtpSession(oauthToken, true);
SMTPTransport transport = XOAuth2Authenticator.connectToSmtp(smtpSession, "smtp.gmail.com", 587, email);

and now use transport as usual.

PS: The easier way round IMO, is to use the new GMAIL api.

koma
  • 6,486
  • 2
  • 27
  • 53
  • Did you have to include a Oracle's JavaMail JAR file to import SMTPTransport? Because that is exactly what is causing problems for normal App Engine mail service. – Kumar Dec 11 '14 at 17:01
  • I'll check out the Gmail API too see if that can be used instead – Kumar Dec 11 '14 at 17:02
  • Okay I remember the problem with Gmail API. It uses HTTP requests. Sending mails with slightly larger attachments will cross the one-minute timeout limit. – Kumar Dec 11 '14 at 17:03
  • @ArjunKumar to prevent hitting the 1 min deadline, send your mails using the Task queue and your deadline will be 10 mins. – koma Dec 11 '14 at 17:12
  • I included the javamail jars and gimap-1.5.0.jar – koma Dec 11 '14 at 17:13
  • @ArjunKumar Sending mails using the Task queue is always better and will give you exponential backoff on top for free... . You will run into the same timeouts using java mail when sending out mails with larger attachments. – koma Dec 11 '14 at 17:15
  • Yes. I can't use Task Queues because they are bound by URLFetch limit too. I can't import Oracle's JavaMail JARs because they block access to App Engine Mail. Thanks for your suggestions so far. I'm still seriously hoping for a solution. – Kumar Dec 11 '14 at 18:21
  • @ArjunKumar URLFetch in the context of a task queue also has a max 10 min deadline. – koma Dec 11 '14 at 20:01
  • have the limits been revised? or are you contradicting your own answer? http://stackoverflow.com/a/19982022/1668384 – Kumar Dec 12 '14 at 04:17
  • Anyway, thanks. So, I guess Task Queues won't help. I'm still looking for any alternatives to SMTPTransport class or any other solution to this. – Kumar Dec 12 '14 at 11:44
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/66743/discussion-between-koma-and-arjunkumar). – koma Dec 12 '14 at 11:51