diff --git a/docs/source/email.rst b/docs/source/email.rst new file mode 100644 index 0000000..91a4144 --- /dev/null +++ b/docs/source/email.rst @@ -0,0 +1,109 @@ + +Email +===== + +Remember, always import library. + +.. code-block:: python + + import easymacro as app + + +**IMPORTANT:** Always save your config the more security way possible. + + +Send email +---------- + +.. code-block:: python + + from conf import PASSWORD + + SERVER = dict( + server = 'mail.server.net' , + port = 495, + ssl = True, + user = 'no-responder@noexiste.mx', + password = PASSWORD, + ) + + body = "Hello Ingrid\n\nWho are you?\n\nBest regards" + + message = dict( + to = 'ingrid.bergman@love.you', + subject = 'I love you', + body = body, + ) + + app.email.send(SERVER, message) + +* We can use fields `cc`, `bcc` too and send to more than one address emails. + +.. code-block:: python + + to = 'mail1@correo.com,mail2@correo.com,mail3@correo.com' + cc = 'other@correo.com' + bcc = 'hidden@correo.com' + +* We can send too more than one message. + +.. code-block:: python + + message1 = dict( + to = 'ingrid.bergman@email.net', + subject = 'I love you', + body = "Hello Ingrid\n\nWho are you?\n\nBest regards", + ) + message2 = dict( + to = 'sophia.loren@email.net', + subject = 'I love you', + body = "Hello Sophia\n\nWho are you?\n\nBest regards", + ) + messages = (message1, message2) + + app.email.send(SERVER, messages) + +.. code-block:: bash + + 30/06/2021 13:43:23 - DEBUG - Connect to: mail.gandi.net + 30/06/2021 13:43:24 - DEBUG - Email sent... + 30/06/2021 13:43:26 - DEBUG - Email sent... + 30/06/2021 13:43:26 - DEBUG - Close connection... + +* Send with attachment + +.. code-block:: python + + files = '/home/mau/file.epub' + message = dict( + to = 'ingrid.bergman@email.net', + subject = 'I love you', + body = "Hello Ingrid\n\nWho are you?\n\nBest regards", + files = files, + ) + +* Send more than one file. + +.. code-block:: python + + files = ( + '/home/mau/file1.epub', + '/home/mau/file2.epub', + ) + +* If your client email used `mbox` format, we can save in any path into your email client configuration. + +.. code-block:: python + + path_save = '/home/mau/.thunderbird/7iznrbyw.default/Mail/Local Folders/LibreOffice' + message = dict( + to = 'ingrid.bergman@email.net', + subject = 'I love you', + body = "Hello Ingrid\n\nWho are you?\n\nBest regards", + path = path_save + ) + app.email.send(SERVER, message) + + +* All emails always send in other thread. + diff --git a/docs/source/generated/easymacro.rst b/docs/source/generated/easymacro.rst index 0166ab0..71166a2 100644 --- a/docs/source/generated/easymacro.rst +++ b/docs/source/generated/easymacro.rst @@ -43,6 +43,7 @@ Color Config Dates + Email Hash Json LOServer diff --git a/docs/source/index.rst b/docs/source/index.rst index e9d7d5e..5d6b626 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -25,6 +25,7 @@ You can used **easymacro** with any extension or directly in your macros. tools_debug tools paths + email api diff --git a/source/easymacro.py b/source/easymacro.py index 35e032e..3d72460 100644 --- a/source/easymacro.py +++ b/source/easymacro.py @@ -1633,6 +1633,151 @@ class Url(object): return cls._open(url, data, headers, verify, json, timeout, 'POST') +class Email(object): + """Class for send email + """ + class SmtpServer(object): + + def __init__(self, config): + self._server = None + self._error = '' + self._sender = '' + self._is_connect = self._login(config) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + @property + def is_connect(self): + return self._is_connect + + @property + def error(self): + return self._error + + def _login(self, config): + name = config['server'] + port = config['port'] + is_ssl = config['ssl'] + self._sender = config['user'] + hosts = ('gmail' in name or 'outlook' in name) + try: + if is_ssl and hosts: + self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) + self._server.ehlo() + self._server.starttls() + self._server.ehlo() + elif is_ssl: + self._server = smtplib.SMTP_SSL(name, port, timeout=TIMEOUT) + self._server.ehlo() + else: + self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) + + self._server.login(self._sender, config['password']) + msg = 'Connect to: {}'.format(name) + debug(msg) + return True + except smtplib.SMTPAuthenticationError as e: + if '535' in str(e): + self._error = _('Incorrect user or password') + return False + if '534' in str(e) and 'gmail' in name: + self._error = _('Allow less secure apps in GMail') + return False + except smtplib.SMTPException as e: + self._error = str(e) + return False + except Exception as e: + self._error = str(e) + return False + return False + + def _body(self, msg): + body = msg.replace('\n', '
') + return body + + def send(self, message): + # ~ file_name = 'attachment; filename={}' + email = MIMEMultipart() + email['From'] = self._sender + email['To'] = message['to'] + email['Cc'] = message.get('cc', '') + email['Subject'] = message['subject'] + email['Date'] = formatdate(localtime=True) + if message.get('confirm', False): + email['Disposition-Notification-To'] = email['From'] + email.attach(MIMEText(self._body(message['body']), 'html')) + + paths = message.get('files', ()) + if isinstance(paths, str): + paths = (paths,) + for path in paths: + fn = _P(path).file_name + print('NAME', fn) + part = MIMEBase('application', 'octet-stream') + part.set_payload(_P.read_bin(path)) + encoders.encode_base64(part) + part.add_header('Content-Disposition', f'attachment; filename="{fn}"') + email.attach(part) + + receivers = ( + email['To'].split(',') + + email['CC'].split(',') + + message.get('bcc', '').split(',')) + try: + self._server.sendmail(self._sender, receivers, email.as_string()) + msg = 'Email sent...' + debug(msg) + if message.get('path', ''): + self.save_message(email, message['path']) + return True + except Exception as e: + self._error = str(e) + return False + return False + + def save_message(self, email, path): + mbox = mailbox.mbox(path, create=True) + mbox.lock() + try: + msg = mailbox.mboxMessage(email) + mbox.add(msg) + mbox.flush() + finally: + mbox.unlock() + return + + def close(self): + try: + self._server.quit() + msg = 'Close connection...' + debug(msg) + except: + pass + return + + @classmethod + def _send_email(cls, server, messages): + with cls.SmtpServer(server) as server: + if server.is_connect: + for msg in messages: + server.send(msg) + else: + error(server.error) + return server.error + + @classmethod + def send(cls, server: dict, messages: Union[dict, tuple, list]): + if isinstance(messages, dict): + messages = (messages,) + t = threading.Thread(target=cls._send_email, args=(server, messages)) + t.start() + return + + class Color(object): """Class for colors @@ -1820,6 +1965,7 @@ def __getattr__(name): 'path': Paths, 'config': Config, 'url': Url, + 'email': Email, 'color': Color(), } if name in classes: