¿Cómo enviar un correo electrónico a través de gmail sin habilitar el ‘acceso inseguro’?

Google nos está presionando para mejorar la seguridad del acceso de script a sus servidores smtp de gmail. No tengo ningún problema con eso. De hecho estoy feliz de ayudar.

Pero no lo están haciendo fácil. Es bueno y bueno sugerir que Upgrade to a more secure app that uses the most up to date security measures , pero eso no me ayuda a descubrir cómo actualizar los bits de código que se parecen a esto:

 server = smtplib.SMTP("smtp.gmail.com", 587) server.ehlo() server.starttls() server.login(GMAIL_USER, GMAIL_PASSWORD) server.sendmail(FROM, TO, MESSAGE) server.close() 

Claro, iré y activaré “Acceso para aplicaciones menos seguras”, pero si alguien ha averiguado con qué reemplazar este código, se lo agradeceré.

Parece que la respuesta de John Mee está fuera de fecha. No funciona en julio de 2016. Tal vez debido a la actualización de la API de Gmail. Actualizo su código (python 2) de la siguiente manera:

  """Send an email message from the user's account. """ import base64 from email.mime.audio import MIMEAudio from email.mime.base import MIMEBase from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText import mimetypes import os #from __future__ import print_function import httplib2 import os from apiclient import discovery import oauth2client from oauth2client import client from oauth2client import tools from apiclient import errors SCOPES = 'https://www.googleapis.com/auth/gmail.compose' CLIENT_SECRET_FILE = 'client_secret.json' APPLICATION_NAME = 'Gmail API Python Quickstart' try: import argparse flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args() except ImportError: flags = None def SendMessage(service, user_id, message): """Send an email message. Args: service: Authorized Gmail API service instance. user_id: User's email address. The special value "me" can be used to indicate the authenticated user. message: Message to be sent. Returns: Sent Message. """ try: message = (service.users().messages().send(userId=user_id, body=message) .execute()) print 'Message Id: %s' % message['id'] return message except errors.HttpError, error: print 'An error occurred: %s' % error def CreateMessage(sender, to, subject, message_text): """Create a message for an email. Args: sender: Email address of the sender. to: Email address of the receiver. subject: The subject of the email message. message_text: The text of the email message. Returns: An object containing a base64url encoded email object. """ message = MIMEText(message_text) message['to'] = to message['from'] = sender message['subject'] = subject return {'raw': base64.urlsafe_b64encode(message.as_string())} def get_credentials(): """Gets valid user credentials from storage. If nothing has been stored, or if the stored credentials are invalid, the OAuth2 flow is completed to obtain the new credentials. Returns: Credentials, the obtained credential. """ home_dir = os.path.expanduser('~') credential_dir = os.path.join(home_dir, '.credentials') if not os.path.exists(credential_dir): os.makedirs(credential_dir) credential_path = os.path.join(credential_dir, 'sendEmail.json') store = oauth2client.file.Storage(credential_path) credentials = store.get() if not credentials or credentials.invalid: flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES) flow.user_agent = APPLICATION_NAME if flags: credentials = tools.run_flow(flow, store, flags) else: # Needed only for compatibility with Python 2.6 credentials = tools.run(flow, store) print('Storing credentials to ' + credential_path) return credentials if __name__ == "__main__": try: credentials = get_credentials() http = credentials.authorize(httplib2.Http()) service = discovery.build('gmail', 'v1', http=http) SendMessage(service, "me", CreateMessage("send@gmail.com", "receive@gmail.com", "Test gmail automation", "Hello world")) except Exception, e: print e raise 

Tenga en cuenta que si encuentra el error de Insufficient Permission , una posible razón es que el scope en el progtwig no está configurado correctamente. La otra posible razón puede ser que necesite eliminar el archivo json de almacenamiento (“sendEmail.json” en este progtwig) y actualizar su progtwig. Más detalles se pueden ver en este post .

Esto fue doloroso, pero parece que tengo algo en marcha ahora …

Python3 no es compatible (todavía)

No creo que sea demasiado difícil de lograr, ya que estaba tropezando a través de la conversión de paquetes sin golpear nada masivo: solo las cosas habituales de 2to3. Sin embargo, después de un par de horas me cansé de nadar contra la stream. Al momento de escribir, no pude encontrar un paquete publicado para el consumo público para Python 3. La experiencia con Python 2 fue sencilla (en comparación).

Navegar por el sitio web de Google es la mitad de la batalla

Sin duda, con el tiempo, esto va a cambiar. En última instancia, necesita descargar un archivo client_secret.json . Solo puedes (probablemente) hacer esto configurando cosas a través de un navegador web:

  1. Necesitas una cuenta de Google, ya sea Google Apps o Gmail. Entonces, si no tienes uno, ve por uno.
  2. Accede a la consola de desarrolladores
  3. Cree un nuevo proyecto y espere 4 o 400 segundos para que se complete.
  4. Navegue a API's and Auth -> Credentials
  5. En OAuth seleccione Create New Client ID
  6. Elija Installed Application como el tipo de aplicación y Otro
  7. Ahora debería tener un botón Download JSON . Haz eso. Es tu client_secret.json las contraseñas por así decirlo

Pero espera eso no es todo!

Tiene que dar a su aplicación un “Nombre del producto” para evitar algunos errores impares. (mira cuanto sufrí para darte esto 😉

  1. Navegue a API's & auth -> Consent Screen
  2. Elige tu email
  3. Introduzca un nombre de producto. No importa lo que sea. “Foobar” estará bien.
  4. Salvar

Noticias de última hora! Whoa ¡Ahora hay aún más!

  1. Navegue a API’s y auth -> APIs -> API de Gmail
  2. Haga clic en el botón Habilitar API

Hurra. Ahora podemos actualizar el script de correo electrónico.

Python 2

Necesitas ejecutar el script interactivamente la primera vez. Abrirá un navegador web en su máquina y le otorgará permisos (presione un botón). Este ejercicio guardará un archivo en su computadora gmail.storage que contiene un token reutilizable.

[No tuve la suerte de transferir el token a una máquina que no tiene una funcionalidad de navegador gráfico; devuelve un error HTTP. Intenté atravesarlo a través del navegador gráfico lynx. ¿Eso también falló porque Google ha configurado el botón “aceptar” final en “deshabilitado”? Voy a hacer otra pregunta para saltar este obstáculo (más quejas)]

Primero necesitas algunas bibliotecas:

 pip install --upgrade google-api-python-client pip install --upgrade python-gflags 
  • Necesitas cambiar las direcciones ay desde
  • asegúrese de tener el archivo client_token.json donde sea que las instrucciones de Storage esperen
  • el directorio debe poder escribirse para que pueda guardar el archivo gmail.storage

Finalmente un código:

 import base64 import httplib2 from email.mime.text import MIMEText from apiclient.discovery import build from oauth2client.client import flow_from_clientsecrets from oauth2client.file import Storage from oauth2client.tools import run # Path to the client_secret.json file downloaded from the Developer Console CLIENT_SECRET_FILE = 'client_secret.json' # Check https://developers.google.com/gmail/api/auth/scopes for all available scopes OAUTH_SCOPE = 'https://www.googleapis.com/auth/gmail.compose' # Location of the credentials storage file STORAGE = Storage('gmail.storage') # Start the OAuth flow to retrieve credentials flow = flow_from_clientsecrets(CLIENT_SECRET_FILE, scope=OAUTH_SCOPE) http = httplib2.Http() # Try to retrieve credentials from storage or run the flow to generate them credentials = STORAGE.get() if credentials is None or credentials.invalid: credentials = run(flow, STORAGE, http=http) # Authorize the httplib2.Http object with our credentials http = credentials.authorize(http) # Build the Gmail service from discovery gmail_service = build('gmail', 'v1', http=http) # create a message to send message = MIMEText("Message goes here.") message['to'] = "yourvictim@goes.here" message['from'] = "you@go.here" message['subject'] = "your subject goes here" body = {'raw': base64.b64encode(message.as_string())} # send it try: message = (gmail_service.users().messages().send(userId="me", body=body).execute()) print('Message Id: %s' % message['id']) print(message) except Exception as error: print('An error occurred: %s' % error) 

Esperemos que eso nos haga comenzar a todos. No es tan simple como a la antigua, pero parece mucho menos complicado ahora que puedo verlo en la carne.

¿Has considerado usar la API de Gmail? La API tiene funciones de seguridad integradas y está optimizada específicamente para Gmail. Puede encontrar la documentación de la API en http://developers.google.com ; por ejemplo, aquí está la documentación para la llamada a la API de envío:

https://developers.google.com/gmail/api/v1/reference/users/messages/send

Incluyo un código que se ha actualizado para el uso de Python 3: parece que envía correos electrónicos una vez que obtienes los permisos necesarios y los tokens de OAuth en funcionamiento. Se basa principalmente en las muestras del sitio web de Google Api

  from __future__ import print_function import base64 import os from email.mime.text import MIMEText import httplib2 from apiclient import discovery from googleapiclient import errors from oauth2client import client from oauth2client import tools from oauth2client.file import Storage try: import argparse flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args() except ImportError: flags = None # If modifying these scopes, delete your previously saved credentials # at ~/.credentials/gmail-python-quickstart.json SCOPES = 'https://www.googleapis.com/auth/gmail.send' CLIENT_SECRET_FILE = 'client_secret.json' APPLICATION_NAME = 'Gmail API Python Quickstart' def get_credentials(): """Gets valid user credentials from storage. If nothing has been stored, or if the stored credentials are invalid, the OAuth2 flow is completed to obtain the new credentials. Returns: Credentials, the obtained credential. """ home_dir = os.path.expanduser('~') credential_dir = os.path.join(home_dir, '.credentials') if not os.path.exists(credential_dir): os.makedirs(credential_dir) credential_path = os.path.join(credential_dir, 'gmail-python-quickstart.json') store = Storage(credential_path) credentials = store.get() if not credentials or credentials.invalid: flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES) flow.user_agent = APPLICATION_NAME if flags: credentials = tools.run_flow(flow, store, flags) else: # Needed only for compatibility with Python 2.6 credentials = tools.run(flow, store) print('Storing credentials to ' + credential_path) return credentials to = 'test@email.com' sender = 'test@email.com' subject = 'test emails' message_text = 'hello this is a text test message' user_id = 'me' def create_message(sender, to, subject, message_text): """Create a message for an email. Args: sender: Email address of the sender. to: Email address of the receiver. subject: The subject of the email message. message_text: The text of the email message. Returns: An object containing a base64url encoded email object. """ message = MIMEText(message_text) message['to'] = to message['from'] = sender message['subject'] = subject return {'raw': (base64.urlsafe_b64encode(message.as_bytes()).decode())} def send_message(service, user_id, message): """Send an email message. Args: service: Authorized Gmail API service instance. user_id: User's email address. The special value "me" can be used to indicate the authenticated user. message: Message to be sent. Returns: Sent Message. """ try: message = (service.users().messages().send(userId=user_id, body=message) .execute()) print('Message Id: {}'.format(message['id'])) return message except errors.HttpError as error: print('An error occurred: {}'.format(error)) def main(): """Shows basic usage of the Gmail API. Creates a Gmail API service object and outputs a list of label names of the user's Gmail account. """ credentials = get_credentials() http = credentials.authorize(httplib2.Http()) service = discovery.build('gmail', 'v1', http=http) msg = create_message(sender,to,subject,message_text) message = (service.users().messages().send(userId=user_id, body=msg) .execute()) print('Message Id: {}'.format(message['id'])) results = service.users().messages().list(userId='me').execute() labels = results.get('labels', []) if not labels: print('No labels found.') else: print('Labels:') for label in labels: print(label['name']) if __name__ == '__main__': main() 
  protected string SendEmail(string toAddress, string subject, string body) { string result = "Message Sent Successfully..!!"; string senderID = "...........";// use sender's email id here.. const string senderPassword = "........."; // sender password here... try { SmtpClient smtp = new SmtpClient { Host = "smtp.gmail.com", // smtp server address here... Port = 587, EnableSsl = true, DeliveryMethod = SmtpDeliveryMethod.Network, Credentials = new System.Net.NetworkCredential(senderID, senderPassword), Timeout = 30000, }; MailMessage message = new MailMessage(senderID, toAddress, subject, body); smtp.Send(message); } catch (Exception ex) { result = "Error sending email.!!!"; } return result; }