Gmail API 사용 활성화 및 사용자 인증 정보 생성
GCP 프로젝트를 생선한 후, 왼쪽 사이드바에서 [API 및 서비스] - [라이브러리]에 진입한다.
gmail을 검색해서 들어간 후에 [사용] 버튼을 클릭한다. (이미 사용중인 경우 [관리] 버튼으로 대체되어 있다.)
조금 기다린 후 [사용]이 [관리] 버튼으로 변경되면(안 되면 페이지를 새로고침 해보자), 버튼을 눌러 관리 페이지로 진입한다.
위의 [+ 사용자 인증 정보 만들기] 버튼을 누르고 [OAuth 클라이언트 ID 만들기]를 선택한다.
맨 처음 사용자 인증 정보를 만드는 경우, OAuth 동의 화면을 먼저 작성해야 할 수 있다. (이미 작성한 경우 생략)
OAuth 동의 화면은 안내에 따라 간단히 생성할 수 있다.
앱을 게시할 필요는 없다. 다만 테스트 사용자에 본인 계정을 추가해야 본인이 사용할 수 있으니 등록하도록 한다.
다시 사용자 인증 정보를 만드는 것으로 돌아와서,
애플리케이션 유형은 특별한 경우가 아닌 이상 "데스크톱 앱"을 선택한다. 이름은 자유롭게 정한다.
방금 만든 OAuth 클라이언트에 대한 정보를 확인할 수있다. 다운로드 버튼을 눌러 JSON 파일을 다운받는다.
코드 작성 및 실행
먼저 프로젝트를 초기화하고 googleapis 패키지를 설치한다.
npm init
npm i googleapis
package.json에 다음을 추가한다. (불필요한 부분은 생략했다.)
{
"scripts": {
"mailer": "node mailer.js"
},
"type": "module"
}
앞서 다운받은 JSON 파일을 프로젝트 폴더에 추가한다. 나는 편의상 credentials.json라고 이름을 바꿨다.
mailer.js 파일을 만들고 내용은 다음과 같이 작성한다. (80, 81줄의 발신/수신 이메일 주소는 적당히 설정한다. 발신 이메일 주소의 경우 앞서 OAuth 동의 화면에 등록된 테스트 사용자의 것이어야 한다.)
/* eslint-disable no-undef */
import fs from 'fs';
import readline from 'readline';
import { google } from 'googleapis';
// To check all scopes: https://developers.google.com/identity/protocols/oauth2/scopes?hl=en
// If modifying these scopes, delete token.json.
const SCOPES = [
'https://mail.google.com/',
'https://www.googleapis.com/auth/gmail.send',
];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Gmail API.
authorize(JSON.parse(content), sendMail);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* @param {Object} credentials The authorization client credentials.
* @param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
const {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getNewToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* @param {getEventsCallback} callback The callback for the authorized client.
*/
function getNewToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Enter the code from that page here: ', (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
function sendMail(auth) {
const gmail = google.gmail({version: 'v1', auth});
gmail.users.messages.send({
userId: 'me',
requestBody: {
raw: base64Encode(
'From: 이메일@gmail.com\n' +
'To: 이메일@naver.com\n' +
'Subject: "Test Message"\n' +
'MIME-Version: 1.0\n' +
'Content-Type: text/plain; charset="UTF-8"\n' +
'Content-Transfer-Encoding: message/rfc2822\n' + // 7bit, quoted-printable
'\n' +
'This is a test message.\n'
),
},
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
const messageId = res.data.id;
console.log(`Message sent with ID: ${messageId}`);
});
}
const base64Encode = (message) => {
return Buffer.from(message)
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_');
};
이제 프로젝트 폴더구조는 다음과 같다.
- /
- node_modules
- mailer.js
- credentials.json
- package-lock.json
- package.json
다음을 실행한다.
npm run mailer
대략 이런 내용이 출력된다. 해당 링크로 방문하여 로그인 후 액세스를 허용하면 코드를 알려준다. 코드를 복사해서 터미널에 입력해주면 된다.
Authorize this app by visiting this url: https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&scope=https%3A%2F%2Fmail.google.com%2F%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fgmail.send&response_type=code&client_id=&redirect_uri=
Enter the code from that page here:
프로젝트 폴더에 token.json 파일이 생성되었을 것이다.
그리고 코드와 설정에 문제가 없었다면 메일까지 보내졌을 것이다.
개선
글을 쓴 이후, 토큰 갱신 기능 추가 등 개선을 여러 번 했다.
https://github.com/gunhoflash/mailer 에서 확인할 수 있다.
참고
https://github.com/googleworkspace/node-samples/blob/master/gmail/quickstart/index.js
https://developers.google.com/identity/protocols/oauth2
https://developers.google.com/gmail/api/quickstart/nodejs
https://developers.google.com/gmail/api/guides/sending
https://developers.google.com/identity/protocols/oauth2/scopes?hl=en#gmail