FAILED_PRECONDITION - Gmail API (message send)

Gmail APIs

Trying to send a mail using a "Service Account" & getting following error.

 

 

POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send
{
  "code": 400,
  "errors": [
    {
      "domain": "global",
      "message": "Precondition check failed.",
      "reason": "failedPrecondition"
    }
  ],
  "message": "Precondition check failed.",
  "status": "FAILED_PRECONDITION"
}

 

 

 

I am using following sample.

https://github.com/googleworkspace/java-samples/blob/main/gmail/snippets/src/main/java/SendMessage.j...

  1. I am using a "Service Account"
  2. I have also enabled "Domain wide delegation" from GSuite Admin
  3. I have also provided the credentials JSON file
  4. Please find below my sample code

 

 

/*
 * This Java source file was generated by the Gradle 'init' task.
 */
package com.gmail;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Properties;

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

import org.apache.commons.codec.binary.Base64;

import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.gmail.Gmail;
import com.google.api.services.gmail.GmailScopes;
import com.google.api.services.gmail.model.Message;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;

/* Class to demonstrate the use of Gmail Send Message API */
public class App {

	/**
	 * Send an email from the user's mailbox to its recipient.
	 *
	 *  fromEmailAddress - Email address to appear in the from: header
	 *  toEmailAddress   - Email address of the recipient
	 * @return the sent message, {@code null} otherwise.
	 * @throws MessagingException - if a wrongly formatted address is encountered.
	 * @throws IOException        - if service account credentials file not found.
	 */
	public static Message sendEmail(final String fromEmailAddress, final String toEmailAddress)
			throws MessagingException, IOException {
		/*
		 * Load pre-authorized user credentials from the environment. TODO(developer) -
		 * See https://developers.google.com/identity for guides on implementing OAuth2
		 * for your application.
		 */
		final GoogleCredentials credentials = GoogleCredentials.getApplicationDefault()
				.createScoped(GmailScopes.GMAIL_SEND);
		final HttpRequestInitializer requestInitializer = new HttpCredentialsAdapter(credentials);

		// Create the gmail API client
		final Gmail service = new Gmail.Builder(new NetHttpTransport(), GsonFactory.getDefaultInstance(),
				requestInitializer).setApplicationName("Gmail samples").build();

		// Create the email content
		final String messageSubject = "Test message";
		final String bodyText = "lorem ipsum.";

		// Encode as MIME message
		final Properties props = new Properties();
		final Session session = Session.getDefaultInstance(props, null);
		final MimeMessage email = new MimeMessage(session);
		email.setFrom(new InternetAddress(fromEmailAddress));
		email.addRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress(toEmailAddress));
		email.setSubject(messageSubject);
		email.setText(bodyText);

		// Encode and wrap the MIME message into a gmail message
		final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
		email.writeTo(buffer);
		final byte[] rawMessageBytes = buffer.toByteArray();
		final String encodedEmail = Base64.encodeBase64URLSafeString(rawMessageBytes);
		Message message = new Message();
		message.setRaw(encodedEmail);

		try {
			// Create send message
			message = service.users().messages().send("me", message).execute();
			System.out.println("Message id: " + message.getId());
			System.out.println(message.toPrettyString());
			return message;
		} catch (final GoogleJsonResponseException e) {
			// TODO(developer) - handle error appropriately
			final GoogleJsonError error = e.getDetails();
			if (error.getCode() == 403) {
				System.err.println("Unable to send message: " + e.getDetails());
			} else {
				throw e;
			}
		}
		return null;
	}

	public static void main(final String[] a) throws MessagingException, IOException {
		sendEmail("<PII removed by staff>", "<<PII removed by staff>>");
	}
}

 

 

1 4 6,886
4 REPLIES 4

You have to specify the user you are looking to impersonate when using domain wide delegation.  It should be something like this:

 

final GoogleCredentials credentials = GoogleCredentials.getApplicationDefault()
				.createScoped(GmailScopes.GMAIL_SEND).CreateWithUser("user@example.com")

 

  The library should auto-populate the "CreateWithUser"  or 

 .createDelegated("user@example.com");

com.google.auth.oauth2.GoogleAuthException: Error getting access token for service account: 401 Unauthorized

I am getting above error now with the following change.

 

 

 

GoogleCredentials credentials = GoogleCredentials.getApplicationDefault()
				.createScoped(GmailScopes.GMAIL_SEND).createDelegated("<PII removed by staff>")

 

 

 

  • The key file is correct
  • Domain wide delegation is also present (see screenshot)

What could be missing? I did search on the internet & even the docs are easier for setup with service account.

Use-case:

I need to use Gmail api to access all mailboxes under my domain. So that my app can send emails based on application needs. Currently, I am doing it by enabling Less secure access (password based) which is not a recommended way.

Is using service account the right choice or should I use individual users Oauth authentication?

delegation.png

I tried creating a VM with service account, but then I get following error.

{"code":403,"details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"ACCESS_TOKEN_SCOPE_INSUFFICIENT"}],"errors":[{"domain":"global","message":"Insufficient Permission","reason":"insufficientPermissions"}],"message":"Request had insufficient authentication scopes.","status":"PERMISSION_DENIED"}
  1. I have used the scope as GmailScopes.MAIL_GOOGLE_COM in the code
  2. You can see the image in previous reply, the scope is present on the workspace console

Finally, following worked.

  • The key json file path must be provided in environment varialble (GOOGLE_APPLICATION_CREDENTIALS)

I have some questions:

  1. When I ran my VM as a service account (specifically created for mailing), I thought there is no need to provide key json file? But it resulted in ACCESS_TOKEN_SCOPE_INSUFFICIENT.
  2. Instance attached user-managed service account should ideally be automatically used by api libraries from Application Default Credentials. Isn't it?
  3. Ideally Application Default Credentials should get credentials automatically, as can be seen with the below command result.
    1. gcloud auth application-default login
    2. Above command gives following message:
      1. You are running on a Google Compute Engine virtual machine.
        The service credentials associated with this virtual machine
        will automatically be used by Application Default
        Credentials, so it is not necessary to use this command.
  4. Is this not working because I am logging in to VM using my cloud console admin account. And App Default Cred is using my credentials (instead of using instance attached service account credentials)?