Viewed   125 times

I expected the code below to send an email, but I'm only getting this:

An error occurred: Error calling POST https://www.googleapis.com/gmail/v1/users/me/messages/send: (400) Bad Request

I get a 200 OK using the Google Developers Console here at the bottom. Any help?

$client_id = '599901532082-js1r50n20q6n5mir9fo1g81qkj9kfn3j.apps.googleusercontent.com';
$service_account_name = '599901532082-js1r50n20q6n5mir9fo1g81qkj9kfn3j@developer.gserviceaccount.com';
$key_file_location = '/tmp/APIProject-cb6558ba6435.p12';

$client = new Google_Client();
$client->setApplicationName("Client_Library_Examples");
$service = new Google_Service_Gmail($client);  

if (isset($_SESSION['service_token'])) {
  $client->setAccessToken($_SESSION['service_token']);
}
$key = file_get_contents($key_file_location);
$cred = new Google_Auth_AssertionCredentials(
  $service_account_name,
  array('https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.compose'),
  $key
);
$client->setAssertionCredentials($cred);

if ($client->getAuth()->isAccessTokenExpired()) {
  $client->getAuth()->refreshTokenWithAssertion($cred);
}
//check if you want the validity of this string at: http://www.komeil.com/toolbox/base64decoder
//it is web safe base64 encoded email
$mime = "RnJvbTogSm9obiBEb2UgPHRpcmVuZ2FyZmlvQGdtYWlsLmVzPiANClRvOiBNYXJ5IFNtaXRoIDx0aXJlbmdhcmZpb0BnbWFpbC5jb20-IA0KU3ViamVjdDogU2F5aW5nIEhlbGxvIA0KRGF0ZTogRnJpLCAyMSBOb3YgMTk5NyAwOTo1NTowNiAtMDYwMCANCk1lc3NhZ2UtSUQ6IDwxMjM0QGxvY2FsLm1hY2hpbmUuZXhhbXBsZT4NCg0KVGhpcyBpcyBhIG1lc3NhZ2UganVzdCB0byBzYXkgaGVsbG8uIFNvLCAiSGVsbG8iLg==";


$service = new Google_Service_Gmail($client);

$msg = new Google_Service_Gmail_Message();
$msg->setRaw($mime);

try {
  $results = $service->users_messages->send("me", $msg);
  print 'Message with ID: ' . $message->getId() . ' sent.';
  return $message;
} catch (Exception $e) {
  print 'An error occurred: ' . $e->getMessage();

}

EDIT:

this is the request object. It includes the response data also:

object(Google_Http_Request)[508]
  private 'batchHeaders' => 
    array (size=3)
      'Content-Type' => string 'application/http' (length=16)
      'Content-Transfer-Encoding' => string 'binary' (length=6)
      'MIME-Version' => string '1.0' (length=3)
  protected 'queryParams' => 
    array (size=0)
      empty
  protected 'requestMethod' => string 'POST' (length=4)
  protected 'requestHeaders' => 
    array (size=3)
      'content-type' => string 'application/json; charset=UTF-8' (length=31)
      'authorization' => string 'Bearer ya29.8gEUMiBLfxS8OLdSmpiQ-EcumeATo2qFAfPtPqwTw9fQ2zVrfZaA1X5OLoBmQccrXr8V8g' (length=82)
      'accept-encoding' => string 'gzip' (length=4)
  protected 'baseComponent' => string 'https://www.googleapis.com' (length=26)
  protected 'path' => string '/gmail/v1/users/me/messages/send' (length=32)
  protected 'postBody' => string '{"raw":"RnJvbTogSm9obiBEb2UgPHRpcmVuZ2FyZmlvQGdtYWlsLmVzPg0KVG86IE1hcnkgU21pdGggPHRpcmVuZ2FyZmlvQGdtYWlsLmNvbT4NClN1YmplY3Q6IFNheWluZyBIZWxsbw0KDQpUaGlzIGlzIGEgbWVzc2FnZSBqdXN0IHRvIHNheSBoZWxsby4gU28sICdIZWxsbycu"}' (length=214)
  protected 'userAgent' => string 'Client_Library_Examples google-api-php-client/1.0.6-beta (gzip)' (length=63)
  protected 'canGzip' => boolean true
  protected 'responseHttpCode' => null
  protected 'responseHeaders' => null
  protected 'responseBody' => null
  protected 'expectedClass' => string 'Google_Service_Gmail_Message' (length=28)
  public 'accessKey' => null

object(Google_Http_Request)[508]
  private 'batchHeaders' => 
    array (size=3)
      'Content-Type' => string 'application/http' (length=16)
      'Content-Transfer-Encoding' => string 'binary' (length=6)
      'MIME-Version' => string '1.0' (length=3)
  protected 'queryParams' => 
    array (size=0)
      empty
  protected 'requestMethod' => string 'POST' (length=4)
  protected 'requestHeaders' => 
    array (size=4)
      'content-type' => string 'application/json; charset=UTF-8' (length=31)
      'authorization' => string 'Bearer ya29.8gEUM***fxS8OLdSmpiQ-EcumeATo2qFAfPtPqwTw9fQ2zVrfZaA1X5OLoBmQccrXr8V8g' (length=82)
      'accept-encoding' => string 'gzip' (length=4)
      'content-length' => int 214
  protected 'baseComponent' => string 'https://www.googleapis.com' (length=26)
  protected 'path' => string '/gmail/v1/users/me/messages/send' (length=32)
  protected 'postBody' => string '{"raw":"RnJvbTogSm9obiBEb2UgPHRpcmVuZ2FyZmlvQGdtYWlsLmVzPg0KVG86IE1hcnkgU21pdGggPHRpcmVuZ2FyZmlvQGdtYWlsLmNvbT4NClN1YmplY3Q6IFNheWluZyBIZWxsbw0KDQpUaGlzIGlzIGEgbWVzc2FnZSBqdXN0IHRvIHNheSBoZWxsby4gU28sICdIZWxsbycu"}' (length=214)
  protected 'userAgent' => string 'Client_Library_Examples google-api-php-client/1.0.6-beta (gzip)' (length=63)
  protected 'canGzip' => boolean true
  protected 'responseHttpCode' => int 400
  protected 'responseHeaders' => 
    array (size=13)
      'vary' => string 'Origin
X-Origin' (length=15)
      'content-type' => string 'application/json; charset=UTF-8' (length=31)
      'content-encoding' => string 'gzip' (length=4)
      'date' => string 'Fri, 18 Sep 2015 08:34:25 GMT' (length=29)
      'expires' => string 'Fri, 18 Sep 2015 08:34:25 GMT' (length=29)
      'cache-control' => string 'private, max-age=0' (length=18)
      'x-content-type-options' => string 'nosniff' (length=7)
      'x-frame-options' => string 'SAMEORIGIN' (length=10)
      'x-xss-protection' => string '1; mode=block' (length=13)
      'server' => string 'GSE' (length=3)
      'alternate-protocol' => string '443:quic,p=1' (length=12)
      'alt-svc' => string 'quic=":443"; p="1"; ma=604800' (length=29)
      'transfer-encoding' => string 'chunked' (length=7)
  protected 'responseBody' => string '{
 "error": {
  "errors": [
   {
    "domain": "global",
    "reason": "failedPrecondition",
    "message": "Bad Request"
   }
  ],
  "code": 400,
  "message": "Bad Request"
 }
}
' (length=179)
  protected 'expectedClass' => string 'Google_Service_Gmail_Message' (length=28)
  public 'accessKey' => null

 Answers

4

finally got to send Mails with your code:

I think you have misunderstood the GMail API a little bit.

To use it, you must authenticate to the API. To do this, there are two ways:

  • use OAuth - the Server redirects the user to google's servers, where they can login, grant permission to your app, and pass a token back to you
  • Service Accounts. These are a little bit more complicated:
    • First, you'll have to setup an app (done)
    • second, you'll have to setup a service account. This is how your app authenticates to google. you've done that, and the certificate you've got contains the private key to authenticate
    • third, the user needs to grant your application access to act on behalf of them. This is the point you haven't done yet.

So what you're currently trying is to send mails from the service account, but this is not an GMail Account.

The Developer Console uses the OAuth method, so there's no problem to try this.

Please also note: With regular GMail Accounts, you can not use 'Service Accounts'. You'll have to use OAuth. To use Service Accounts, you need to be a Google Apps customer.

I won't conver OAuth authorization here, because it's completely different, and there are many examples out there.

To grant your Service Account Permissions to send mails on behalf of your GMails/Google Apps accounts, please follow this document. For One or More API Scopes, you'll have to enter https://mail.google.com/,https://www.googleapis.com/auth/gmail.modify,https://www.googleapis.com/auth/gmail.compose,https://www.googleapis.com/auth/gmail.send.

After you've setup this, it's possible to send mails, just modify the code as follows:

$results = $service->users_messages->send("me", $msg);

won't work, because 'me' referrs to the service account, which can't send mail (see above). Replace me with the user id (mail-address) of the account from which the mails should be send.:

$results = $service->users_messages->send("senders_mail@domain.com", $msg);

Then, you'll need to add

$cred->sub = 'senders_mail@domain.com';

below

$cred = new Google_Auth_AssertionCredentials(
  $service_account_name,
  array('https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.compose'),
  $key
);

Please also note that $message should be $msg in the try...catch-Block.

Below, you'll find the the complete, working code for me:

<?php
require_once realpath(dirname(__FILE__) . '/../src/Google/autoload.php');
$client_id = '*censored*.apps.googleusercontent.com';
$service_account_name = '*censored*@developer.gserviceaccount.com';
$key_file_location = '/tmp/apiKey.p12';


$userid_from='*censored*';
$client = new Google_Client();
$client->setApplicationName("Client_Library_Examples");


//hmmm, really don't know whether these lines are necessary
if (isset($_SESSION['service_token'])) {
  $client->setAccessToken($_SESSION['service_token']);
}

$key = file_get_contents($key_file_location);
$cred = new Google_Auth_AssertionCredentials(
  $service_account_name,
  array('https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/gmail.compose', 'https://www.googleapis.com/auth/gmail.modify','https://www.googleapis.com/auth/gmail.readonly'),
  $key
);
$cred->sub=$userid_from; //<-- Important!
$client->setAssertionCredentials($cred);

if ($client->getAuth()->isAccessTokenExpired()) {
  $client->getAuth()->refreshTokenWithAssertion($cred);
}

//check if you want the validity of this string at: http://www.komeil.com/toolbox/base64decoder
//it is web safe base64 encoded email
$mime = "*censored*, same content as you posted, but another recipient ;-)";


$service = new Google_Service_Gmail($client);

$msg = new Google_Service_Gmail_Message();
$msg->setRaw($mime);

try {
  $results = $service->users_messages->send($userid_from, $msg);
  print 'Message with ID: ' . $results->id . ' sent.';
} catch (Exception $e) {
  print 'An error occurred: ' . $e->getMessage();
}

If there are any questions left, feel free to ask!

Friday, November 18, 2022
5

Swiftmailer 6.0 does not support the mail driver anymore

With laravel, you also updated swiftmailer, and thus, the mail driver does not work anymore.

Here is the swiftmailer commit that removed the mail driver in 6.0.

It was deprecated since Swiftmailer 5.4.5 with this commit.

Here is a long discussion about why it was removed, where the most useful comment says that:

PHP's mail() function is insecure

The fifth parameter can be exploited to execute arbitrary code on most Linux systems. This is mainly because PHP incorrectly escapes shell arguments. You can read the full explanation in "Why mail() is dangerous in PHP" by RIPS.

A couple of applications had security issues because of that, including Roundcube webmailer, MediaWiki, PHPMailer, ZendFramework, SquirrelMail and Swiftmailer. And your app. So be thankful that it got removed and use SMTP instead. You can usually simply use your local mail server using:

MAIL_DRIVER=smtp
MAIL_HOST=localhost
MAIL_PORT=25
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=null

(You may need to use tls encryption if your server does not accept local unencrypted mail.)

Laravel

The main issue with Laravel is that its documentation still mentions the mail driver in the Laravel 5.7 documentation and did only mention in their upgrade notes for 5.5 that swiftmailer 6.0 is required, but not that this means you cannot use the mail driver anymore.

Monday, October 3, 2022
 
4

You edited your question several times, each time changing its meaning, that should be avoided in future as it generates tones of comments. Instead next time pls, read error messages and fix them before asking. What you did wrong (in order of appearance)

  1. Don't use assigning methods without values just for future like $mail->addAddress('');, $mail->addCC(''), etc, it will cause errors.
  2. Use proper SMTP server address.
  3. Use proper credentials. Of course login and password are case sensitive.
  4. Turn ON 'Less secure applications' on your Gmail account if still have problem with credentials errors. It can be done at the security tab as shown at screenshot

Thursday, November 17, 2022
5

AFAIK this is not possible

Would be happy to be proved wrong on this!

I place this as a disclaimer because you are right, it does not explicitly deny the possibility. Its not generally a requirement for documentation to explicitly express everything you can not do with it, though I do see the potential for confusion here so it might be worth "Sending Feedback" to the documentation page.

What the instructions say

Preparing to make an authorized API call

After you obtain the client email address and private key from the API Console ...

source

In no place does it say "this is an optional step". This is also true in other places like the Directory API instructions on domain-wide delegation, or the Reports API

The Recommendation to not use Keys

The following is from the article you linked:

Use service account keys only if there is no viable alternative

A service account key lets an application authenticate as a service account, similar to how a user might authenticate with a username and password.

source

When you attach a service account, for example to an App Engine instance, the instance is like the user and the service account is its user account. It serves as a sort of identity for the instance. More info on that on this other thread.

So the only way for a service account to act as-if it were another account, is with a key. Likewise the only way for a user to act as-if it were a service account is with a key. Therefore, if you are trying to enable domain-wide delegation, which presupposes that you want to have a service account act as-if it were other accounts, having a key is essential, whether the service account is attached to an App Engine instance or not.

Granted, its not mentioned explicitly that you can not achieve this without a key. Though it doesn't mention that you can either. From the tests that I have run, I have run into similar results as yours, so it would seem that you just can't. As mentioned in the disclaimer, I would be happy to be proved wrong, however, I suspect that it would be due to a vulnerability, not intended behavior.

You haven't mentioned why you need domain-wide delegation of authority, so you may want to evaluate if you really need it. Usually all that is needed is the OAuth flow, in which an app acts oh-behalf of a user, not as-if it were the user.

References

  • Access to Google APIs with Service Accounts
  • How to Authenticate Service Accounts
  • Directory API instructions on domain-wide delegation
  • Reports API instructions on domain-wide delegation
  • "How to assign multiple service accounts to cloud functions" (you can't).
Wednesday, November 23, 2022
 
2

if you check the documentation filters it appears that you need to remove the label INBOX

action.removeLabelIds=['INBOX']
Monday, November 7, 2022
Only authorized users can answer the search term. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :