diff --git a/README.md b/README.md index 433042c..9812297 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,24 @@ 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`. 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" + :cc "another@another.dom" + :recipients ["member1@foo.dom" "member2@another.dom"] + :subject "An announcement to all members!" + :body "Regards."} + +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 @@ -229,6 +247,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..2a11067 100644 --- a/src/postal/message.clj +++ b/src/postal/message.clj @@ -34,11 +34,7 @@ (def default-charset "utf-8") -(declare make-jmessage) - -(defn recipients [msg] - (let [^javax.mail.Message jmsg (make-jmessage msg)] - (map str (.getAllRecipients jmsg)))) +(declare make-jmessage-with-recipients) (defn sender [msg] (or (:sender msg) (:from msg))) @@ -64,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)))) @@ -143,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) @@ -152,11 +148,11 @@ (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 - :user-agent] + :user-agent :recipients] charset (or (:charset msg) default-charset) jmsg (proxy [MimeMessage] [session] (updateMessageID [] @@ -177,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 5ecc64d..8ad5caa 100644 --- a/src/postal/smtp.clj +++ b/src/postal/smtp.clj @@ -22,25 +22,36 @@ ;; OTHER DEALINGS IN THE SOFTWARE. (ns postal.smtp - (:use [postal.message :only [make-jmessage]] + (:use [postal.message :only [make-jmessage-with-recipients 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-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 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] (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"}))) + (smtp-connect* transport host port user pass) + (doseq [msg msgs] + (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 64784c5..1b765bc 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 (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 (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 "))) + (is (not (.contains r "Quux "))))) \ No newline at end of file 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]])) 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"]))