#!/usr/bin/env python3 """ SMTP webhook server """ import os import json import smtplib import sys import time from email.header import Header from email.mime.text import MIMEText import cherrypy import logging import threading logging.basicConfig(stream=sys.stdout, format='%(name)s - %(levelname)s - %(message)s') def soft_return(msg): time.sleep(0.2) return(msg) class SMTPWebhookApp: """ SMTP webhook server """ def create_new_client(self, response): try: mythread.client = smtplib.SMTP(host=SMTP_SERVER_HOST, port=SMTP_SERVER_PORT) mythread.client.starttls() return True except (smtplib.SMTPException, TimeoutError) as e: soft_return("can't connect") response = {"status": 500, # noqa:F841 "message": "some error: {}".format(e)} return False def smtp_login(self, response, msg): try: mythread.client.login(msg["From"], FROM_MAIL_PASSWORD) return True except smtplib.SMTPException as e: soft_return(e) response = {"status": 500, # noqa:F841 "message": "some error: {}".format(e)} return False def _send_mail(self): if not cherrypy.request.headers.get('Content-Length'): logging.error("error: Invalid content length.") return {"status": 400, "message": "Invalid content length."} cl = cherrypy.request.headers['Content-Length'] rawbody = cherrypy.request.body.read(int(cl)) req_body = json.loads(rawbody) if not req_body.get('subject') or req_body.get('subject') == '': logging.error("error: body field is required.") return {"status": 400, "message": "subject field is required."} if not req_body.get('body') or req_body.get('body') == '': logging.error("error: body field is required.") return {"status": 400, "message": "body field is required."} if not req_body.get('to_mail') or req_body.get('to_mail') == '': logging.error("error: to_mail field is required.") return {"status": 400, "message": "to_mail field is required."} subject = req_body['subject'] body = req_body['body'] to_mail = req_body['to_mail'] msg = MIMEText(body, 'plain', 'utf-8') msg['Subject'] = Header(subject, 'utf-8') msg['From'] = FROM_MAIL msg['To'] = to_mail c = 0 response = {} while (c < 2): try: mythread.client.sendmail(msg['From'], msg['To'], msg.as_string()) response = {"status": 200, "message": "mail sent successfully"} except (AttributeError, ValueError, smtplib.SMTPServerDisconnected): if (c == 1): soft_return("can't connect") if not self.create_new_client(response): break if not self.smtp_login(response, msg): break except smtplib.SMTPException as e: soft_return("can't send for some reason") response = {"status": 500, "message": "some error: {}".format(e)} finally: c += 1 return response @cherrypy.expose def send_mail(self): """ api endpoint for send mail """ data = self._send_mail() cherrypy.response.status = data["status"] cherrypy.response.headers["Content-Type"] = "application/json" return json.dumps(data["status"]).encode() ROOT = SMTPWebhookApp() SMTP_SERVER_PORT = 587 CHERRYPY_SERVER_HOST = "0.0.0.0" CHERRYPY_SERVER_PORT = 8081 if __name__ == "__main__": try: SMTP_SERVER_HOST = os.environ["SMTP_SERVER_HOST"] FROM_MAIL = os.environ["FROM_MAIL"] FROM_MAIL_PASSWORD = os.environ["FROM_MAIL_PASSWORD"] except KeyError: raise KeyError("Please check missing environment variables: " "SMTP_SERVER_HOST, FROM_MAIL, " "FROM_MAIL_PASSWORD") cherrypy.server.socket_host = CHERRYPY_SERVER_HOST cherrypy.server.socket_port = CHERRYPY_SERVER_PORT ENGINE = cherrypy.engine cherrypy.tree.mount(ROOT, config={}) mythread = threading.local() mythread.client = None if hasattr(ENGINE, "signal_handler"): ENGINE.signal_handler.subscribe() if hasattr(ENGINE, "console_control_handler"): ENGINE.console_control_handler.subscribe() try: ENGINE.start() except Exception: # pylint: disable=broad-except sys.exit(1) else: ENGINE.block()