2016-11-20 20:04:52 +01:00
|
|
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
;;
|
2021-03-30 14:55:19 +02:00
|
|
|
;; Copyright (c) UXBOX Labs SL
|
2016-11-20 20:04:52 +01:00
|
|
|
|
2020-08-18 19:26:37 +02:00
|
|
|
(ns app.emails
|
2016-11-20 20:04:52 +01:00
|
|
|
"Main api for send emails."
|
2019-11-18 11:52:57 +01:00
|
|
|
(:require
|
2021-09-29 16:39:25 +02:00
|
|
|
[app.common.logging :as l]
|
2020-08-18 19:26:37 +02:00
|
|
|
[app.common.spec :as us]
|
2021-09-16 14:40:50 +02:00
|
|
|
[app.config :as cf]
|
2021-02-11 17:57:41 +01:00
|
|
|
[app.db :as db]
|
|
|
|
[app.db.sql :as sql]
|
2020-12-02 12:36:08 +01:00
|
|
|
[app.util.emails :as emails]
|
2021-03-30 14:55:19 +02:00
|
|
|
[app.worker :as wrk]
|
|
|
|
[clojure.spec.alpha :as s]
|
|
|
|
[integrant.core :as ig]))
|
2016-11-20 20:04:52 +01:00
|
|
|
|
2021-03-30 14:55:19 +02:00
|
|
|
;; --- PUBLIC API
|
2021-04-06 23:25:34 +02:00
|
|
|
|
2019-11-22 18:08:27 +01:00
|
|
|
(defn render
|
2020-11-10 18:24:02 +01:00
|
|
|
[email-factory context]
|
|
|
|
(email-factory context))
|
2019-11-18 11:52:57 +01:00
|
|
|
|
|
|
|
(defn send!
|
|
|
|
"Schedule the email for sending."
|
2021-03-30 14:55:19 +02:00
|
|
|
[{:keys [::conn ::factory] :as context}]
|
|
|
|
(us/verify fn? factory)
|
|
|
|
(us/verify some? conn)
|
|
|
|
(let [email (factory context)]
|
|
|
|
(wrk/submit! (assoc email
|
|
|
|
::wrk/task :sendmail
|
|
|
|
::wrk/delay 0
|
|
|
|
::wrk/max-retries 1
|
|
|
|
::wrk/priority 200
|
|
|
|
::wrk/conn conn))))
|
2020-01-13 23:52:31 +01:00
|
|
|
|
2021-02-11 17:57:41 +01:00
|
|
|
|
2021-03-30 14:55:19 +02:00
|
|
|
;; --- BOUNCE/COMPLAINS HANDLING
|
|
|
|
|
2021-02-11 17:57:41 +01:00
|
|
|
(def sql:profile-complaint-report
|
|
|
|
"select (select count(*)
|
|
|
|
from profile_complaint_report
|
|
|
|
where type = 'complaint'
|
|
|
|
and profile_id = ?
|
|
|
|
and created_at > now() - ?::interval) as complaints,
|
|
|
|
(select count(*)
|
|
|
|
from profile_complaint_report
|
|
|
|
where type = 'bounce'
|
|
|
|
and profile_id = ?
|
|
|
|
and created_at > now() - ?::interval) as bounces;")
|
|
|
|
|
|
|
|
(defn allow-send-emails?
|
|
|
|
[conn profile]
|
|
|
|
(when-not (:is-muted profile false)
|
2021-09-16 14:40:50 +02:00
|
|
|
(let [complaint-threshold (cf/get :profile-complaint-threshold)
|
|
|
|
complaint-max-age (cf/get :profile-complaint-max-age)
|
|
|
|
bounce-threshold (cf/get :profile-bounce-threshold)
|
|
|
|
bounce-max-age (cf/get :profile-bounce-max-age)
|
2021-02-11 17:57:41 +01:00
|
|
|
|
|
|
|
{:keys [complaints bounces] :as result}
|
|
|
|
(db/exec-one! conn [sql:profile-complaint-report
|
|
|
|
(:id profile)
|
|
|
|
(db/interval complaint-max-age)
|
|
|
|
(:id profile)
|
|
|
|
(db/interval bounce-max-age)])]
|
|
|
|
|
2021-12-27 11:13:08 +01:00
|
|
|
(and (< (or complaints 0) complaint-threshold)
|
|
|
|
(< (or bounces 0) bounce-threshold)))))
|
2021-02-11 17:57:41 +01:00
|
|
|
|
|
|
|
(defn has-complaint-reports?
|
|
|
|
([conn email] (has-complaint-reports? conn email nil))
|
|
|
|
([conn email {:keys [threshold] :or {threshold 1}}]
|
|
|
|
(let [reports (db/exec! conn (sql/select :global-complaint-report
|
|
|
|
{:email email :type "complaint"}
|
|
|
|
{:limit 10}))]
|
|
|
|
(>= (count reports) threshold))))
|
|
|
|
|
|
|
|
(defn has-bounce-reports?
|
|
|
|
([conn email] (has-bounce-reports? conn email nil))
|
|
|
|
([conn email {:keys [threshold] :or {threshold 1}}]
|
|
|
|
(let [reports (db/exec! conn (sql/select :global-complaint-report
|
|
|
|
{:email email :type "bounce"}
|
|
|
|
{:limit 10}))]
|
|
|
|
(>= (count reports) threshold))))
|
|
|
|
|
|
|
|
|
2021-03-30 14:55:19 +02:00
|
|
|
;; --- EMAIL FACTORIES
|
2020-01-13 23:52:31 +01:00
|
|
|
|
2021-02-08 22:39:11 +01:00
|
|
|
(s/def ::subject ::us/string)
|
|
|
|
(s/def ::content ::us/string)
|
|
|
|
|
|
|
|
(s/def ::feedback
|
|
|
|
(s/keys :req-un [::subject ::content]))
|
|
|
|
|
|
|
|
(def feedback
|
|
|
|
"A profile feedback email."
|
2021-03-30 14:55:19 +02:00
|
|
|
(emails/template-factory ::feedback))
|
2021-02-08 22:39:11 +01:00
|
|
|
|
2020-01-13 23:52:31 +01:00
|
|
|
(s/def ::name ::us/string)
|
|
|
|
(s/def ::register
|
|
|
|
(s/keys :req-un [::name]))
|
|
|
|
|
|
|
|
(def register
|
|
|
|
"A new profile registration welcome email."
|
2021-03-30 14:55:19 +02:00
|
|
|
(emails/template-factory ::register))
|
2020-01-13 23:52:31 +01:00
|
|
|
|
|
|
|
(s/def ::token ::us/string)
|
|
|
|
(s/def ::password-recovery
|
|
|
|
(s/keys :req-un [::name ::token]))
|
|
|
|
|
|
|
|
(def password-recovery
|
|
|
|
"A password recovery notification email."
|
2021-03-30 14:55:19 +02:00
|
|
|
(emails/template-factory ::password-recovery))
|
2020-05-22 13:48:21 +02:00
|
|
|
|
2020-05-26 12:28:35 +02:00
|
|
|
(s/def ::pending-email ::us/email)
|
2020-05-22 13:48:21 +02:00
|
|
|
(s/def ::change-email
|
|
|
|
(s/keys :req-un [::name ::pending-email ::token]))
|
|
|
|
|
|
|
|
(def change-email
|
|
|
|
"Password change confirmation email"
|
2021-03-30 14:55:19 +02:00
|
|
|
(emails/template-factory ::change-email))
|
2020-10-05 18:12:40 +02:00
|
|
|
|
|
|
|
(s/def :internal.emails.invite-to-team/invited-by ::us/string)
|
|
|
|
(s/def :internal.emails.invite-to-team/team ::us/string)
|
|
|
|
(s/def :internal.emails.invite-to-team/token ::us/string)
|
|
|
|
|
|
|
|
(s/def ::invite-to-team
|
2021-06-09 15:01:17 +02:00
|
|
|
(s/keys :req-un [:internal.emails.invite-to-team/invited-by
|
|
|
|
:internal.emails.invite-to-team/token
|
|
|
|
:internal.emails.invite-to-team/team]))
|
2020-10-05 18:12:40 +02:00
|
|
|
|
|
|
|
(def invite-to-team
|
|
|
|
"Teams member invitation email."
|
2021-03-30 14:55:19 +02:00
|
|
|
(emails/template-factory ::invite-to-team))
|
|
|
|
|
|
|
|
|
|
|
|
;; --- SENDMAIL TASK
|
|
|
|
|
|
|
|
(declare send-console!)
|
|
|
|
|
2021-09-16 14:40:50 +02:00
|
|
|
(s/def ::username ::cf/smtp-username)
|
|
|
|
(s/def ::password ::cf/smtp-password)
|
|
|
|
(s/def ::tls ::cf/smtp-tls)
|
|
|
|
(s/def ::ssl ::cf/smtp-ssl)
|
|
|
|
(s/def ::host ::cf/smtp-host)
|
|
|
|
(s/def ::port ::cf/smtp-port)
|
|
|
|
(s/def ::default-reply-to ::cf/smtp-default-reply-to)
|
|
|
|
(s/def ::default-from ::cf/smtp-default-from)
|
2021-03-30 14:55:19 +02:00
|
|
|
|
|
|
|
(defmethod ig/pre-init-spec ::sendmail-handler [_]
|
2021-09-16 14:40:50 +02:00
|
|
|
(s/keys :opt-un [::username
|
2021-03-30 14:55:19 +02:00
|
|
|
::password
|
|
|
|
::tls
|
|
|
|
::ssl
|
|
|
|
::host
|
|
|
|
::port
|
|
|
|
::default-from
|
|
|
|
::default-reply-to]))
|
|
|
|
|
|
|
|
(defmethod ig/init-key ::sendmail-handler
|
|
|
|
[_ cfg]
|
|
|
|
(fn [{:keys [props] :as task}]
|
2021-09-16 14:40:50 +02:00
|
|
|
(let [enabled? (or (contains? cf/flags :smtp)
|
|
|
|
(cf/get :smtp-enabled)
|
|
|
|
(:enabled task))]
|
|
|
|
(if enabled?
|
|
|
|
(emails/send! cfg props)
|
|
|
|
(send-console! cfg props)))))
|
2021-03-30 14:55:19 +02:00
|
|
|
|
|
|
|
(defn- send-console!
|
|
|
|
[cfg email]
|
|
|
|
(let [baos (java.io.ByteArrayOutputStream.)
|
|
|
|
mesg (emails/smtp-message cfg email)]
|
|
|
|
(.writeTo mesg baos)
|
|
|
|
(let [out (with-out-str
|
|
|
|
(println "email console dump:")
|
|
|
|
(println "******** start email" (:id email) "**********")
|
|
|
|
(println (.toString baos))
|
|
|
|
(println "******** end email "(:id email) "**********"))]
|
2021-04-06 23:25:34 +02:00
|
|
|
(l/info :email out))))
|
2021-03-30 14:55:19 +02:00
|
|
|
|