apb_extra_utils.send_mail

  1#  coding=utf-8
  2#
  3#  Author: Ernesto Arredondo Martinez (ernestone@gmail.com)
  4#  Created: 7/6/19 18:23
  5#  Last modified: 7/6/19 18:21
  6#  Copyright (c) 2019
  7
  8# Functions to send mail from a server (environment variable MAIL_SERVER)
  9
 10import datetime
 11import mimetypes
 12import os
 13import smtplib
 14import ssl
 15import warnings
 16
 17import docutils.core
 18from sendgrid import SendGridAPIClient
 19from sendgrid.helpers.mail import Mail
 20
 21from .misc import machine_apb, machine_name
 22
 23
 24def set_attachment_to_msg(msg, file_path):
 25    """
 26
 27    Args:
 28        msg (EmailMessage): objecto EmailMessage donde se hara attach
 29        file_path: path del fichero a vincular al mensaje
 30
 31    Returns:
 32
 33    """
 34    if not os.path.isfile(file_path):
 35        return
 36
 37    # Guess the content type based on the file's extension.  Encoding
 38    # will be ignored, although we should check for simple things like
 39    # gzip'd or compressed files.
 40    ctype, encoding = mimetypes.guess_type(file_path)
 41    if ctype is None or encoding is not None:
 42        # No guess could be made, or the file is encoded (compressed), so
 43        # use a generic bag-of-bits type.
 44        ctype = 'application/octet-stream'
 45    maintype, subtype = ctype.split('/', 1)
 46    with open(file_path, 'rb') as fp:
 47        msg.add_attachment(fp.read(),
 48                           maintype=maintype,
 49                           subtype=subtype,
 50                           filename=os.path.basename(file_path))
 51
 52
 53def sendMailWithAttach(server=os.environ.get('MAIL_SERVER', 'server-mail.com'), frm='', to='', subject='', body='',
 54                       lineSep='not_line_separator', files=None, to_html=False, tls=True):
 55    """
 56    Permet enviar un E-mail des de FROM a TO amb SUBJECT amb BOBY line_separator (cas body amb multilinea) i ATTACH
 57
 58    Args:
 59        server (str=os.environ.get('MAIL_SERVER'):
 60        frm (str=""):
 61        to (str=""):
 62        subject (str=""):
 63        body (str=""):
 64        lineSep (str="not_line_separator"):
 65        files (list=None): lista de paths de ficheros a adjuntar
 66        to_html (bool=False): Si true parsea con docutils (reestructuredText [rst], Latex, ...)
 67                              el texto del body enviado y lo convierte a html
 68        tls (bool=True): start TLS
 69    Returns:
 70
 71    """
 72    from email.message import EmailMessage
 73
 74    msg = EmailMessage()
 75
 76    msg['From'] = frm
 77    msg['To'] = to
 78    msg['Subject'] = subject
 79    msg.epilogue = ''
 80
 81    if lineSep != 'not_line_separator' and body.find(lineSep) >= 0:
 82        body = '\n'.join(body.split(lineSep))
 83
 84    msg.set_content(body)
 85    if to_html:
 86        msg.add_alternative(docutils.core.publish_string(body, writer_name="html").decode('utf-8'), subtype='html')
 87
 88    if files:
 89        for file_path in files:
 90            set_attachment_to_msg(msg, file_path)
 91
 92    context = None
 93    if tls:
 94        context = ssl.create_default_context()
 95    srv = None
 96    try:
 97        codi = 0
 98        srv = smtplib.SMTP(server)
 99        srv.ehlo()
100        if tls:
101            try:
102                srv.starttls(context=context)
103            except smtplib.SMTPNotSupportedError as exc:
104                print(f"El server SMTP '{server}' no suporta TLS. Error: {exc}")
105        srv.ehlo()
106        srv.send_message(msg)
107    except smtplib.SMTPException as exc:
108        import traceback
109        print(traceback.format_exc())
110        codi = 1
111    finally:
112        if srv:
113            srv.quit()
114
115    return codi
116
117
118FROM_MAIL = os.getenv('DEFAULT_FROM_MAIL', 'from_your_account@mail.com')
119
120
121def enviar_mail(subject, body, user_mail_list, to_html=False, *attach_path_files):
122    """
123    Envia mail desde la cuenta FROM_MAIL a la lista de mails especificados y adjunta los logs del gestor
124    si estos han generado entradas
125
126    Args:
127        subject (str): Le asignará el nombre de la máquina desde la que está corriendo
128        body (str): Texto con el cuerpo del mail. Por defecto buscará '$$NEWLINE$$' para substituir por saltos de línea
129        user_mail_list (list): Lista de strings con los correos
130        to_html (bool=False): Si true parsea con docutils (reestructuredText [rst], Latex, ...)
131                      el texto del body enviado y lo convierte a html
132        *attach_path_files: PATHs de ficheros a adjuntar
133
134    Returns:
135        codi (int)
136    """
137    codi = 1
138    if machine_apb():
139        subject = f"[{machine_name()}] {subject}"
140
141    # SendMail
142    try:
143        codi = sendMailWithAttach(frm=FROM_MAIL,
144                                  to=", ".join(user_mail_list),
145                                  subject="{} {}".format(subject,
146                                                         datetime.datetime.now().strftime(
147                                                             '%Y-%m-%d %H:%M')),
148                                  body=body,
149                                  lineSep='$$NEWLINE$$',
150                                  files=list(attach_path_files),
151                                  to_html=to_html)
152
153    except Exception as exc:
154        import traceback
155        print(traceback.format_exc())
156        warnings.warn("No se ha podido enviar el mail con subject '{subject}'".format(subject=subject))
157
158    return codi
159
160
161def send_grid(subject: str, body: str, user_mail_list: list, sender: str = None, api_key: str = None):
162    """
163    Envia mail desde la api de sendGrid
164
165    Args:
166        subject (str): Tema a enviar en el correo
167        body (str): Texto con el cuerpo del mail. Por defecto buscará '$$NEWLINE$$' para substituir por saltos de línea
168        user_mail_list (list): Lista de strings con los correos
169        sender (str=None): Mail del sender. Si no el passen, agafem variable d'entorn SENDGRID_SENDER
170        api_key(str=None): Api key del send grid a utilitzar. Si no el passen, agafem variable d'entorn SENDGRID_API_KEY
171
172    Returns:
173        dict: Diccionario con la respuesta de la api de sendGrid
174            Examples:
175                OK = {'status_code': 202, 'body': ...}
176
177    """
178    if not api_key:
179        api_key = os.getenv('SENDGRID_API_KEY')
180
181    if not sender:
182        sender = os.getenv('SENDGRID_SENDER')
183
184    resp = dict()
185    try:
186        message = Mail(
187            from_email=sender,
188            to_emails=user_mail_list,
189            subject=subject,
190            html_content=body)
191
192        sg = SendGridAPIClient(api_key)
193        response = sg.send(message)
194
195        resp['status_code'] = response.status_code
196        resp['body'] = response.body
197
198    except Exception as exc:
199        error = f"No se ha podido enviar el mail con subject '{subject}'\n" \
200                f"Error: {exc}"
201        resp['error'] = error
202        warnings.warn(error)
203
204    return resp
205
206
207if __name__ == '__main__':
208    import fire
209
210    fire.Fire({
211        enviar_mail.__name__: enviar_mail,
212        send_grid.__name__: send_grid
213    })
def set_attachment_to_msg(msg, file_path):
25def set_attachment_to_msg(msg, file_path):
26    """
27
28    Args:
29        msg (EmailMessage): objecto EmailMessage donde se hara attach
30        file_path: path del fichero a vincular al mensaje
31
32    Returns:
33
34    """
35    if not os.path.isfile(file_path):
36        return
37
38    # Guess the content type based on the file's extension.  Encoding
39    # will be ignored, although we should check for simple things like
40    # gzip'd or compressed files.
41    ctype, encoding = mimetypes.guess_type(file_path)
42    if ctype is None or encoding is not None:
43        # No guess could be made, or the file is encoded (compressed), so
44        # use a generic bag-of-bits type.
45        ctype = 'application/octet-stream'
46    maintype, subtype = ctype.split('/', 1)
47    with open(file_path, 'rb') as fp:
48        msg.add_attachment(fp.read(),
49                           maintype=maintype,
50                           subtype=subtype,
51                           filename=os.path.basename(file_path))
Arguments:
  • msg (EmailMessage): objecto EmailMessage donde se hara attach
  • file_path: path del fichero a vincular al mensaje

Returns:

def sendMailWithAttach( server='server-mail.com', frm='', to='', subject='', body='', lineSep='not_line_separator', files=None, to_html=False, tls=True):
 54def sendMailWithAttach(server=os.environ.get('MAIL_SERVER', 'server-mail.com'), frm='', to='', subject='', body='',
 55                       lineSep='not_line_separator', files=None, to_html=False, tls=True):
 56    """
 57    Permet enviar un E-mail des de FROM a TO amb SUBJECT amb BOBY line_separator (cas body amb multilinea) i ATTACH
 58
 59    Args:
 60        server (str=os.environ.get('MAIL_SERVER'):
 61        frm (str=""):
 62        to (str=""):
 63        subject (str=""):
 64        body (str=""):
 65        lineSep (str="not_line_separator"):
 66        files (list=None): lista de paths de ficheros a adjuntar
 67        to_html (bool=False): Si true parsea con docutils (reestructuredText [rst], Latex, ...)
 68                              el texto del body enviado y lo convierte a html
 69        tls (bool=True): start TLS
 70    Returns:
 71
 72    """
 73    from email.message import EmailMessage
 74
 75    msg = EmailMessage()
 76
 77    msg['From'] = frm
 78    msg['To'] = to
 79    msg['Subject'] = subject
 80    msg.epilogue = ''
 81
 82    if lineSep != 'not_line_separator' and body.find(lineSep) >= 0:
 83        body = '\n'.join(body.split(lineSep))
 84
 85    msg.set_content(body)
 86    if to_html:
 87        msg.add_alternative(docutils.core.publish_string(body, writer_name="html").decode('utf-8'), subtype='html')
 88
 89    if files:
 90        for file_path in files:
 91            set_attachment_to_msg(msg, file_path)
 92
 93    context = None
 94    if tls:
 95        context = ssl.create_default_context()
 96    srv = None
 97    try:
 98        codi = 0
 99        srv = smtplib.SMTP(server)
100        srv.ehlo()
101        if tls:
102            try:
103                srv.starttls(context=context)
104            except smtplib.SMTPNotSupportedError as exc:
105                print(f"El server SMTP '{server}' no suporta TLS. Error: {exc}")
106        srv.ehlo()
107        srv.send_message(msg)
108    except smtplib.SMTPException as exc:
109        import traceback
110        print(traceback.format_exc())
111        codi = 1
112    finally:
113        if srv:
114            srv.quit()
115
116    return codi

Permet enviar un E-mail des de FROM a TO amb SUBJECT amb BOBY line_separator (cas body amb multilinea) i ATTACH

Arguments:
  • server (str=os.environ.get('MAIL_SERVER'):
  • frm (str=""):
  • to (str=""):
  • subject (str=""):
  • body (str=""):
  • lineSep (str="not_line_separator"):
  • files (list=None): lista de paths de ficheros a adjuntar
  • to_html (bool=False): Si true parsea con docutils (reestructuredText [rst], Latex, ...) el texto del body enviado y lo convierte a html
  • tls (bool=True): start TLS

Returns:

FROM_MAIL = 'from_your_account@mail.com'
def enviar_mail(subject, body, user_mail_list, to_html=False, *attach_path_files):
122def enviar_mail(subject, body, user_mail_list, to_html=False, *attach_path_files):
123    """
124    Envia mail desde la cuenta FROM_MAIL a la lista de mails especificados y adjunta los logs del gestor
125    si estos han generado entradas
126
127    Args:
128        subject (str): Le asignará el nombre de la máquina desde la que está corriendo
129        body (str): Texto con el cuerpo del mail. Por defecto buscará '$$NEWLINE$$' para substituir por saltos de línea
130        user_mail_list (list): Lista de strings con los correos
131        to_html (bool=False): Si true parsea con docutils (reestructuredText [rst], Latex, ...)
132                      el texto del body enviado y lo convierte a html
133        *attach_path_files: PATHs de ficheros a adjuntar
134
135    Returns:
136        codi (int)
137    """
138    codi = 1
139    if machine_apb():
140        subject = f"[{machine_name()}] {subject}"
141
142    # SendMail
143    try:
144        codi = sendMailWithAttach(frm=FROM_MAIL,
145                                  to=", ".join(user_mail_list),
146                                  subject="{} {}".format(subject,
147                                                         datetime.datetime.now().strftime(
148                                                             '%Y-%m-%d %H:%M')),
149                                  body=body,
150                                  lineSep='$$NEWLINE$$',
151                                  files=list(attach_path_files),
152                                  to_html=to_html)
153
154    except Exception as exc:
155        import traceback
156        print(traceback.format_exc())
157        warnings.warn("No se ha podido enviar el mail con subject '{subject}'".format(subject=subject))
158
159    return codi

Envia mail desde la cuenta FROM_MAIL a la lista de mails especificados y adjunta los logs del gestor si estos han generado entradas

Arguments:
  • subject (str): Le asignará el nombre de la máquina desde la que está corriendo
  • body (str): Texto con el cuerpo del mail. Por defecto buscará '$$NEWLINE$$' para substituir por saltos de línea
  • user_mail_list (list): Lista de strings con los correos
  • to_html (bool=False): Si true parsea con docutils (reestructuredText [rst], Latex, ...) el texto del body enviado y lo convierte a html
  • *attach_path_files: PATHs de ficheros a adjuntar
Returns:

codi (int)

def send_grid( subject: str, body: str, user_mail_list: list, sender: str = None, api_key: str = None):
162def send_grid(subject: str, body: str, user_mail_list: list, sender: str = None, api_key: str = None):
163    """
164    Envia mail desde la api de sendGrid
165
166    Args:
167        subject (str): Tema a enviar en el correo
168        body (str): Texto con el cuerpo del mail. Por defecto buscará '$$NEWLINE$$' para substituir por saltos de línea
169        user_mail_list (list): Lista de strings con los correos
170        sender (str=None): Mail del sender. Si no el passen, agafem variable d'entorn SENDGRID_SENDER
171        api_key(str=None): Api key del send grid a utilitzar. Si no el passen, agafem variable d'entorn SENDGRID_API_KEY
172
173    Returns:
174        dict: Diccionario con la respuesta de la api de sendGrid
175            Examples:
176                OK = {'status_code': 202, 'body': ...}
177
178    """
179    if not api_key:
180        api_key = os.getenv('SENDGRID_API_KEY')
181
182    if not sender:
183        sender = os.getenv('SENDGRID_SENDER')
184
185    resp = dict()
186    try:
187        message = Mail(
188            from_email=sender,
189            to_emails=user_mail_list,
190            subject=subject,
191            html_content=body)
192
193        sg = SendGridAPIClient(api_key)
194        response = sg.send(message)
195
196        resp['status_code'] = response.status_code
197        resp['body'] = response.body
198
199    except Exception as exc:
200        error = f"No se ha podido enviar el mail con subject '{subject}'\n" \
201                f"Error: {exc}"
202        resp['error'] = error
203        warnings.warn(error)
204
205    return resp

Envia mail desde la api de sendGrid

Arguments:
  • subject (str): Tema a enviar en el correo
  • body (str): Texto con el cuerpo del mail. Por defecto buscará '$$NEWLINE$$' para substituir por saltos de línea
  • user_mail_list (list): Lista de strings con los correos
  • sender (str=None): Mail del sender. Si no el passen, agafem variable d'entorn SENDGRID_SENDER
  • api_key(str=None): Api key del send grid a utilitzar. Si no el passen, agafem variable d'entorn SENDGRID_API_KEY
Returns:

dict: Diccionario con la respuesta de la api de sendGrid Examples: OK = {'status_code': 202, 'body': ...}