From 4b38e84ccb61b981eba4dd370f2b40212d86feaf Mon Sep 17 00:00:00 2001 From: Cam Saul <cam@getluckybird.com> Date: Thu, 30 Apr 2015 12:58:03 -0700 Subject: [PATCH] Use SMTP for sending email --- project.clj | 1 + src/metabase/email.clj | 108 +++++++++++++------------------- src/metabase/email/messages.clj | 18 +++--- 3 files changed, 53 insertions(+), 74 deletions(-) diff --git a/project.clj b/project.clj index d9823f29fb0..77a8594460f 100644 --- a/project.clj +++ b/project.clj @@ -26,6 +26,7 @@ [clj-time "0.9.0"] ; library for dealing with date/time [colorize "0.1.1" :exclusions [org.clojure/clojure]] ; string output with ANSI color codes (for logging) [com.cemerick/friend "0.2.1"] ; auth library + [com.draines/postal "1.11.3"] ; SMTP library [com.h2database/h2 "1.4.186"] ; embedded SQL database [com.mattbertolini/liquibase-slf4j "1.2.1"] [com.novemberain/monger "2.1.0"] ; MongoDB Driver diff --git a/src/metabase/email.clj b/src/metabase/email.clj index fdbc6bb1f87..6ae4a81db71 100644 --- a/src/metabase/email.clj +++ b/src/metabase/email.clj @@ -1,77 +1,57 @@ (ns metabase.email - (:require [clojure.data.json :as json] - [clojure.tools.logging :as log] - [clj-http.lite.client :as client] - [medley.core :as medley] + (:require [clojure.tools.logging :as log] + [postal.core :as postal] [metabase.models.setting :refer [defsetting]] [metabase.util :as u])) -(declare api-post-messages-send - format-recipients) - ;; ## CONFIG -(defsetting mandrill-api-key "API key for Mandrill.") (defsetting email-from-address "Email address used as the sender of system notifications." "notifications@metabase.com") -(defsetting email-from-name "Name used as the sender of the system notifications." "Metabase") - +(defsetting email-smtp-host "SMTP host." "smtp.mandrillapp.com") +(defsetting email-smtp-username "SMTP username.") +(defsetting email-smtp-password "SMTP password.") +(defsetting email-smtp-port "SMTP port." "587") ;; ## PUBLIC INTERFACE -(defn send-message [subject recipients message-type message & {:as kwargs}] +;; TODO - wouldn't this be *nicer* if all args were kwargs? +(defn send-message + "Send an email to one or more RECIPIENTS. + RECIPIENTS is a sequence of email addresses; MESSAGE-TYPE must be either `:text` or `:html`. + + (email/send-message + :subject \"[Metabase] Password Reset Request\" + :recipients [\"cam@metabase.com\"] + :message-type :text + :message \"How are you today?\") + + Upon success, this returns the MESSAGE that was just sent." + [& {:keys [subject recipients message-type message]}] {:pre [(string? subject) - (map? recipients) + (sequential? recipients) + (every? u/is-email? recipients) (contains? #{:text :html} message-type) (string? message)]} - (medley/mapply api-post-messages-send (merge {:subject subject - :to (format-recipients recipients) - message-type message} - kwargs))) - -;; ## IMPLEMENTATION - -(def ^:private api-prefix - "URL prefix for API calls to the Mandrill API." - "https://mandrillapp.com/api/1.0/") - -(defn- api-post - "Make a `POST` call to the Mandrill API. - - (api-post \"messages/send\" :body { ... })" - [endpoint & {:keys [body] :as request-map - :or {body {}}}] - {:pre [(string? endpoint)]} - (if-not (mandrill-api-key) - (log/warn "Cannot send email: no Mandrill API key!") - (let [defaults {:content-type :json - :accept :json} - body (-> body - (assoc :key (mandrill-api-key)) - json/write-str)] - (client/post (str api-prefix endpoint ".json") - (merge defaults request-map {:body body}))))) - -(defn- api-post-messages-send - "Make a `POST messages/send` call to the Mandrill API." - [& {:as kwargs}] - (let [defaults {:from_email (email-from-address) - :from_name (email-from-name)}] - (= (:status (api-post "messages/send" - :body {:message (merge defaults kwargs)})) - 200))) - -(defn- format-recipients - "Format a map of email -> name in the format expected by the Mandrill API. - - (format-recipients {\"cam@metabase.com\" \"Cam Saul\"}) - -> {:email \"cam@metabase.com\" - :name \"Cam Saul\" - :type :to}" - [email->name] - (map (fn [[email name]] - {:pre [(u/is-email? email) - (string? name)]} - {:email email - :name name - :type :to}) - email->name)) + (try + ;; Check to make sure all valid settings are set! + (when-not (email-smtp-username) + (throw (Exception. "SMTP username is not set."))) + (when-not (email-smtp-password) + (throw (Exception. "SMTP password is not set."))) + ;; Now send the email + (let [{error :error error-message :message} (postal/send-message {:host (email-smtp-host) + :user (email-smtp-username) + :pass (email-smtp-password) + :port (Integer/parseInt (email-smtp-port))} + {:from (email-from-address) + :to recipients + :subject subject + :body (condp = message-type + :text message + :html [{:type "text/html; charset=utf-8" + :content message}])})] + (when-not (= error :SUCCESS) + (throw (Exception. (format "Emails failed to send: error: %s; message: %s" error error-message)))) + message) + (catch Throwable e + (log/warn "Failed to send email: " (.getMessage e))))) diff --git a/src/metabase/email/messages.clj b/src/metabase/email/messages.clj index 1f7993854d8..8032475f720 100644 --- a/src/metabase/email/messages.clj +++ b/src/metabase/email/messages.clj @@ -21,11 +21,10 @@ [:p "Your account is setup and ready to go, you just need to set a password so you can login. Follow the link below to reset your account password."] [:p [:a {:href password-reset-url} password-reset-url]]]])] (email/send-message - "Your new Metabase account is all set up" - {email email} - :html message-body) - ;; return the message body we sent - message-body)) + :subject "Your new Metabase account is all set up" + :recipients [email] + :message-type :html + :message message-body))) (defn send-password-reset-email "Format and Send an email informing the user how to reset their password." @@ -40,8 +39,7 @@ "It can be safely ignored if you did not request a password reset. Click the link below to reset your password.")] [:p [:a {:href password-reset-url} password-reset-url]]]])] (email/send-message - "[Metabase] Password Reset Request" - {email email} - :html message-body) - ;; return the message body we sent - message-body)) + :subject "[Metabase] Password Reset Request" + :recipients [email] + :message-type :html + :message message-body))) -- GitLab