18
votes

I'm trying to send mails based on Gmail REST API using google java api services. I have configured through Google Develover Console an application client and downloaded p12 and json files.

I have used this sample programs, https://developers.google.com/gmail/api/guides/sending#sending_messages...

This sample works, but this is based on GoogleAuthorizationCodeFlow. I want just to work from server-to-server, invoking directly, not opening a browser to get an access token... and I got it (the access token) but finally i receive a Bad Request.... Why??? I don't receive more info just than "Bad Request" and "Precondition Failed"

Based on this i follow the next steps:

  1. First Step: Create a GoogleCredential object based on my client account mail and p12 generated file:

    GoogleCredential  credential = new GoogleCredential.Builder().setTransport(new NetHttpTransport())
                                        .setJsonFactory(new JacksonFactory())
                                        .setServiceAccountId(serviceAccountUserEmail)
                                        .setServiceAccountScopes(scopes)
                                        .setServiceAccountPrivateKeyFromP12File(
                                                    new java.io.File(SERVICE_ACCOUNT_PKCS12_FILE_PATH))                             
                                                            .build();
    

Here i have to point that i had many issues for ussing ClientID instead of ClientMail. It must use @developer.gserviceaccount.com account instead of .apps.googleusercontent.com . If u don't send this params ok , u get an "INVALID GRANT" error. This is explained here: https://developers.google.com/analytics/devguides/config/mgmt/v3/mgmtAuthorization

  1. Second Step : Create the Gmail Service based on the credentials:

    Gmail gmailService = new Gmail.Builder(httpTransport,
                                                  jsonFactory,
                                                  credential)
                                                .setApplicationName(APP_NAME)
                                                    .build();
    
  2. Third Step Create a Google raw message from a MimmeMessage:

    private static Message _createMessageWithEmail(final MimeMessage email) throws MessagingException, IOException {
    
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    email.writeTo(bytes);
    String encodedEmail =       Base64.encodeBase64URLSafeString(bytes.toByteArray());      
    Message message = new Message();    
    message.setRaw(encodedEmail);
    return message;
    

    }

  3. Fourth Step Invoke the service:

        Message message = _createMessageWithEmail(email);
    message = service.users()
    .messages()
    .send(userId,message)
    .execute();
    
  4. Fifth Step : Get Result after execution...Here i receive the exception:

Exception in thread "main"

com.google.api.client.googleapis.json.GoogleJsonResponseException: 400 Bad Request
{
  "code" : 400,
  "errors" : [ {
    "domain" : "global",
    "message" : "Bad Request",
    "reason" : "failedPrecondition"
  } ],
  "message" : "Bad Request"
}
    at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:145)

Any idea what is wrong or which is the Precondition failed??

2
have you ever found out what was wrong? I have the same issue.botenvouwer

2 Answers

27
votes

This is how I managed to get it work with a Google Apps domain:

1.- Using a google apps user open the developer console

2.- Create a new project (ie MyProject)

3.- Go to [Apis & auth] > [Credentials] and create a new [Service Account] client ID

4.- Copy the [service account]'s [Client ID] (the one like xxx.apps.googleusercontent.com) for later use

5.- Now you have to Delegate domain-wide authority to the service account in order to authorize your appl to access user data on behalf of users in the Google Apps domain ... so go to your google apps domain admin console

6.- Go to the [Security] section and find the [Advanced Settings] (it might be hidden so you'd have to click [Show more..])

7.- Click con [Manage API Client Access]

8.- Paste the [Client ID] you previously copied at [4] into the [Client Name] text box.

9.- To grant your app full access to gmail, at the [API Scopes] text box enter: https://mail.google.com, https://www.googleapis.com/auth/gmail.compose, https://www.googleapis.com/auth/gmail.modify, https://www.googleapis.com/auth/gmail.readonly (it's very important that you enter ALL the scopes)


Now you've done all the settings... now the code:

1.- Create an HttpTransport

private static HttpTransport _createHttpTransport() throws GeneralSecurityException,
                                                           IOException { 
    HttpTransport httpTransport = new NetHttpTransport.Builder()                                                 
                                                      .trustCertificates(GoogleUtils.getCertificateTrustStore())
                          .build();
    return httpTransport;
}

2.- Create a JSonFactory

private static JsonFactory _createJsonFactory() {
    JsonFactory jsonFactory = new JacksonFactory();
    return jsonFactory;
}

3.- Create a google credential

private static GoogleCredential _createCredentialUsingServerToken(final HttpTransport httpTransport,
                                                                  final JsonFactory jsonFactory) throws IOException,
                                                                                                        GeneralSecurityException {
    // Use the client ID when making the OAuth 2.0 access token request (see Google's OAuth 2.0 Service Account documentation).
    String serviceAccountClientID = "327116756300-thcjqf1mvrn0geefnu6ef3pe2sm61i2q.apps.googleusercontent.com"; 

    // Use the email address when granting the service account access to supported Google APIs 
    String serviceAccountUserEmail = "[email protected]";

    GoogleCredential credential = new GoogleCredential.Builder()
                                                .setTransport(httpTransport)
                                                .setJsonFactory(jsonFactory)
                                                .setServiceAccountId(serviceAccountUserEmail)    // requesting the token
                                                .setServiceAccountPrivateKeyFromP12File(new File(SERVER_P12_SECRET_PATH))
                                                .setServiceAccountScopes(SCOPES)    // see https://developers.google.com/gmail/api/auth/scopes
                                                .setServiceAccountUser("[email protected]")
                                                .build();    
    credential.refreshToken();
    return credential;
}

NOTE: At setServiceAccountUser() method use any user from your google apps domain

4.- Create a Gmail Service

private static Gmail _createGmailService(final HttpTransport httpTransport,
                                         final JsonFactory jsonFactory,
                                         final GoogleCredential credential) {
    Gmail gmailService = new Gmail.Builder(httpTransport,
                                            jsonFactory,
                                            credential)
                                   .setApplicationName(APP_NAME)
                                   .build();
    return gmailService;
}

Now you can do whatever you want with the GmailService like send an email ;-) as described at https://developers.google.com/gmail/api/guides/sending

3
votes

Thank you so much for the detailed answer. I was trying to make this work for asp.net MVC. But after following everything from the given steps, my code was giving same error.

After spending 2 days to solve this error, i could solve this in following way. In ServiceAccountCredential.Initializer constructor, You need to specify the user id you are trying to impersonate with this service account.

    ServiceAccountCredential credential = new ServiceAccountCredential(
               new ServiceAccountCredential.Initializer(serviceAccountEmail)
               {
                   User = "<USER@YOUR_GSUIT_DOMAIN.COM>",
                   Scopes = new[] { GmailService.Scope.MailGoogleCom }
               }.FromPrivateKey(<YOUR_PRIVATE_KEY>);

    if (credential != null)
                {
                    var service = new GmailService(new BaseClientService.Initializer
                    {
                        HttpClientInitializer = credential,
                        ApplicationName = "Enquiry Email Sender"
                    });
    
                    var list = service.Users.Messages.List("me").Execute();
                    var count = "Message Count is: " + list.Messages.Count();
    
                    return count;
    
                }