Last active
December 14, 2015 03:38
-
-
Save jeremyheiler/5022062 to your computer and use it in GitHub Desktop.
Possible ways to script SMTP with Clojure.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; A data-oriented approach. | |
;; Basic SMTP. Falls back to HELO if Extended SMTP isn't supported. | |
(def script | |
[[:CONNECT ["server.example.com" 587]] | |
[:EHLO ["client.example.com"] :on-reply {502 [:HELO ["client.example.com"]]}] | |
[:MAIL ["foo@example.com"]] | |
[:RCPT ["bar@example.com"]] | |
[:RCPT ["baz@example.com"]] | |
[:DATA] | |
[:MESSAGE ["From: foo@example.com\r\nTo: bar@example.com, baz@example.com\r\nSubject: Test\r\n\r\nThis is a test!"]] | |
[:QUIT]]) | |
;; Extended SMTP. Uses STARTTLS and AUTH extensions. (Good enough for Gmail.) | |
(def script-with-ext | |
[[:CONNECT ["server.example.com" 587]] | |
[:EHLO ["client.example.com"]] | |
[:STARTTLS] | |
[:EHLO ["client.example.com"]] | |
[:AUTH ["PLAIN" "dGVzdAB0ZXN0ADEyMzQ="]] | |
[:MAIL ["foo@example.com"]] | |
[:RCPT ["bar@example.com"]] | |
[:RCPT ["baz@example.com"]] | |
[:DATA] | |
[:MESSAGE ["From: foo@example.com\r\nTo: bar@example.com, baz@example.com\r\nSubject: Test\r\n\r\nThis is a test!"]] | |
[:QUIT]]) | |
;; Note: The :MESSAGE key is not an SMTP command. It adds "\r\n.\r\n"to the end to complete the message. | |
;; The :CONNECT key is not an SMTP command. It esatblishs the connection and should handle a reply. | |
;; Another potential special command is :FREE for free-form writes. | |
;; A more "DSL-ish" approach. | |
;; Basic SMTP. Each function interacts with the server and handles a reply. | |
(script | |
(connect "server.example.com" 587) | |
(command :EHLO ["client.example.com"]) | |
(command :MAIL ["foo@example.com"]) | |
(command :RCPT ["bar@example.com"]) | |
(command :RCPT ["baz@example.com"]) | |
(command :DATA) | |
(message (format-message {:from "foo@example.com" :to ["bar@example.com" "baz@example.com"] :subject "Test" :body "This is a test!"})) | |
(command :QUIT)) | |
;; The advantage here is that there are no pseudo commands. | |
;; Also, since it's not data, it's more difficult to manipulate. | |
;; However, that may not be as important for scripting SMTP. | |
;; The format-message function is not part of the DSL, but an ordinary function. | |
;; In fact, the message map at this point should look like one of the following: | |
(def message-map-a | |
{:headers ["From: foo@example.com" | |
"To: bar@example.com, baz@example.com" | |
"Subject: Test"] | |
:body "This is a test!"}) | |
(def message-map-b | |
{:headers [{:name "From" :body "foo@example.com"} | |
{:name "To" :body "bar@example.com, baz@example.com"} | |
{:name "Subject" :body "Test"}] | |
:body "This is a test!"}) | |
;; The interesting points are: | |
;; - The headers remain ordered. | |
;; - The headers are easy to parse by type/name. | |
;; - The headers are easy to wrap when formatting the message. | |
;; - That the message map can be nested for more complex message bodies. | |
;; The advantage of having a map represent a header is that additional meta data could be provided. |
Looks very interesting. I particularly like the use of a DSL to make the SMTP conversation easier to follow.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Lime: https://github.com/jeremyheiler/lime