How do I now (since June 2022) send an email via Gmail using a Python script?

2024/10/15 9:18:56

I had a Python script which did this. I had to enable something in the Gmail account. For maybe 3 years the script then ran like this:

import smtplib, ssl
...
subject = 'some subject message'
body = """text body of the email"""
sender_email = '[email protected]'
receiver_email = '[email protected]'# Create a multipart message and set headers
message = MIMEMultipart()
message['From'] = 'Mike'
message['To'] = receiver_email
message['Subject'] = subject
# Add body to email
message.attach(MIMEText(body, 'plain'))
# Open file in binary mode
with open( client_zip_filename, 'rb') as attachment:# Add file as application/octet-stream# Email client can usually download this automatically as attachmentpart = MIMEBase('application', 'octet-stream')part.set_payload(attachment.read())
# Encode file in ASCII characters to send by email    
encoders.encode_base64(part)
# Add header as key/value pair to attachment part
part.add_header('Content-Disposition',f'attachment; filename={subject}',
)
# Add attachment to message and convert message to string
message.attach(part)
text = message.as_string()
# Log in to server using secure context and send email
context = ssl.create_default_context()
with smtplib.SMTP_SSL('smtp.gmail.com', 465, context=context) as server:print( 'waiting to login...')server.login(sender_email, password)print( 'waiting to send...')server.sendmail(sender_email, receiver_email, text)
print( 'email appears to have been sent')

In May or so of this year I got a message from Google saying that authority to use emails from scripts would be tightened. "Oh dear", I thought.

Some time in June I found that the above script no longer works, and raises an exception, specifically on the line server.login(sender_email, password):

  ...File "D:\My documents\software projects\operative\sysadmin_py\src\job_backup_routine\__main__.py", line 307, in mainserver.login(sender_email, password)File "c:\users\mike\appdata\local\programs\python\python39\lib\smtplib.py", line 745, in loginraise last_exceptionFile "c:\users\mike\appdata\local\programs\python\python39\lib\smtplib.py", line 734, in login(code, resp) = self.auth(File "c:\users\mike\appdata\local\programs\python\python39\lib\smtplib.py", line 657, in authraise SMTPAuthenticationError(code, resp)
smtplib.SMTPAuthenticationError: (535, b'5.7.8 Username and Password not accepted. 
Learn more at\n5.7.8  https://support.google.com/mail/?p=BadCredentials p14-20020aa7cc8e000000b00435651c4a01sm8910838edt.56 - gsmtp')

... I was thus not entirely surprised by this, and have now gone looking for a solution.

  • I have got this idea that the way forward is something called "OAuth consent" (I don't have any idea what this is...)

  • I found this answer and tried to follow the steps there. Here is my account of trying to follow step 1:

  • I went to this Google configuration page and chose "my_gmail_account_name", the account I want to send emails from ...

  • new "project", name: test-project-2022-07-18

  • location: default ("No organisation")

  • clicked Create

  • clicked NEXT

  • clicked ENABLE

  • clicked the icon to enable the "Google Developer Console"

  • in the hamburger menu (top left) there is an item "APIs and services" ... one item there is "Credentials" - clicked

  • one item in the left-hand list is "OAuth consent screen"

  • another item is "Credentials". Clicked this: then, at the top, "+ CREATE CREDENTIALS"

  • in the dropdown menu, choose "OAuth Client ID"

  • clicked "CONFIGURE CONSENT SCREEN"

  • radio buttons: "Internal" and "External". chose latter.

  • clicked "CREATE"

  • under "App information":

  • "App name": sysadmin_py

  • "User support email": [email protected]

  • "Developer contact information": [email protected]

  • clicked "SAVE AND CONTINUE"

  • then find myself on a page about "SCOPES", with a button "ADD OR REMOVE SCOPES"...

At this point I'm meant to be following "Step 1" instruction "d. Select the application type Other, enter the name "Gmail API Quickstart" and click the Create button"... but nothing of this kind is in view!

The update to that answer was done in 2021-04. A year later the interface in Google appears to have changed radically. Or maybe I have taken the wrong path and disappeared down a rabbit hole.

I have no idea what to do. Can anyone help?

Answer

Google has recently made changes to access of less secure apps (read here: https://myaccount.google.com/lesssecureapps).

In order to make your script work again, you'll need to make a new app password for it. Directions to do so are below:

  • Go to My Account in Gmail and click on Security. After that, scroll down to choose the Signing into Google option.
  • Now, click on App Password. (Note: You can see this option when two-step authentication is enabled). To enable two-step authentication:
    1. From the Signing into Google, click on the Two-step Verification option and then enter the password.
    2. Then Turn ON the two-step verification by entering the OTP code received on the mobile.

(Here's a quick link to the same page: https://myaccount.google.com/apppasswords)

  • Here, you can see a list of applications, choose the required one.
  • Next, pick the Select Device option and click on the device which is being used to operate Gmail.
  • Now, click on Generate.
  • After that, enter the Password shown in the Yellow bar.
  • Lastly, click on Done.

(Source: https://www.emailsupport.us/blog/gmail-smtp-not-working/)

Simply switch out the password the script is using for this newly generated app password. This worked for me and I wish the same for you.

I hope this helps!

https://en.xdnf.cn/q/69302.html

Related Q&A

Fast relational database for simple use with Python [closed]

Closed. This question is seeking recommendations for books, tools, software libraries, and more. It does not meet Stack Overflow guidelines. It is not currently accepting answers.We don’t allow questi…

Event handling with Jython Swing

Im making a GUI by using Swing from Jython. Event handling seems to be particularly elegant from Jython, just setJButton("Push me", actionPerformed = nameOfFunctionToCall)However, trying same…

How Does Deque Work in Python

I am having trouble understanding how the deque works in the snippet of code below, while trying to recreate a queue and a stack in Python.Stack Example - Understoodstack = ["a", "b"…

When to use generator functions and when to use loops in Python

I am coming from a Matlab background and I am finding it difficult to get around the concept of generators in Python. Can someone please answer me the following:The difference between a generator funct…

Airflow - Disable heartbeat logs

My logs are getting completely flooded with useless messages for every heartbeat. [2019-11-27 21:32:47,890] {{logging_mixin.py:112}} INFO - [2019-11-27 21:32:47,889] {local_task_job.py:124} WARNING - T…

different validation in drf serializer per request method

Lets say i have a model like so:class MyModel(models.Model):first_field = models.CharField()second_field = models.CharField()and an API view like so:class MyModelDetailAPI(GenericAPIView):serializer_cl…

How to import r-packages in Python

Im a bit troubled with a simple thing. I was trying to install a package called hunspell, but I discovered it is originally an R package. I installed this version: https://anaconda.org/conda-forge/r-hu…

XPath predicate with sub-paths with lxml?

Im trying to understand and XPath that was sent to me for use with ACORD XML forms (common format in insurance). The XPath they sent me is (truncated for brevity):./PersApplicationInfo/InsuredOrPrinci…

Best way to access and close a postgres database using python dataset

import dataset from sqlalchemy.pool import NullPooldb = dataset.connect(path_database, engine_kwargs={poolclass: NullPool})table_f1 = db[name_table] # Do operations on table_f1db.commit() db.execut…

Using different binds in the same class in Flask-SQLAlchemy

I currently have multiple databases with identical Tables and Columns (but different data inside). So clearly I need to use binds to access all of them, but its apparently not as simple as doing this:c…