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