From 96472b6cd216703cf9ea328e9fe6be7d5d20196d Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Sun, 19 Apr 2020 19:36:48 +0200 Subject: [PATCH 1/4] :tada: Add redis to devenv docker compose. --- docker/devenv/Dockerfile | 1 + docker/devenv/docker-compose.yaml | 24 ++++++++++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile index 16646f97ac..98994a87a5 100644 --- a/docker/devenv/Dockerfile +++ b/docker/devenv/Dockerfile @@ -32,6 +32,7 @@ RUN set -ex; \ imagemagick \ webp \ jq \ + redis-tools \ ; \ rm -rf /var/lib/apt/lists/*; diff --git a/docker/devenv/docker-compose.yaml b/docker/devenv/docker-compose.yaml index f073f73f95..aa36571d3f 100644 --- a/docker/devenv/docker-compose.yaml +++ b/docker/devenv/docker-compose.yaml @@ -1,4 +1,4 @@ -version: '3' +version: "3" networks: default: @@ -15,13 +15,16 @@ services: main: privileged: true image: "uxbox-devenv" - hostname: 'uxbox-devenv-main' - container_name: 'uxbox-devenv-main' + hostname: "uxbox-devenv-main" + container_name: "uxbox-devenv-main" command: "/home/uxbox/init.sh" stop_signal: SIGINT + depends_on: - postgres - smtp + - redis + volumes: - "user_data:/home/uxbox/local" - "${HOME}/.m2:/home/uxbox/.m2" @@ -41,7 +44,7 @@ services: - UXBOX_DATABASE_PASSWORD=uxbox smtp: - container_name: 'uxbox-devenv-smtp' + container_name: "uxbox-devenv-smtp" image: mwader/postfix-relay restart: always environment: @@ -51,8 +54,8 @@ services: postgres: image: postgres:12 command: postgres -c config_file=/etc/postgresql.conf - hostname: 'uxbox-devenv-postgres' - container_name: 'uxbox-devenv-postgres' + hostname: "uxbox-devenv-postgres" + container_name: "uxbox-devenv-postgres" restart: always stop_signal: SIGINT ports: @@ -66,3 +69,12 @@ services: - ./files/postgresql.conf:/etc/postgresql.conf - ./files/postgresql_init.sql:/docker-entrypoint-initdb.d/init.sql - postgres_data:/var/lib/postgresql/data + + redis: + image: redis:6.0-rc3 + hostname: "uxbox-devenv-redis" + container_name: "uxbox-devenv-redis" + restart: always + + ports: + - 6379:6379 From 6ba3a281430330466f8aee980ea481f862c8f730 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Sun, 19 Apr 2020 19:37:15 +0200 Subject: [PATCH 2/4] :tada: Add initial redis client foundation. --- backend/deps.edn | 1 + backend/src/uxbox/config.clj | 3 + backend/src/uxbox/redis.clj | 49 ++++++++++++++++ backend/src/uxbox/util/redis.clj | 99 ++++++++++++++++++++++++++++++++ backend/tests/user.clj | 1 + 5 files changed, 153 insertions(+) create mode 100644 backend/src/uxbox/redis.clj create mode 100644 backend/src/uxbox/util/redis.clj diff --git a/backend/deps.edn b/backend/deps.edn index 2580d01fc0..ba2aa49759 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -20,6 +20,7 @@ ;; TODO: vendorize pgclient under `vertx-clojure/vertx-pgclient` io.vertx/vertx-pg-client {:mvn/version "4.0.0-milestone4"} + io.lettuce/lettuce-core {:mvn/version "5.2.2.RELEASE"} vertx-clojure/vertx {:local/root "vendor/vertx" diff --git a/backend/src/uxbox/config.clj b/backend/src/uxbox/config.clj index 88a7fc3f69..0b4661e15b 100644 --- a/backend/src/uxbox/config.clj +++ b/backend/src/uxbox/config.clj @@ -25,6 +25,8 @@ :database-uri "postgresql://127.0.0.1/uxbox" :database-username "uxbox" :database-password "uxbox" + + :redis-uri "redis://redis/0" :media-directory "resources/public/media" :assets-directory "resources/public/static" :media-uri "http://localhost:6060/media/" @@ -44,6 +46,7 @@ (s/def ::database-username (s/nilable ::us/string)) (s/def ::database-password (s/nilable ::us/string)) (s/def ::database-uri ::us/string) +(s/def ::redis-uri ::us/string) (s/def ::assets-uri ::us/string) (s/def ::assets-directory ::us/string) (s/def ::media-uri ::us/string) diff --git a/backend/src/uxbox/redis.clj b/backend/src/uxbox/redis.clj new file mode 100644 index 0000000000..8c8ae0f594 --- /dev/null +++ b/backend/src/uxbox/redis.clj @@ -0,0 +1,49 @@ +;; 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/. +;; +;; Copyright (c) 2019 Andrey Antukh + +(ns uxbox.redis + (:refer-clojure :exclude [run!]) + (:require + [clojure.tools.logging :as log] + [lambdaisland.uri :refer [uri]] + [mount.core :as mount :refer [defstate]] + [promesa.core :as p] + [uxbox.common.exceptions :as ex] + [uxbox.config :as cfg] + [uxbox.core :refer [system]] + [uxbox.util.redis :as redis] + [uxbox.util.data :as data] + [vertx.util :as vu]) + (:import + java.lang.AutoCloseable)) + +;; --- Connection Handling & State + +(defn- create-client + [config] + (let [uri (:redis-uri config "redis://redis/0")] + (log/info "creating redis client with" uri) + (redis/client uri))) + +(defstate client + :start (create-client cfg/config) + :stop (.close ^AutoCloseable client)) + +(defstate conn + :start (redis/connect client) + :stop (.close ^AutoCloseable conn)) + +;; --- API FORWARD + +(defmacro with-conn + [& args] + `(redis/with-conn ~@args)) + +(defn run! + [conn cmd params] + (let [ctx (vu/get-or-create-context system)] + (-> (redis/run! conn cmd params) + (vu/handle-on-context ctx)))) diff --git a/backend/src/uxbox/util/redis.clj b/backend/src/uxbox/util/redis.clj new file mode 100644 index 0000000000..4fab67410e --- /dev/null +++ b/backend/src/uxbox/util/redis.clj @@ -0,0 +1,99 @@ +;; 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/. +;; +;; Copyright (c) 2019 Andrey Antukh + +(ns uxbox.util.redis + "Asynchronous posgresql client." + (:refer-clojure :exclude [get set run!]) + (:require + [promesa.core :as p]) + (:import + io.lettuce.core.RedisClient + io.lettuce.core.RedisURI + io.lettuce.core.codec.StringCodec + io.lettuce.core.api.async.RedisAsyncCommands + io.lettuce.core.api.StatefulRedisConnection + )) + +(defrecord Client [conn uri] + java.lang.AutoCloseable + (close [_] + (.shutdown ^RedisClient conn))) + +(defrecord Connection [cmd conn] + java.lang.AutoCloseable + (close [_] + (.close ^StatefulRedisConnection conn))) + +(defn client + [uri] + (->Client (RedisClient/create) (RedisURI/create uri))) + +(defn connect + [client] + (let [^RedisURI uri (:uri client) + ^RedisClient conn (:conn client) + ^StatefulRedisConnection conn' (.connect conn StringCodec/UTF8 uri)] + (->Connection (.async conn') conn'))) + +(declare impl-with-conn) + +(defmacro with-conn + [[csym sym] & body] + `(impl-with-conn ~sym (fn [~csym] ~@body))) + +(defn impl-with-conn + [client f] + (let [^RedisURI uri (:uri client) + ^RedisClient conn (:conn client)] + (-> (.connectAsync conn StringCodec/UTF8 uri) + (p/then (fn [^StatefulRedisConnection conn] + (let [cmd (.async conn) + conn (->Connection cmd conn)] + (-> (p/do! (f conn)) + (p/handle (fn [v e] + (.close conn) + (if e + (throw e) + v)))))))))) + +(defn- resolve-to-bool + [v] + (if (= v 1) + true + false)) + +(defmulti impl-run (fn [conn cmd parmas] cmd)) + +(defn run! + [conn cmd params] + (let [^RedisAsyncCommands conn (:cmd conn)] + (impl-run conn cmd params))) + +(defmethod impl-run :get + [conn _ {:keys [key]}] + (.get ^RedisAsyncCommands conn ^String key)) + +(defmethod impl-run :set + [conn _ {:keys [key val]}] + (.set ^RedisAsyncCommands conn ^String key ^String val)) + +(defmethod impl-run :smembers + [conn _ {:keys [key]}] + (-> (.smembers ^RedisAsyncCommands conn ^String key) + (p/then' #(into #{} %)))) + +(defmethod impl-run :sadd + [conn _ {:keys [key val]}] + (let [keys (into-array String [val])] + (-> (.sadd ^RedisAsyncCommands conn ^String key ^"[S;" keys) + (p/then resolve-to-bool)))) + +(defmethod impl-run :srem + [conn _ {:keys [key val]}] + (let [keys (into-array String [val])] + (-> (.srem ^RedisAsyncCommands conn ^String key ^"[S;" keys) + (p/then resolve-to-bool)))) + diff --git a/backend/tests/user.clj b/backend/tests/user.clj index 2c1f43b82e..c94a8636eb 100644 --- a/backend/tests/user.clj +++ b/backend/tests/user.clj @@ -23,6 +23,7 @@ [promesa.exec :as px] [uxbox.migrations] [uxbox.db :as db] + [uxbox.redis :as rd] [uxbox.util.storage :as st] [uxbox.util.time :as tm] [uxbox.util.blob :as blob] From 7fba483bf13eaf51f92edbfe8a43990327ce9d18 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 20 Apr 2020 13:44:42 +0200 Subject: [PATCH 3/4] :recycle: Refactor email sending. --- backend/deps.edn | 14 ++-- .../en.mustache} | 0 .../en.mustache} | 0 .../fr.mustache} | 0 backend/src/uxbox/config.clj | 19 +++-- backend/src/uxbox/emails.clj | 15 ++-- backend/src/uxbox/tasks/impl.clj | 3 +- backend/src/uxbox/tasks/sendmail.clj | 74 +++++++++---------- backend/src/uxbox/util/emails.clj | 17 +++-- backend/src/uxbox/util/http.clj | 13 +++- 10 files changed, 87 insertions(+), 68 deletions(-) rename backend/resources/emails/{en/password-recovery.mustache => password-recovery/en.mustache} (100%) rename backend/resources/emails/{en/register.mustache => register/en.mustache} (100%) rename backend/resources/emails/{fr/register.mustache => register/fr.mustache} (100%) diff --git a/backend/deps.edn b/backend/deps.edn index ba2aa49759..b0408e1651 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -4,8 +4,7 @@ "jcenter" {:url "https://jcenter.bintray.com/"}} :deps {org.clojure/clojure {:mvn/version "1.10.1"} - funcool/promesa {:mvn/version "5.1.0"} - funcool/cuerdas {:mvn/version "2020.03.26-3"} + org.clojure/data.json {:mvn/version "1.0.0"} ;; Logging org.clojure/tools.logging {:mvn/version "0.5.0"} @@ -13,7 +12,7 @@ org.apache.logging.log4j/log4j-core {:mvn/version "2.13.0"} org.apache.logging.log4j/log4j-jul {:mvn/version "2.13.0"} org.apache.logging.log4j/log4j-slf4j-impl {:mvn/version "2.13.0"} - funcool/datoteka {:mvn/version "1.2.0"} + expound/expound {:mvn/version "0.8.4"} instaparse/instaparse {:mvn/version "1.4.10"} com.cognitect/transit-clj {:mvn/version "0.8.319"} @@ -21,23 +20,26 @@ ;; TODO: vendorize pgclient under `vertx-clojure/vertx-pgclient` io.vertx/vertx-pg-client {:mvn/version "4.0.0-milestone4"} io.lettuce/lettuce-core {:mvn/version "5.2.2.RELEASE"} + java-http-clj/java-http-clj {:mvn/version "0.4.1"} vertx-clojure/vertx {:local/root "vendor/vertx" :deps/manifest :pom} + funcool/datoteka {:mvn/version "1.2.0"} + funcool/promesa {:mvn/version "5.1.0"} + funcool/cuerdas {:mvn/version "2020.03.26-3"} funcool/sodi {:local/root "vendor/sodi" :deps/manifest :pom} - lambdaisland/uri {:mvn/version "1.1.0"} + lambdaisland/uri {:mvn/version "1.1.0" + :exclusions [org.clojure/data.json]} danlentz/clj-uuid {:mvn/version "0.1.9"} org.jsoup/jsoup {:mvn/version "1.12.1"} org.im4java/im4java {:mvn/version "1.4.0"} - org.lz4/lz4-java {:mvn/version "1.7.1"} - com.github.spullara.mustache.java/compiler {:mvn/version "0.9.6"} commons-io/commons-io {:mvn/version "2.6"} com.draines/postal {:mvn/version "2.0.3" diff --git a/backend/resources/emails/en/password-recovery.mustache b/backend/resources/emails/password-recovery/en.mustache similarity index 100% rename from backend/resources/emails/en/password-recovery.mustache rename to backend/resources/emails/password-recovery/en.mustache diff --git a/backend/resources/emails/en/register.mustache b/backend/resources/emails/register/en.mustache similarity index 100% rename from backend/resources/emails/en/register.mustache rename to backend/resources/emails/register/en.mustache diff --git a/backend/resources/emails/fr/register.mustache b/backend/resources/emails/register/fr.mustache similarity index 100% rename from backend/resources/emails/fr/register.mustache rename to backend/resources/emails/register/fr.mustache diff --git a/backend/src/uxbox/config.clj b/backend/src/uxbox/config.clj index 0b4661e15b..461f60aa97 100644 --- a/backend/src/uxbox/config.clj +++ b/backend/src/uxbox/config.clj @@ -31,8 +31,11 @@ :assets-directory "resources/public/static" :media-uri "http://localhost:6060/media/" :assets-uri "http://localhost:6060/static/" - :email-reply-to "no-reply@nodomain.com" - :email-from "no-reply@nodomain.com" + + :sendmail-backend "console" + :sendmail-reply-to "no-reply@example.com" + :sendmail-from "no-reply@example.com" + :smtp-enabled false :allow-demo-users true :registration-enabled true @@ -51,8 +54,10 @@ (s/def ::assets-directory ::us/string) (s/def ::media-uri ::us/string) (s/def ::media-directory ::us/string) -(s/def ::email-reply-to ::us/email) -(s/def ::email-from ::us/email) +(s/def ::sendmail-backend ::us/string) +(s/def ::sendmail-backend-apikey ::us/string) +(s/def ::sendmail-reply-to ::us/email) +(s/def ::sendmail-from ::us/email) (s/def ::smtp-host ::us/string) (s/def ::smtp-port ::us/integer) (s/def ::smtp-user (s/nilable ::us/string)) @@ -76,8 +81,10 @@ ::assets-uri ::media-directory ::media-uri - ::email-reply-to - ::email-from + ::sendmail-reply-to + ::sendmail-from + ::sendmail-backend + ::sendmail-backend-apikey ::smtp-host ::smtp-port ::smtp-user diff --git a/backend/src/uxbox/emails.clj b/backend/src/uxbox/emails.clj index 5efebc25c2..2efac2f7ed 100644 --- a/backend/src/uxbox/emails.clj +++ b/backend/src/uxbox/emails.clj @@ -34,16 +34,16 @@ (defn send! "Schedule the email for sending." ([email context] (send! db/pool email context)) - ([conn email context] - (us/verify fn? email) + ([conn email-factory context] + (us/verify fn? email-factory) (us/verify map? context) - (let [defaults {:from (:email-from cfg/config) - :reply-to (:email-reply-to cfg/config)} - data (->> (merge defaults context) - (email))] + (let [defaults {:from (:sendmail-from cfg/config) + :reply-to (:sendmail-reply-to cfg/config)} + data (merge defaults context) + email (email-factory data)] (tasks/schedule! conn {:name "sendmail" :delay 0 - :props data})))) + :props email})))) ;; --- Emails @@ -62,4 +62,3 @@ (def password-recovery "A password recovery notification email." (emails/build ::password-recovery default-context)) - diff --git a/backend/src/uxbox/tasks/impl.clj b/backend/src/uxbox/tasks/impl.clj index dc299d02a6..1f91f427ca 100644 --- a/backend/src/uxbox/tasks/impl.clj +++ b/backend/src/uxbox/tasks/impl.clj @@ -117,8 +117,7 @@ (p/then decode-task-row) (p/then (fn [item] (when item - (log/info "Execute task" (:name item) - "with props" (pr-str (:props item))) + (log/info "Execute task" (:name item)) (-> (p/do! (handle-task tasks item)) (p/handle (fn [v e] (if e diff --git a/backend/src/uxbox/tasks/sendmail.clj b/backend/src/uxbox/tasks/sendmail.clj index 2dfb173a07..0e15880587 100644 --- a/backend/src/uxbox/tasks/sendmail.clj +++ b/backend/src/uxbox/tasks/sendmail.clj @@ -5,33 +5,20 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020 Andrey Antukh +;; Copyright (c) 2020 UXBOX Labs SL (ns uxbox.tasks.sendmail - "Email sending jobs." (:require + [clojure.data.json :as json] [clojure.tools.logging :as log] - [cuerdas.core :as str] - [postal.core :as postal] - [vertx.util :as vu] [promesa.core :as p] - [uxbox.common.exceptions :as ex] [uxbox.config :as cfg] - [uxbox.core :refer [system]] - [uxbox.util.blob :as blob])) + [uxbox.util.http :as http])) -(defn- get-smtp-config - [config] - {:host (:smtp-host config) - :port (:smtp-port config) - :user (:smtp-user config) - :pass (:smtp-password config) - :ssl (:smtp-ssl config) - :tls (:smtp-tls config) - :enabled (:smtp-enabled config)}) +(defmulti sendmail (fn [config email] (:sendmail-backend config))) -(defn- send-email-to-console - [email] +(defmethod sendmail "console" + [config email] (let [out (with-out-str (println "email console dump:") (println "******** start email" (:id email) "**********") @@ -40,28 +27,41 @@ (println " reply-to: " (:reply-to email)) (println " subject: " (:subject email)) (println " content:") - (doseq [item (rest (:body email))] - (when (str/starts-with? (:type item) "text/plain") - (println (:content item)))) + (doseq [item (:content email)] + (when (= (:type item) "text/plain") + (println (:value item)))) (println "******** end email "(:id email) "**********"))] - (log/info out) - {:error :SUCCESS})) + (log/info out))) -(defn send-email - [email] - (vu/blocking - (let [config (get-smtp-config cfg/config) - result (if (:enabled config) - (postal/send-message config email) - (send-email-to-console email))] - (when (not= (:error result) :SUCCESS) - (ex/raise :type :sendmail-error - :code :email-not-sent - :context result)) - nil))) +(defmethod sendmail "sendgrid" + [config email] + (let [apikey (:sendmail-backend-apikey config) + dest (mapv #(array-map :email %) (:to email)) + params {:personalizations [{:to dest + :subject (:subject email)}] + :from {:email (:from email)} + :reply_to {:email (:reply-to email)} + :content (:content email)} + headers {"Authorization" (str "Bearer " apikey) + "Content-Type" "application/json"} + body (json/write-str params)] + (-> (http/send! {:method :post + :headers headers + :uri "https://api.sendgrid.com/v3/mail/send" + :body body}) + (p/handle (fn [response error] + (cond + error + (log/error "Error on sending email to sendgrid:" (pr-str error)) + + (= 202 (:status response)) + nil + + :else + (log/error "Unexpected status from sendgrid:" (pr-str response)))))))) (defn handler {:uxbox.tasks/name "sendmail"} [{:keys [props] :as task}] - (send-email props)) + (sendmail cfg/config props)) diff --git a/backend/src/uxbox/util/emails.clj b/backend/src/uxbox/util/emails.clj index ae706039d9..b7d4897269 100644 --- a/backend/src/uxbox/util/emails.clj +++ b/backend/src/uxbox/util/emails.clj @@ -30,7 +30,7 @@ "eol = ('\\n' | '\\r\\n'); ")) (def ^:private parse-fn (insta/parser grammar)) -(def ^:private email-path "emails/%(lang)s/%(id)s.mustache") +(def ^:private email-path "emails/%(id)s/%(lang)s.mustache") (defn- parse-template [content] @@ -49,7 +49,8 @@ (s/def ::body-html string?) (s/def ::parsed-email - (s/keys :req-un [::subject ::body-html ::body-html])) + (s/keys :req-un [::subject ::body-text] + :opt-un [::body-html])) (defn- build-base-email [data context] @@ -59,11 +60,11 @@ :hint "Seems like the email template has invalid data." :contex data)) {:subject (:subject data) - :body [:alternative - {:type "text/plain; charset=utf-8" - :content (:body-text data)} - {:type "text/html; charset=utf-8" - :content (:body-html data)}]}) + :content (cond-> [] + (:body-text data) (conj {:type "text/plain" + :value (:body-text data)}) + (:body-html data) (conj {:type "text/html" + :value (:body-html data)}))}) (defn- impl-build-email [id context] @@ -102,6 +103,6 @@ :hint "seems like the template is wrong or does not exists." ::id id)) (cond-> (assoc email :id (name id)) - (:to context) (assoc :to (:to context)) + (:to context) (assoc :to [(:to context)]) (:from context) (assoc :from (:from context)) (:reply-to context) (assoc :reply-to (:reply-to context))))))) diff --git a/backend/src/uxbox/util/http.clj b/backend/src/uxbox/util/http.clj index 4cbc1fb78b..f5b8edfa63 100644 --- a/backend/src/uxbox/util/http.clj +++ b/backend/src/uxbox/util/http.clj @@ -5,4 +5,15 @@ ;; Copyright (c) 2019 Andrey Antukh (ns uxbox.util.http - "Http related helpers.") + "Http client abstraction layer." + (:require + [promesa.core :as p] + [promesa.exec :as px] + [java-http-clj.core :as http])) + +(def default-client + (delay (http/build-client {:executor @px/default-executor}))) + +(defn send! + [req] + (http/send-async req {:client @default-client :as :string})) From 428bd42f15b93b5f891dbdfe5f72d4a0bd98b42f Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 20 Apr 2020 14:08:33 +0200 Subject: [PATCH 4/4] :recycle: Add smtp backend for sendmail task. --- backend/src/uxbox/config.clj | 3 -- backend/src/uxbox/tasks/sendmail.clj | 54 +++++++++++++++++++++++----- docker/devenv/docker-compose.yaml | 3 ++ 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/backend/src/uxbox/config.clj b/backend/src/uxbox/config.clj index 461f60aa97..2d8f135499 100644 --- a/backend/src/uxbox/config.clj +++ b/backend/src/uxbox/config.clj @@ -36,7 +36,6 @@ :sendmail-reply-to "no-reply@example.com" :sendmail-from "no-reply@example.com" - :smtp-enabled false :allow-demo-users true :registration-enabled true :registration-domain-whitelist "" @@ -64,7 +63,6 @@ (s/def ::smtp-password (s/nilable ::us/string)) (s/def ::smtp-tls ::us/boolean) (s/def ::smtp-ssl ::us/boolean) -(s/def ::smtp-enabled ::us/boolean) (s/def ::allow-demo-users ::us/boolean) (s/def ::registration-enabled ::us/boolean) (s/def ::registration-domain-whitelist ::us/string) @@ -91,7 +89,6 @@ ::smtp-password ::smtp-tls ::smtp-ssl - ::smtp-enabled ::debug-humanize-transit ::allow-demo-users ::registration-enabled])) diff --git a/backend/src/uxbox/tasks/sendmail.clj b/backend/src/uxbox/tasks/sendmail.clj index 0e15880587..95cb3f4375 100644 --- a/backend/src/uxbox/tasks/sendmail.clj +++ b/backend/src/uxbox/tasks/sendmail.clj @@ -11,9 +11,13 @@ (:require [clojure.data.json :as json] [clojure.tools.logging :as log] + [postal.core :as postal] [promesa.core :as p] + [uxbox.common.data :as d] + [uxbox.common.exceptions :as ex] [uxbox.config :as cfg] - [uxbox.util.http :as http])) + [uxbox.util.http :as http] + [vertx.util :as vu])) (defmulti sendmail (fn [config email] (:sendmail-backend config))) @@ -49,16 +53,48 @@ :headers headers :uri "https://api.sendgrid.com/v3/mail/send" :body body}) - (p/handle (fn [response error] - (cond - error - (log/error "Error on sending email to sendgrid:" (pr-str error)) + (p/handle + (fn [response error] + (cond + error + (log/error "Error on sending email to sendgrid:" (pr-str error)) - (= 202 (:status response)) - nil + (= 202 (:status response)) + nil - :else - (log/error "Unexpected status from sendgrid:" (pr-str response)))))))) + :else + (log/error "Unexpected status from sendgrid:" (pr-str response)))))))) + +(defn- get-smtp-config + [config] + {:host (:smtp-host config) + :port (:smtp-port config) + :user (:smtp-user config) + :pass (:smtp-password config) + :ssl (:smtp-ssl config) + :tls (:smtp-tls config)}) + +(defn- email->postal + [email] + {:from (:from email) + :to (:to email) + :subject (:subject email) + :body (d/concat [:alternative] + (map (fn [{:keys [type value]}] + {:type (str type "; charset=utf-8") + :content value}) + (:content email)))}) + +(defmethod sendmail "smtp" + [config email] + (vu/blocking + (let [config (get-smtp-config config) + email (email->postal email) + result (postal/send-message config email)] + (when (not= (:error result) :SUCCESS) + (ex/raise :type :sendmail-error + :code :email-not-sent + :context result))))) (defn handler {:uxbox.tasks/name "sendmail"} diff --git a/docker/devenv/docker-compose.yaml b/docker/devenv/docker-compose.yaml index aa36571d3f..e773d4bc48 100644 --- a/docker/devenv/docker-compose.yaml +++ b/docker/devenv/docker-compose.yaml @@ -42,6 +42,9 @@ services: - UXBOX_DATABASE_URI=postgresql://postgres/uxbox - UXBOX_DATABASE_USERNAME=uxbox - UXBOX_DATABASE_PASSWORD=uxbox + - UXBOX_SENDMAIL_BACKEND=smtp + - UXBOX_SMTP_HOST=smtp + - UXBOX_SMTP_PORT=25 smtp: container_name: "uxbox-devenv-smtp"