Search code examples
python-3.xgoogle-apigmailgmail-api

send mail using service account and google api python


Edit: after accepting the answer: my question was about a free Gmail account (I didn't know there was a difference) and the answer is about a paid account (and it is a currect one), the answer showed me that there was a difference and it led me the correct answer to my situation - use a passcode

im trying to send mail using google API and service account, but I'm getting the following erre:

An error occurred: <HttpError 400 when requesting https://gmail.googleapis.com/gmail/v1/users/me/drafts?alt=json returned "Precondition check failed.". Details: "[{'message': 'Precondition check failed.', 'domain': 'global', 'reason': 'failedPrecondition'}]">

this is my code:

from __future__ import print_function

import os.path

from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from oauth2client.service_account import ServiceAccountCredentials

import base64
from email.message import EmailMessage


# If modifying these scopes, delete the file token.json.
SCOPES = ['https://mail.google.com/']


def main():
    """Shows basic usage of the Gmail API.
    Lists the user's Gmail labels.
    """
    creds = None
       creds = ServiceAccountCredentials.from_json_keyfile_name(
        """path_to_cred_file.json""", SCOPES)

    try:
        # Call the Gmail API
        service = build('gmail', 'v1', credentials=creds)
        message = EmailMessage()
        message.set_content('This is automated draft mail')
        message['To'] = 'somemail@gmail.com'
        message['From'] = 'somemail@gmail.com'
        message['Subject'] = 'Automated draft'

        # encoded message
        encoded_message = base64.urlsafe_b64encode(message.as_bytes()).decode()

        create_message = {
            'message': {
                'raw': encoded_message
            }
        }
        # pylint: disable=E1101
        draft = service.users().drafts().create(userId="me",
                                                body=create_message).execute()
    except HttpError as error:
        # TODO(developer) - Handle errors from gmail API.
        print(f'An error occurred: {error}')


if __name__ == '__main__':
    main()

Solution

  • "Precondition check failed" usually means that you're trying to do something that cannot be done. In this case you're trying to send an email from a service account, which is not possible. This answer has a link to a relevant thread from the Google documentation. They say the following:

    Service accounts dont work with gmail unless you set up domain wide delegation to a Gsuite account. The reason being is that a service account is its own user you need to delegate its permission to access your gmail account. This will only work with a gsuite domain email address.

    This means that the service account by itself cannot send messages, but instead needs to be delegated access to a regular user account in order to send emails. To do this you can add the following line after your creds:

        delegated_creds=credentials.with_subject("someuser@yourdomain.com")
    
        #where someuser@ is the email of the user that you're sending email as
    

    After that you can use delegated_creds instead of creds to call the service.

    Also, you seem to have gotten your sample from Google's guide, but note that your sample creates a draft instead of sending an email. The API call to send emails is a little different. With that in mind here's a complete example based on your code which worked for me:

    #all the imports
    
    SCOPES = ['https://mail.google.com/']
    
    def main():
    
        creds = None
           creds = ServiceAccountCredentials.from_json_keyfile_name(
            """path_to_cred_file.json""", SCOPES)
         
           delegated_creds=credentials.with_subject("someuser@yourdomain.com")
    
        try:
            # Call the Gmail API
            service = build('gmail', 'v1', credentials=delegated_creds) 
            message = EmailMessage()
            message.set_content('This is automated draft mail')
            message['To'] = 'somemail@gmail.com'
            message['From'] = 'somemail@gmail.com'
            message['Subject'] = 'Automated draft'
    
            encoded_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
    
            create_message = {
                'raw': encoded_message
            }
    
            email = service.users().messages().send(userId="me", 
                                                    body=create_message).execute()
            except HttpError as error:
                # TODO(developer) - Handle errors from gmail API.
                print(f'An error occurred: {error}')
    
    
    if __name__ == '__main__':
        main()
    

    Finally, as explained in the thread I linked, this only works for Google Workspace accounts and you cannot delegate access to free Gmail accounts, so keep that in mind.