295 lines
7.9 KiB
Python
295 lines
7.9 KiB
Python
# Copyright (c) 2020, JINGROW
|
|
# For license information, please see license.txt
|
|
|
|
import calendar
|
|
import json
|
|
import secrets
|
|
from datetime import datetime
|
|
|
|
import jingrow
|
|
import requests
|
|
from jingrow.exceptions import OutgoingEmailError, TooManyRequestsError, ValidationError
|
|
from jingrow.utils.password import get_decrypted_password
|
|
|
|
from jcloud.api.developer.marketplace import get_subscription_info
|
|
from jcloud.api.site import site_config, update_config
|
|
from jcloud.utils import log_error
|
|
|
|
|
|
class EmailLimitExceeded(TooManyRequestsError):
|
|
pass
|
|
|
|
|
|
class EmailSendError(OutgoingEmailError):
|
|
pass
|
|
|
|
|
|
class EmailConfigError(ValidationError):
|
|
http_status_code = 400
|
|
|
|
|
|
class SpamDetectionError(ValidationError):
|
|
http_status_code = 422
|
|
|
|
|
|
@jingrow.whitelist(allow_guest=True)
|
|
def email_ping():
|
|
return "pong"
|
|
|
|
|
|
def setup(site):
|
|
"""
|
|
set site config for overriding email account validations
|
|
"""
|
|
pg_exists = jingrow.db.exists("Mail Setup", {"site": site})
|
|
|
|
if pg_exists:
|
|
pg = jingrow.get_pg("Mail Setup", pg_exists)
|
|
|
|
if not pg.is_complete:
|
|
pg.is_complete = 1
|
|
pg.save()
|
|
|
|
return
|
|
|
|
old_config = site_config(site)
|
|
|
|
new_config = [
|
|
{"key": "mail_login", "value": "example@gmail.com", "type": "String"},
|
|
{"key": "mail_password", "value": "password", "type": "String"},
|
|
{"key": "mail_port", "value": 587, "type": "Number"},
|
|
{"key": "mail_server", "value": "smtp.gmail.com", "type": "String"},
|
|
]
|
|
for row in old_config:
|
|
new_config.append({"key": row.key, "value": row.value, "type": row.type})
|
|
|
|
update_config(site, json.dumps(new_config))
|
|
|
|
jingrow.get_pg({"pagetype": "Mail Setup", "site": site, "is_complete": 1}).insert(ignore_permissions=True)
|
|
|
|
|
|
@jingrow.whitelist(allow_guest=True)
|
|
def get_analytics(**data):
|
|
"""
|
|
send data for a specific month
|
|
"""
|
|
month = data.get("month")
|
|
year = datetime.now().year
|
|
last_day = calendar.monthrange(year, int(month))[1]
|
|
status = data.get("status")
|
|
site = data.get("site")
|
|
subscription_key = data.get("key")
|
|
|
|
for value in (site, subscription_key):
|
|
if not value or not isinstance(value, str):
|
|
jingrow.throw("Invalid Request")
|
|
|
|
return jingrow.get_all(
|
|
"Mail Log",
|
|
filters={
|
|
"site": site,
|
|
"subscription_key": subscription_key,
|
|
"status": ["like", f"%{status}%"],
|
|
"date": ["between", [f"{month}-01-{year}", f"{month}-{last_day}-{year}"]],
|
|
},
|
|
fields=["date", "status", "message", "sender", "recipient"],
|
|
order_by="date asc",
|
|
)
|
|
|
|
|
|
def validate_plan(secret_key):
|
|
"""
|
|
check if subscription is active on marketplace and valid
|
|
#TODO: get activation date
|
|
"""
|
|
|
|
# TODO: replace this with plan attributes
|
|
plan_label_map = jingrow.conf.email_plans
|
|
|
|
if not secret_key:
|
|
jingrow.throw(
|
|
"Secret key missing. Email Delivery Service seems to be improperly installed. Try uninstalling and reinstalling it.",
|
|
EmailConfigError,
|
|
)
|
|
|
|
try:
|
|
subscription = get_subscription_info(secret_key=secret_key)
|
|
except Exception as e:
|
|
jingrow.throw(
|
|
str(e)
|
|
or "Something went wrong fetching subscription details of Email Delivery Service. Please raise a ticket at support.jingrow.com",
|
|
type(e),
|
|
)
|
|
|
|
if not subscription["enabled"]:
|
|
jingrow.throw(
|
|
"Your subscription is not active. Try activating it from, "
|
|
f"{jingrow.utils.get_url()}/dashboard/sites/{subscription['site']}/overview",
|
|
EmailConfigError,
|
|
)
|
|
|
|
# TODO: add a date filter(use start date from plan)
|
|
first_day = str(jingrow.utils.now_datetime().replace(day=1).date())
|
|
count = jingrow.db.count(
|
|
"Mail Log",
|
|
filters={
|
|
"site": subscription["site"],
|
|
"creation": (">=", first_day),
|
|
"subscription_key": secret_key,
|
|
},
|
|
)
|
|
if not count < plan_label_map[subscription["plan"]]:
|
|
jingrow.throw(
|
|
"You have exceeded your quota for Email Delivery Service. Try upgrading it from, "
|
|
f"{jingrow.utils.get_url()}/dashboard/sites/{subscription['site']}/overview",
|
|
EmailLimitExceeded,
|
|
)
|
|
|
|
|
|
def check_spam(message: bytes):
|
|
jcloud_settings = jingrow.get_cached_value(
|
|
"Jcloud Settings",
|
|
None,
|
|
["enable_spam_check", "spamd_endpoint", "spamd_api_key"],
|
|
as_dict=True,
|
|
)
|
|
if not jcloud_settings.enable_spam_check:
|
|
return
|
|
try:
|
|
headers = {}
|
|
if jcloud_settings.spamd_api_key:
|
|
spamd_api_secret = get_decrypted_password("Jcloud Settings", "Jcloud Settings", "spamd_api_secret")
|
|
headers["Authorization"] = f"token {jcloud_settings.spamd_api_key}:{spamd_api_secret}"
|
|
resp = requests.post(
|
|
jcloud_settings.spamd_endpoint,
|
|
headers=headers,
|
|
files={"message": message},
|
|
)
|
|
resp.raise_for_status()
|
|
data = resp.json()
|
|
if data["message"] > 3.5:
|
|
jingrow.throw(
|
|
"This email was blocked as it was flagged as spam by our system. Please review the contents and try again.",
|
|
SpamDetectionError,
|
|
)
|
|
except requests.exceptions.HTTPError as e:
|
|
# Ignore error, if server.jingrowmail.com is being updated.
|
|
if e.response.status_code != 503:
|
|
log_error("Spam Detection : Error", data=e)
|
|
|
|
|
|
@jingrow.whitelist(allow_guest=True)
|
|
def send_mime_mail(**data):
|
|
"""
|
|
send api request to mailgun
|
|
"""
|
|
files = jingrow._dict(jingrow.request.files)
|
|
data = json.loads(data["data"])
|
|
|
|
validate_plan(data["sk_mail"])
|
|
|
|
api_key, domain = jingrow.db.get_value("Jcloud Settings", None, ["mailgun_api_key", "root_domain"])
|
|
|
|
message: bytes = files["mime"].read()
|
|
check_spam(message)
|
|
|
|
resp = requests.post(
|
|
f"https://api.mailgun.net/v3/{domain}/messages.mime",
|
|
auth=("api", f"{api_key}"),
|
|
data={"to": data["recipients"], "v:sk_mail": data["sk_mail"]},
|
|
files={"message": message},
|
|
)
|
|
|
|
if resp.status_code == 200:
|
|
return "Sending" # Not really required as v14 and up automatically marks the email q as sent
|
|
log_error("Email Delivery Service: Sending error", data=resp.text)
|
|
jingrow.throw(
|
|
"Something went wrong with sending emails. Please try again later or raise a support ticket with support.jingrow.com",
|
|
EmailSendError,
|
|
)
|
|
return None
|
|
|
|
|
|
def is_valid_mailgun_event(event_data):
|
|
if not event_data:
|
|
return None
|
|
|
|
if event_data.get("user-variables", {}).get("sk_mail") is None:
|
|
# We don't know where to send this event
|
|
# TOOD: Investigate why this is happening
|
|
# Hint: Likely from other emails not sent via the email delivery app
|
|
return None
|
|
|
|
if "delivery-status" not in event_data:
|
|
return None
|
|
|
|
if "message" not in event_data["delivery-status"]:
|
|
return None
|
|
|
|
return True
|
|
|
|
|
|
@jingrow.whitelist(allow_guest=True)
|
|
def event_log():
|
|
"""
|
|
log the webhook and forward it to site
|
|
"""
|
|
data = json.loads(jingrow.request.data)
|
|
event_data = data.get("event-data")
|
|
|
|
if not is_valid_mailgun_event(event_data):
|
|
return None
|
|
|
|
try:
|
|
secret_key = event_data["user-variables"]["sk_mail"]
|
|
headers = event_data["message"]["headers"]
|
|
if "message-id" not in headers:
|
|
# We can't log this event without a message-id
|
|
# TOOD: Investigate why this is happening
|
|
return None
|
|
message_id = headers["message-id"]
|
|
site = (
|
|
jingrow.get_cached_value("Subscription", {"secret_key": secret_key}, "site")
|
|
or message_id.split("@")[1]
|
|
)
|
|
status = event_data["event"]
|
|
delivery_message = (
|
|
event_data["delivery-status"]["message"] or event_data["delivery-status"]["description"]
|
|
)
|
|
jingrow.get_pg(
|
|
{
|
|
"pagetype": "Mail Log",
|
|
"unique_token": secrets.token_hex(25),
|
|
"message_id": message_id,
|
|
"sender": headers["from"],
|
|
"recipient": event_data.get("recipient") or headers.get("to"),
|
|
"site": site,
|
|
"status": event_data["event"],
|
|
"subscription_key": secret_key,
|
|
"message": delivery_message,
|
|
"log": json.dumps(data),
|
|
}
|
|
).insert(ignore_permissions=True)
|
|
jingrow.db.commit()
|
|
except Exception:
|
|
log_error("Mail App: Event log error", data=data)
|
|
raise
|
|
|
|
data = {
|
|
"status": status,
|
|
"message_id": message_id,
|
|
"delivery_message": delivery_message,
|
|
"secret_key": secret_key,
|
|
}
|
|
|
|
try:
|
|
host_name = jingrow.db.get_value("Site", site, "host_name") or site
|
|
requests.post(
|
|
f"https://{host_name}/api/action/email_delivery_service.controller.update_status",
|
|
data=data,
|
|
)
|
|
except Exception as e:
|
|
log_error("Mail App: Email status update error", data=e)
|
|
|
|
return "Successful", 200
|