Help us understand the problem. What is going on with this article?

サービスアカウントで認証してGMail APIでメール送信(Java)

More than 1 year has passed since last update.

はじめに

前回 は実行時にメール送信元アカウントに OAuth認証 させました。
今回はその代わりにいい感じに設定した サービスアカウント を使用します。

いい感じになったサービスアカウントはメール送信元アカウントから委譲された権限でメール送信します。
どちらの実装が適当かは要件によると思いますが、今回は前回と比較して次のような違いがあります。

  • OAuth認証画面は出てこない
  • トークン期限切れの考慮が不要
  • メール送信元アカウントはGSuite上のアカウントでなければならない

なお今回試した内容の 公式ガイドはこのへん とかにあります・・
ちょっとでも指定を間違うと実行時に原因不明のエラーが出て辛い。。

修正の概要

前回作成したものをベースにして次の通り修正します。

(1/3) GCPのウェブUIでサービスアカウントを作成する
(2/3) GSuiteのウェブUIでサービスアカウントに権限を委譲する設定をする
(3/3) サンプルソースの認証部分を修正する

試してみる

そんなわけでやってみます。

(1/3) GCPのウェブUIでサービスアカウントを作成する

サービスアカウントを作成します。
作成したら後の工程で使う次の3点も取得します。

  • サービスアカウントのクライアントID(20桁ぐらいの数値)
  • サービスアカウントのメール(...@....iam.gserviceaccount.comみたいなメール)
  • サービスアカウントの秘密鍵ファイル(p12形式)

サービスアカウントを作る
image.png

image.png

出来たアカウントのオプションを修正する
image.png

image.png

サービスアカウントのクライアントIDとメールを取得する
image.png

image.png

秘密鍵を取得する
image.png

(2/3) GSuiteのウェブUIでサービスアカウントに権限を委譲する設定をする

image.png

image.png

image.png

Gmail APIのスコープは こちら に記載されています。
今回はメール送信のみなので https://www.googleapis.com/auth/gmail.send を指定します。
なお、ここで指定したスコープと認証処理で指定するスコープは 完全に一致 している必要があるようです。

またメール送信元として使用するアカウントのメール(GSuiteログイン時に指定するメール)も控えておきます。

(3/3) サンプルソースの認証部分を修正する

前回のサンプルソース全体は こちら にあります。

上記を取得した後、SendingMailUsingGmailApi.java を下記に差し替え、
さらにコメントを参考にサービスアカウントのメールほかを入れます。

SendingMailUsingGmailApi.java
package com.example;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.Base64;
import com.google.api.services.gmail.Gmail;
import com.google.api.services.gmail.GmailScopes;
import com.google.api.services.gmail.model.Message;

public class SendingMailUsingGmailApi {

    private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();

    private static final String SERVICE_ACCOUNT_EMAIL = "[(1/3)のサービスアカウントのメール]";        // サービスアカウントのメール
    private static final String SERVICE_ACCOUNT_P12_FILE_PATH = "[(1/3)の秘密鍵のフルパス]";         // サービスアカウントの秘密鍵(p12形式)
    private static final List<String> SCOPES = Collections.singletonList(GmailScopes.GMAIL_SEND);  // スコープ (2/3)での指定と一致している必要あり
    private static final String GSUITE_USER_EMAIL = "[(2/3)の送信元アカウントのメール]";              // GSuite上のメール送信元アカウント

    public static void main(String... args) {

        try {
            final NetHttpTransport TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();

            GoogleCredential credential = new GoogleCredential.Builder().setTransport(TRANSPORT)
                    .setJsonFactory(JSON_FACTORY)
                    .setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
                    .setServiceAccountPrivateKeyFromP12File(new File(SERVICE_ACCOUNT_P12_FILE_PATH))
                    .setServiceAccountScopes(SCOPES)
                    .setServiceAccountUser(GSUITE_USER_EMAIL)
                    .build();

            Gmail service = new Gmail.Builder(TRANSPORT, JSON_FACTORY, credential).setApplicationName("demo").build();

            // (b-3) メール作成
            String to = "[ここに送信先メールアドレス]"; // FIXME
            MimeMessage mimeMessage = createEmail(to, null, "送ってみるテスト", "こいつ…動くぞ!");

            // (b-4) メール送信
            sendMessage(service, "me", mimeMessage);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * メール作成と送信
     *
     * これ以降はほぼ次の公式サンプルのまま
     * https://developers.google.com/gmail/api/guides/sending
     */
    public static MimeMessage createEmail(String to, String from, String subject, String bodyText)
            throws MessagingException {

        Properties props = new Properties();
        Session session = Session.getDefaultInstance(props, null);

        MimeMessage email = new MimeMessage(session);

//        InternetAddress fromAddress = new InternetAddress(from);
//        email.setFrom(fromAddress);
//        email.setReplyTo(new InternetAddress[] { fromAddress });

        email.addRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress(to));
        email.setSubject(subject);
        email.setText(bodyText);
        return email;
    }

    public static Message createMessageWithEmail(MimeMessage emailContent) throws MessagingException, IOException {

        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        emailContent.writeTo(buffer);
        byte[] bytes = buffer.toByteArray();
        String encodedEmail = Base64.encodeBase64URLSafeString(bytes);
        Message message = new Message();
        message.setRaw(encodedEmail);
        return message;
    }

    public static Message sendMessage(Gmail service, String userId, MimeMessage emailContent)
            throws MessagingException, IOException {

        Message message = createMessageWithEmail(emailContent);
        message = service.users().messages().send(userId, message).execute();

        System.out.println("Message id: " + message.getId());
        System.out.println(message.toPrettyString());
        return message;
    }
}

これにて修正は完了です。
あとは適当な方法で実行してみてください。

以上です。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした