25

Desired Behaviour

Use Gmail, OAuth2 and Nodemailer to send an email from a server side node.js file.

What I've Tried

Relevant Documentation

https://nodemailer.com/smtp/oauth2
https://nodemailer.com/usage/using-gmail
https://developers.google.com/gmail/api/auth/web-server

Relevant Questions

send emails from MY gmail account with OAuth2 and nodemailer
How do I authorise an app (web or installed) without user intervention?
https://stackoverflow.com/a/47936349
https://stackoverflow.com/a/22572776

There were gaps in the instructions of the above sources and some information was outdated, so the answer below was my final implementation which appears to be working.

I'm posting this solution for confirmation it is best practice and, if it is, to save others time.

user1063287
  • 10,265
  • 25
  • 122
  • 218

2 Answers2

59

The following worked for me, there are two parts:

01) app.js

02) Google and OAuth2 setup


app.js

var nodemailer = require("nodemailer");

var transporter = nodemailer.createTransport({
    host: 'smtp.gmail.com',
    port: 465,
    secure: true,
    auth: {
        type: 'OAuth2',
        user: local_settings.my_gmail_username,
        clientId: local_settings.my_oauth_client_id,
        clientSecret: local_settings.my_oauth_client_secret,
        refreshToken: local_settings.my_oauth_refresh_token,
        accessToken: local_settings.my_oauth_access_token
    }
});


var mail = {
    from: "John Smith <me@mydomain.com>",
    to: "user@userdomain.com",
    subject: "Registration successful",
    text: "You successfully registered an account at www.mydomain.com",
    html: "<p>You successfully registered an account at www.mydomain.com</p>"
}

transporter.sendMail(mail, function(err, info) {
    if (err) {
        console.log(err);
    } else {
        // see https://nodemailer.com/usage
        console.log("info.messageId: " + info.messageId);
        console.log("info.envelope: " + info.envelope);
        console.log("info.accepted: " + info.accepted);
        console.log("info.rejected: " + info.rejected);
        console.log("info.pending: " + info.pending);
        console.log("info.response: " + info.response);
    }
    transporter.close();
});

Google and OAuth Setup

The code above requires the following setup:

01) Go to https://console.developers.google.com

02) If you don't have a project, you will be prompted to create one

enter image description here

03) Click on Create Project

04) Click on Create

enter image description here
05) Enter a Project Name and click Create

enter image description here

06) Select the Gmail API

enter image description here

07) Click on Enable

enter image description here

08) Click on Create Credentials

enter image description here

09) Enter the required settings

enter image description here

10) Give the OAuth client a name and ensure you add https://developers.google.com/oauthplayground as a redirect URI in order to generate the refresh and access tokens later

enter image description here
11) Define the consent screen settings

enter image description here

12) Click I'll do this later and Done

enter image description here

13) Click on the Edit icon, to view your Client ID and Client Secret

enter image description here

14) To generate access and refresh tokens, go to https://developers.google.com/oauthplayground

15) Click on the cog icon in the top right, check Use your own OAuth credentials and enter Client ID and Client Secret

enter image description here

16) In the left column, select Gmail API v1 and click Authorise APIs

enter image description here

17) If you are signed into multiple accounts, when prompted select the relevant account

enter image description here

18) Click Allow

enter image description here

19) Click Exchange authorisation code for tokens

enter image description here
I'm not sure why there is a count down on the access token, but hopefully the message at the bottom of the screen means that the token won't expire.

user1063287
  • 10,265
  • 25
  • 122
  • 218
  • Why do you call `transporter.close();`, is it needed for every email? – rraallvv Oct 19 '18 at 11:11
  • 6
    You save my day !!! This answer deserves more votes. Hand down for the best answer !!! – Hoang Minh Jun 20 '19 at 06:29
  • 5
    i wish i could give you 1000 upvotes, this was a huge help – ReganPerkins Aug 19 '19 at 20:48
  • 2
    This was so helpful! Thank you for posting it. Great instruction and totally worked for me. –  Jan 02 '20 at 00:28
  • This worked great with my G Suite account - However, I cant figure out how to make it work with my email aliases with my G Suite account! Do you know how I could send emails from the email alias of my account? – user12669401 May 11 '20 at 20:35
  • How do I create and submit a Oauth demo video for verification after this? – user1071182 Oct 02 '20 at 18:56
  • I just tried and it seems to work! (The UI changed a bit though.) What about the expiration of the token? The token is said to be temporary and to expire - what will happen when the token expires? Will I need to re-create the token every once in a while? – brillout Nov 06 '20 at 19:09
  • 2
    @brillout - a few people have mentioned this in the comments, and i can only speak from my experience implementing it as shown in the post, but two years after posting the solution it is still working for me, and i haven't had to recreate any token. – user1063287 Nov 07 '20 at 07:01
  • @user1063287 Actually in production (an AWS EC2 instance) Gmail doesn't send out any email, even though nodemailer gives me a `250 OK` response. It works on my local dev machine. Has anyone else also had problems in production? – brillout Nov 12 '20 at 07:24
  • @brillout - if it is working on dev machine, and nodemailer is giving the desired response in both dev and production environments, is it possible the issue is limited to the amazon environment, and therefore should be troubleshooted there (via amazon support ticket or even a new post on stack overflow detailing the issue)? my implementation is on red hat openshift and email is sending as desired. – user1063287 Nov 13 '20 at 08:47
  • If you're trying to use service accounts for this see : https://medium.com/@imre_7961/nodemailer-with-g-suite-oauth2-4c86049f778a . If you get "Error: Missing credentials for "PLAIN" ... code: 'EAUTH', command: 'API'" check for typos – Kevin Danikowski Feb 17 '21 at 19:40
  • The refresh token can expire after 200 days. Also there is a send quota. If you want a large scale production solution, use amazon ses or mailgun or some proper smtp provider service – Ethan SK Apr 29 '21 at 14:17
  • 1
    I know this is old, but I am currently going through this same headache, and I have already done exactly everything you showed, and I was able to send emails. But according to the Oauth documentation, if you are using an app in Testing state, ie. not a verified app, the refresh token expires every 7 days, and there is a maximum 50 refresh tokens per account. So this solution is kind of a band-aid as I am only wanting to use this to simply send an email from my website. I shouldn't need to verify an app to send an email from myself to myself. Now I'm trying to figure how to use a service acct – leisheng Apr 30 '21 at 22:36
  • For token generation reference checkout this blog, https://medium.com/@nickroach_50526/sending-emails-with-node-js-using-smtp-gmail-and-oauth2-316fe9c790a1#:~:text=accessToken%20%3D%20oauth2Client.getAccessToken() – Nikhil Bhandarkar Aug 27 '21 at 09:32
4

OAuth Consent Screen

You are definetely right about the gaps and outdated information, and you did a really great job on documenting the steps needed to use Gmail with OAuth and nodemailer! Nevertheless, I think it worths mentioning that in the Credentials page there is another step: the OAuth Consent Screen tab.

It contains a form like a Google Play app submission that requires validation from Google, if you choose your app to not being validated, you have a limitation of 100 calls of what they call Sensitive scopes before being asked for submission.

What about quota?

It's still not clear to me if this 100 calls quota will be consumed even if you don't select any additional permission to use sensitive scopes (the default ones are email, profile, openid). I hope not, since the OAuth Consent Screen asks for things like the Application Homepage Link and Authorised domains that is something you might not have if you are working on a backend application.

I think that this whole procedure is really slow and uselessly complex since most people do all these steps to just send an email from their app using nodemailer...

Community
  • 1
  • 1
Gono
  • 41
  • 3