From 0c31eb1824edf48e41d2c8f796c3138fc67910d0 Mon Sep 17 00:00:00 2001 From: oysteinjakobsen Date: Sat, 4 Oct 2014 22:49:12 +0200 Subject: [PATCH 1/4] Introduced :recipients that will override :to, :cc, and :bcc - useful for mail forwarding. --- README.md | 14 ++++++++++++++ src/postal/message.clj | 8 +++++--- src/postal/smtp.clj | 20 ++++++++++++++------ test/postal/test/message.clj | 26 ++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 433042c..55da493 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,19 @@ its randomness and only customize the hostname. :subject "Message IDs!" :body "Regards." :message-id #(postal.support/message-id "foo.bar.dom")} + +#### Mail forwarding + +If you're forwarding a mail it is possible to specify which recipients +will actually receive the mail regardless of what is given in the header: +Supply `:recipients` to override `:to`, `:cc`, and `:bcc`. + + postal.core> (send-message {:from "foo@bar.dom" + :to "mailinglist@bar.dom" + :cc "another@another.dom" + :recipients ["member1@foo.dom" "member2@another.dom"] + :subject "An announcement to all members!" + :body "Regards."} #### User Agent @@ -229,6 +242,7 @@ Paul Stadig Phil Hagelberg Roman Flammer Sam Ritchie +Øystein Jakobsen ## License diff --git a/src/postal/message.clj b/src/postal/message.clj index 929757f..6a3a00b 100644 --- a/src/postal/message.clj +++ b/src/postal/message.clj @@ -37,8 +37,10 @@ (declare make-jmessage) (defn recipients [msg] - (let [^javax.mail.Message jmsg (make-jmessage msg)] - (map str (.getAllRecipients jmsg)))) + (map str + (if-let [recipients (:recipients msg)] + recipients + (.getAllRecipients (make-jmessage msg))))) (defn sender [msg] (or (:sender msg) (:from msg))) @@ -156,7 +158,7 @@ ([msg session] (let [standard [:from :reply-to :to :cc :bcc :date :subject :body :message-id - :user-agent] + :user-agent :recipients] charset (or (:charset msg) default-charset) jmsg (proxy [MimeMessage] [session] (updateMessageID [] diff --git a/src/postal/smtp.clj b/src/postal/smtp.clj index 5ecc64d..f4b7f32 100644 --- a/src/postal/smtp.clj +++ b/src/postal/smtp.clj @@ -22,19 +22,27 @@ ;; OTHER DEALINGS IN THE SOFTWARE. (ns postal.smtp - (:use [postal.message :only [make-jmessage]] + (:use [postal.message :only [make-jmessage make-addresses]] [postal.support :only [make-props]]) - (:import [javax.mail Transport Session])) + (:import [javax.mail Transport Session] + [javax.mail.internet InternetAddress MimeMessage])) + +(defn ^:dynamic smtp-send-single* + [^Transport transport ^MimeMessage msg recipients] + (.sendMessage transport msg recipients)) (defn ^:dynamic smtp-send* [^Session session ^String proto {:keys [host port user pass]} msgs] (assert (or (and (nil? user) (nil? pass)) (and user pass))) (with-open [transport (.getTransport session proto)] (.connect transport host port user pass) - (let [jmsgs (map #(make-jmessage % session) msgs)] - (doseq [^javax.mail.Message jmsg jmsgs] - (.sendMessage transport jmsg (.getAllRecipients jmsg))) - {:code 0 :error :SUCCESS :message "messages sent"}))) + (doseq [msg msgs] + (let [jmsg (make-jmessage msg session) + recipients (if (empty? (:recipients msg)) + (.getAllRecipients jmsg) + (make-addresses (:recipients msg) (:charset msg)))] + (smtp-send-single* transport jmsg recipients)))) + {:code 0 :error :SUCCESS :message "messages sent"}) (defn smtp-send ([msg] diff --git a/test/postal/test/message.clj b/test/postal/test/message.clj index 64784c5..44a478e 100644 --- a/test/postal/test/message.clj +++ b/test/postal/test/message.clj @@ -225,3 +225,29 @@ :body "Where is that message ID!" :user-agent "foo/1.0"})] (is (.contains m "User-Agent: foo")))) + +(deftest test-recipients + (let [r (recipients + {:from "fee@bar.dom" + :to "Foo Bar " + :cc ["baz@bar.dom" "Quux "] + :subject "Test" + :body "Test!" + :charset "us-ascii"})] + (is (.contains r "Foo Bar ")) + (is (.contains r "baz@bar.dom")) + (is (.contains r "Quux ")))) + +(deftest test-recipients-with-recipients-given + (let [r (recipients + {:from "fee@bar.dom" + :to "Foo Bar " + :cc ["baz@bar.dom" "Quux "] + :recipients ["recip1@r.dom" "Recip Two "] + :subject "Test" + :body "Test!" + :charset "us-ascii"})] + (is (.contains r "recip1@r.dom")) + (is (.contains r "Recip Two ")) + (is (not (.contains r "Foo Bar "))) + (is (not (.contains r "Quux "))))) \ No newline at end of file From c5d152c6fd9e09611904851d57c1eb410ddfb933 Mon Sep 17 00:00:00 2001 From: oysteinjakobsen Date: Sat, 4 Oct 2014 23:51:15 +0200 Subject: [PATCH 2/4] Added tests for SMTP transport with :recipients given. --- README.md | 3 +++ src/postal/smtp.clj | 6 +++++- test/postal/test/smtp.clj | 26 ++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 55da493..be2d190 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,9 @@ Supply `:recipients` to override `:to`, `:cc`, and `:bcc`. :recipients ["member1@foo.dom" "member2@another.dom"] :subject "An announcement to all members!" :body "Regards."} + +Note that some SMTP services - like Postmark - don't accept this and will silently +rewrite the message headers to match the given recipients. #### User Agent diff --git a/src/postal/smtp.clj b/src/postal/smtp.clj index f4b7f32..ce86f1d 100644 --- a/src/postal/smtp.clj +++ b/src/postal/smtp.clj @@ -27,6 +27,10 @@ (:import [javax.mail Transport Session] [javax.mail.internet InternetAddress MimeMessage])) +(defn ^:dynamic smtp-connect* + [^Transport transport ^String host ^String port ^String user ^String pass] + (.connect transport host port user pass)) + (defn ^:dynamic smtp-send-single* [^Transport transport ^MimeMessage msg recipients] (.sendMessage transport msg recipients)) @@ -35,7 +39,7 @@ {:keys [host port user pass]} msgs] (assert (or (and (nil? user) (nil? pass)) (and user pass))) (with-open [transport (.getTransport session proto)] - (.connect transport host port user pass) + (smtp-connect* transport host port user pass) (doseq [msg msgs] (let [jmsg (make-jmessage msg session) recipients (if (empty? (:recipients msg)) diff --git a/test/postal/test/smtp.clj b/test/postal/test/smtp.clj index 33e8d2e..9cf70e5 100644 --- a/test/postal/test/smtp.clj +++ b/test/postal/test/smtp.clj @@ -66,3 +66,29 @@ {"mail.smtp.port" 25 "mail.smtp.auth" "false" "mail.smtp.host" "smtp.bar.dom"})) + +(defn recipients [msg] + (let [capture (atom [])] + (binding [smtp/smtp-connect* (fn [& _]) + smtp/smtp-send-single* (fn [transport msg recipients] + (reset! capture (mapv #(.getAddress %) recipients)))] + (smtp/smtp-send {} msg) + capture))) + +(defmacro is-recipients [input want] + `(is (= (deref (recipients ~input)) ~want))) + +(deftest t-recipients + (is-recipients {:from "foo@bar.dom" + :to "baz@bar.dom" + :cc ["foo@bar.dom"] + :subject "Test" + :body "Hello."} + ["baz@bar.dom" "foo@bar.dom"]) + (is-recipients {:from "foo@bar.dom" + :to "baz@bar.dom" + :cc ["foo@bar.dom"] + :recipients ["another@another.dom"] + :subject "Test" + :body "Hello."} + ["another@another.dom"])) From 8a3bbbba0ec28ea8ed97dbdd3adb2f5ce80094e9 Mon Sep 17 00:00:00 2001 From: oysteinjakobsen Date: Sun, 5 Oct 2014 13:10:23 +0200 Subject: [PATCH 3/4] Changed make-jmessage into make-jmessage-with-recipients to optimize code. --- src/postal/message.clj | 20 +++++++++----------- src/postal/sendmail.clj | 7 ++++--- src/postal/smtp.clj | 17 ++++++++--------- test/postal/test/message.clj | 30 +++++++++++++++--------------- test/postal/test/sendmail.clj | 2 +- 5 files changed, 37 insertions(+), 39 deletions(-) diff --git a/src/postal/message.clj b/src/postal/message.clj index 6a3a00b..2a11067 100644 --- a/src/postal/message.clj +++ b/src/postal/message.clj @@ -34,13 +34,7 @@ (def default-charset "utf-8") -(declare make-jmessage) - -(defn recipients [msg] - (map str - (if-let [recipients (:recipients msg)] - recipients - (.getAllRecipients (make-jmessage msg))))) +(declare make-jmessage-with-recipients) (defn sender [msg] (or (:sender msg) (:from msg))) @@ -66,7 +60,7 @@ (defn message->str [msg] (with-open [out (java.io.ByteArrayOutputStream.)] (let [^javax.mail.Message jmsg (if (instance? MimeMessage msg) - msg (make-jmessage msg))] + msg (:jmsg (make-jmessage-with-recipients msg)))] (.writeTo jmsg out) (str out)))) @@ -145,7 +139,7 @@ (proxy [javax.mail.Authenticator] [] (getPasswordAuthentication [] (PasswordAuthentication. user pass)))) -(defn make-jmessage +(defn make-jmessage-with-recipients ([msg] (let [{:keys [sender from]} msg {:keys [user pass]} (meta msg) @@ -154,7 +148,7 @@ (if user (Session/getInstance props (make-auth user pass)) (Session/getInstance props)))] - (make-jmessage msg session))) + (make-jmessage-with-recipients msg session))) ([msg session] (let [standard [:from :reply-to :to :cc :bcc :date :subject :body :message-id @@ -179,7 +173,11 @@ (.addHeader "User-Agent" (:user-agent msg (user-agent))) (add-extra! (apply dissoc msg standard)) (add-body! (:body msg) charset) - (.saveChanges))))) + (.saveChanges)) + (let [recipients (if (empty? (:recipients msg)) + (.getAllRecipients jmsg) + (make-addresses (:recipients msg) charset))] + {:jmsg jmsg :recipients recipients})))) (defn make-fixture [from to & {:keys [tag]}] (let [uuid (str (UUID/randomUUID)) diff --git a/src/postal/sendmail.clj b/src/postal/sendmail.clj index f4cdcf1..66e148b 100644 --- a/src/postal/sendmail.clj +++ b/src/postal/sendmail.clj @@ -22,7 +22,7 @@ ;; OTHER DEALINGS IN THE SOFTWARE. (ns postal.sendmail - (:use [postal.message :only [message->str sender recipients]])) + (:use [postal.message :only [message->str sender make-jmessage-with-recipients]])) (def sendmails ["/usr/lib/sendmail" "/usr/sbin/sendmail" @@ -64,10 +64,11 @@ (.replaceAll text "\r\n" (System/getProperty "line.separator"))) (defn sendmail-send [msg] - (let [mail (sanitize (message->str msg)) + (let [{:keys [jmsg recipients]} (make-jmessage-with-recipients msg) + mail (sanitize (message->str jmsg)) cmd (concat [(sendmail-find) (format "-f %s" (sender msg))] - (recipients msg)) + (map str recipients)) pb (ProcessBuilder. cmd) p (.start pb) smtp (java.io.PrintStream. (.getOutputStream p))] diff --git a/src/postal/smtp.clj b/src/postal/smtp.clj index ce86f1d..8ad5caa 100644 --- a/src/postal/smtp.clj +++ b/src/postal/smtp.clj @@ -22,7 +22,7 @@ ;; OTHER DEALINGS IN THE SOFTWARE. (ns postal.smtp - (:use [postal.message :only [make-jmessage make-addresses]] + (:use [postal.message :only [make-jmessage-with-recipients make-addresses]] [postal.support :only [make-props]]) (:import [javax.mail Transport Session] [javax.mail.internet InternetAddress MimeMessage])) @@ -32,8 +32,10 @@ (.connect transport host port user pass)) (defn ^:dynamic smtp-send-single* - [^Transport transport ^MimeMessage msg recipients] - (.sendMessage transport msg recipients)) + ([^Transport transport ^MimeMessage jmsg recipients] + (.sendMessage transport jmsg recipients)) + ([^MimeMessage jmsg recipients] + (Transport/send jmsg recipients))) (defn ^:dynamic smtp-send* [^Session session ^String proto {:keys [host port user pass]} msgs] @@ -41,18 +43,15 @@ (with-open [transport (.getTransport session proto)] (smtp-connect* transport host port user pass) (doseq [msg msgs] - (let [jmsg (make-jmessage msg session) - recipients (if (empty? (:recipients msg)) - (.getAllRecipients jmsg) - (make-addresses (:recipients msg) (:charset msg)))] + (let [{:keys [jmsg recipients]} (make-jmessage-with-recipients msg session)] (smtp-send-single* transport jmsg recipients)))) {:code 0 :error :SUCCESS :message "messages sent"}) (defn smtp-send ([msg] - (let [jmsg (make-jmessage msg)] + (let [{:keys [jmsg recipients]} (make-jmessage-with-recipients msg)] (try - (Transport/send jmsg) + (smtp-send-single* jmsg recipients) {:code 0 :error :SUCCESS :message "message sent"} (catch Exception e {:code 99 :error (class e) :message (.getMessage e)})))) diff --git a/test/postal/test/message.clj b/test/postal/test/message.clj index 44a478e..1b765bc 100644 --- a/test/postal/test/message.clj +++ b/test/postal/test/message.clj @@ -227,26 +227,26 @@ (is (.contains m "User-Agent: foo")))) (deftest test-recipients - (let [r (recipients - {:from "fee@bar.dom" - :to "Foo Bar " - :cc ["baz@bar.dom" "Quux "] - :subject "Test" - :body "Test!" - :charset "us-ascii"})] + (let [r (map str (:recipients (make-jmessage-with-recipients + {:from "fee@bar.dom" + :to "Foo Bar " + :cc ["baz@bar.dom" "Quux "] + :subject "Test" + :body "Test!" + :charset "us-ascii"})))] (is (.contains r "Foo Bar ")) (is (.contains r "baz@bar.dom")) (is (.contains r "Quux ")))) (deftest test-recipients-with-recipients-given - (let [r (recipients - {:from "fee@bar.dom" - :to "Foo Bar " - :cc ["baz@bar.dom" "Quux "] - :recipients ["recip1@r.dom" "Recip Two "] - :subject "Test" - :body "Test!" - :charset "us-ascii"})] + (let [r (map str (:recipients (make-jmessage-with-recipients + {:from "fee@bar.dom" + :to "Foo Bar " + :cc ["baz@bar.dom" "Quux "] + :recipients ["recip1@r.dom" "Recip Two "] + :subject "Test" + :body "Test!" + :charset "us-ascii"})))] (is (.contains r "recip1@r.dom")) (is (.contains r "Recip Two ")) (is (not (.contains r "Foo Bar "))) diff --git a/test/postal/test/sendmail.clj b/test/postal/test/sendmail.clj index 4a13517..e14d307 100644 --- a/test/postal/test/sendmail.clj +++ b/test/postal/test/sendmail.clj @@ -22,4 +22,4 @@ ;; OTHER DEALINGS IN THE SOFTWARE. (ns postal.test.sendmail - (:use [postal.message :only [message->str sender recipients]])) + (:use [postal.message :only [message->str sender]])) From 965adfa0ac5d17d24436ad4db3ee0e46331c52db Mon Sep 17 00:00:00 2001 From: oysteinjakobsen Date: Sun, 5 Oct 2014 13:45:43 +0200 Subject: [PATCH 4/4] Updated README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index be2d190..9812297 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,9 @@ its randomness and only customize the hostname. If you're forwarding a mail it is possible to specify which recipients will actually receive the mail regardless of what is given in the header: -Supply `:recipients` to override `:to`, `:cc`, and `:bcc`. +Supply `:recipients` to override `:to`, `:cc`, and `:bcc`. The mail will +only be sent to those given in `:recipients` but to the recipients the mail +will appear to have been sent to those given in `:to` and `:cc`. postal.core> (send-message {:from "foo@bar.dom" :to "mailinglist@bar.dom" @@ -193,8 +195,8 @@ Supply `:recipients` to override `:to`, `:cc`, and `:bcc`. :subject "An announcement to all members!" :body "Regards."} -Note that some SMTP services - like Postmark - don't accept this and will silently -rewrite the message headers to match the given recipients. +Note that some SMTP service providers - like Postmark - don't accept this and will +silently rewrite the message headers to match the given recipients. #### User Agent