2022-02-03 16:32:58 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
"""
|
2022-02-09 18:20:16 +01:00
|
|
|
SMTP webhook server
|
2022-02-03 16:32:58 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
import os
|
|
|
|
import json
|
|
|
|
import smtplib
|
|
|
|
import sys
|
2022-02-11 18:14:20 +01:00
|
|
|
import time
|
2022-02-03 16:32:58 +01:00
|
|
|
from email.header import Header
|
|
|
|
from email.mime.text import MIMEText
|
|
|
|
|
|
|
|
import cherrypy
|
2022-02-11 18:14:20 +01:00
|
|
|
import logging
|
2022-02-23 15:45:56 +01:00
|
|
|
import threading
|
|
|
|
|
2022-02-11 18:14:20 +01:00
|
|
|
|
2022-02-17 18:01:39 +01:00
|
|
|
logging.basicConfig(stream=sys.stdout,
|
2022-02-11 18:14:20 +01:00
|
|
|
format='%(name)s - %(levelname)s - %(message)s')
|
2022-02-03 16:32:58 +01:00
|
|
|
|
|
|
|
|
2022-02-23 15:45:56 +01:00
|
|
|
def soft_return(msg):
|
|
|
|
time.sleep(0.2)
|
|
|
|
return(msg)
|
|
|
|
|
|
|
|
|
2022-02-09 18:20:16 +01:00
|
|
|
class SMTPWebhookApp:
|
2022-02-03 16:32:58 +01:00
|
|
|
"""
|
2022-02-09 18:20:16 +01:00
|
|
|
SMTP webhook server
|
2022-02-03 16:32:58 +01:00
|
|
|
"""
|
|
|
|
|
2022-02-24 16:29:12 +01:00
|
|
|
def create_new_client(self, response):
|
|
|
|
try:
|
|
|
|
mythread.client = smtplib.SMTP(host=SMTP_SERVER_HOST,
|
|
|
|
port=SMTP_SERVER_PORT)
|
|
|
|
mythread.client.starttls()
|
2022-02-25 14:48:37 +01:00
|
|
|
return True
|
2022-02-24 16:29:12 +01:00
|
|
|
except (smtplib.SMTPException, TimeoutError) as e:
|
|
|
|
soft_return("can't connect")
|
2022-02-25 14:48:37 +01:00
|
|
|
response = {"status": 500, # noqa:F841
|
2022-02-24 16:29:12 +01:00
|
|
|
"message": "some error: {}".format(e)}
|
2022-02-25 14:48:37 +01:00
|
|
|
return False
|
2022-02-24 16:29:12 +01:00
|
|
|
|
|
|
|
def smtp_login(self, response, msg):
|
|
|
|
try:
|
|
|
|
mythread.client.login(msg["From"], FROM_MAIL_PASSWORD)
|
2022-02-25 14:48:37 +01:00
|
|
|
return True
|
2022-02-24 16:29:12 +01:00
|
|
|
except smtplib.SMTPException as e:
|
|
|
|
soft_return(e)
|
2022-02-25 14:48:37 +01:00
|
|
|
response = {"status": 500, # noqa:F841
|
2022-02-24 16:29:12 +01:00
|
|
|
"message": "some error: {}".format(e)}
|
2022-02-25 14:48:37 +01:00
|
|
|
return False
|
2022-02-24 16:29:12 +01:00
|
|
|
|
2022-02-03 16:32:58 +01:00
|
|
|
def _send_mail(self):
|
2022-02-11 18:14:20 +01:00
|
|
|
|
|
|
|
if not cherrypy.request.headers.get('Content-Length'):
|
2022-02-18 17:10:06 +01:00
|
|
|
logging.error("error: Invalid content length.")
|
2022-02-11 18:14:20 +01:00
|
|
|
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') == '':
|
2022-02-18 17:10:06 +01:00
|
|
|
logging.error("error: body field is required.")
|
2022-02-11 18:14:20 +01:00
|
|
|
return {"status": 400, "message": "subject field is required."}
|
|
|
|
|
|
|
|
if not req_body.get('body') or req_body.get('body') == '':
|
2022-02-18 17:10:06 +01:00
|
|
|
logging.error("error: body field is required.")
|
2022-02-11 18:14:20 +01:00
|
|
|
return {"status": 400, "message": "body field is required."}
|
|
|
|
|
2022-02-18 17:10:06 +01:00
|
|
|
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."}
|
|
|
|
|
2022-02-11 18:14:20 +01:00
|
|
|
subject = req_body['subject']
|
|
|
|
body = req_body['body']
|
2022-02-18 17:10:06 +01:00
|
|
|
to_mail = req_body['to_mail']
|
2022-02-11 18:14:20 +01:00
|
|
|
|
|
|
|
msg = MIMEText(body, 'plain', 'utf-8')
|
|
|
|
msg['Subject'] = Header(subject, 'utf-8')
|
|
|
|
msg['From'] = FROM_MAIL
|
2022-02-18 17:10:06 +01:00
|
|
|
msg['To'] = to_mail
|
2022-02-23 15:45:56 +01:00
|
|
|
|
|
|
|
c = 0
|
2022-02-24 16:29:12 +01:00
|
|
|
response = {}
|
2022-02-23 15:45:56 +01:00
|
|
|
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")
|
2022-02-24 16:29:12 +01:00
|
|
|
|
2022-02-25 14:48:37 +01:00
|
|
|
if not self.create_new_client(response):
|
|
|
|
break
|
|
|
|
if not self.smtp_login(response, msg):
|
|
|
|
break
|
2022-02-23 15:45:56 +01:00
|
|
|
|
|
|
|
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
|
2022-02-03 16:32:58 +01:00
|
|
|
|
|
|
|
@cherrypy.expose
|
|
|
|
def send_mail(self):
|
|
|
|
"""
|
2022-02-09 18:20:16 +01:00
|
|
|
api endpoint for send mail
|
2022-02-03 16:32:58 +01:00
|
|
|
"""
|
2022-02-11 18:14:20 +01:00
|
|
|
data = self._send_mail()
|
|
|
|
cherrypy.response.status = data["status"]
|
|
|
|
cherrypy.response.headers["Content-Type"] = "application/json"
|
|
|
|
return json.dumps(data["status"]).encode()
|
2022-02-03 16:32:58 +01:00
|
|
|
|
|
|
|
|
2022-02-09 18:20:16 +01:00
|
|
|
ROOT = SMTPWebhookApp()
|
2022-02-11 18:14:20 +01:00
|
|
|
SMTP_SERVER_PORT = 587
|
|
|
|
CHERRYPY_SERVER_HOST = "0.0.0.0"
|
|
|
|
CHERRYPY_SERVER_PORT = 8081
|
|
|
|
|
2022-02-03 16:32:58 +01:00
|
|
|
if __name__ == "__main__":
|
2022-02-07 16:28:53 +01:00
|
|
|
try:
|
2022-02-11 18:14:20 +01:00
|
|
|
SMTP_SERVER_HOST = os.environ["SMTP_SERVER_HOST"]
|
|
|
|
FROM_MAIL = os.environ["FROM_MAIL"]
|
|
|
|
FROM_MAIL_PASSWORD = os.environ["FROM_MAIL_PASSWORD"]
|
2022-02-09 18:20:16 +01:00
|
|
|
except KeyError:
|
2022-02-11 18:14:20 +01:00
|
|
|
raise KeyError("Please check missing environment variables: "
|
2022-02-18 17:10:06 +01:00
|
|
|
"SMTP_SERVER_HOST, FROM_MAIL, "
|
2022-02-11 18:14:20 +01:00
|
|
|
"FROM_MAIL_PASSWORD")
|
2022-02-07 16:28:53 +01:00
|
|
|
|
2022-02-11 18:14:20 +01:00
|
|
|
cherrypy.server.socket_host = CHERRYPY_SERVER_HOST
|
|
|
|
cherrypy.server.socket_port = CHERRYPY_SERVER_PORT
|
2022-02-03 16:32:58 +01:00
|
|
|
ENGINE = cherrypy.engine
|
|
|
|
|
2022-02-08 14:06:05 +01:00
|
|
|
cherrypy.tree.mount(ROOT, config={})
|
2022-02-23 15:45:56 +01:00
|
|
|
mythread = threading.local()
|
2022-02-24 16:29:12 +01:00
|
|
|
mythread.client = None
|
2022-02-17 18:01:39 +01:00
|
|
|
if hasattr(ENGINE, "signal_handler"):
|
|
|
|
ENGINE.signal_handler.subscribe()
|
|
|
|
if hasattr(ENGINE, "console_control_handler"):
|
|
|
|
ENGINE.console_control_handler.subscribe()
|
2022-02-03 16:32:58 +01:00
|
|
|
try:
|
|
|
|
ENGINE.start()
|
|
|
|
except Exception: # pylint: disable=broad-except
|
|
|
|
sys.exit(1)
|
|
|
|
else:
|
|
|
|
ENGINE.block()
|