Email for an application: build a mailer from configuration once, then send! a
message with it.
(def mailer (mail/mailer {:transport :console :from "noreply@example.com"}))
(mail/send! mailer {:to "ada@example.com" :subject "Hello" :text "…"})
Two transports, chosen by :transport:
:console prints the whole message — headers and body — to *out* and sends
nothing. It is for development, where a sign-in link should be readable in the
terminal: in production it would put credentials in the logs, which the host
refuses (this library cannot know what production is).:smtp delivers through a submission server with this library's own client
(dev.arkaitz.sendmail-base.smtp, SPEC §5): TLS always, a required wall-clock
deadline on every send, and refusals that never echo a recipient, a server's text
or a password.A mailer is a value. It holds no connection and no thread: every send! connects,
delivers one message and closes, inside its deadline.
Email for an application: build a mailer from configuration once, then `send!` a
message with it.
(def mailer (mail/mailer {:transport :console :from "noreply@example.com"}))
(mail/send! mailer {:to "ada@example.com" :subject "Hello" :text "…"})
Two transports, chosen by `:transport`:
- `:console` prints the whole message — headers and body — to `*out*` and sends
nothing. It is for development, where a sign-in link should be readable in the
terminal: in production it would put credentials in the logs, which the host
refuses (this library cannot know what production is).
- `:smtp` delivers through a submission server with this library's own client
(`dev.arkaitz.sendmail-base.smtp`, SPEC §5): TLS always, a required wall-clock
deadline on every send, and refusals that never echo a recipient, a server's text
or a password.
A mailer is a value. It holds no connection and no thread: every `send!` connects,
delivers one message and closes, inside its deadline.Optional: requiring this namespace installs one Integrant key,
:dev.arkaitz.sendmail-base/mailer, whose value is (mailer config). There is no
halt-key!: a mailer holds no connection and no thread, so there is nothing to
close.
{:dev.arkaitz.sendmail-base/mailer {:transport :smtp :host "smtp.example.com"
:port 587 :tls :starttls
:user "app" :password #wb/env "SMTP_PASSWORD"
:from "noreply@example.com" :deadline-ms 10000}}
A refusal names its key and never the value; but Integrant's own failure carries the whole resolved configuration of the key that threw, password included, so a host logs the cause and never the exception Integrant threw.
Optional: requiring this namespace installs one Integrant key,
`:dev.arkaitz.sendmail-base/mailer`, whose value is `(mailer config)`. There is no
`halt-key!`: a mailer holds no connection and no thread, so there is nothing to
close.
{:dev.arkaitz.sendmail-base/mailer {:transport :smtp :host "smtp.example.com"
:port 587 :tls :starttls
:user "app" :password #wb/env "SMTP_PASSWORD"
:from "noreply@example.com" :deadline-ms 10000}}
A refusal names its key and never the value; but Integrant's own failure carries the
whole resolved configuration of the key that threw, password included, so a host
logs the cause and never the exception Integrant threw.No vars found in this namespace.
The SMTP submission client: one message per connection, over TLS, inside one wall-clock deadline (SPEC §5).
The deadline is a watchdog, not a timeout per call. A virtual thread closes the socket when the deadline passes, which makes whatever is blocked — the connect, the TLS handshake, a read, a write — throw at once. A timeout per read would let a server that drips one byte at a time hold a send for ever; a socket write has no timeout at all. Name resolution, which no socket bounds, runs on a thread of its own and is waited for only until the deadline.
What a failure says. The stage it happened at and the server's reply code —
refused at RCPT with 550 — and never the recipient, the server's own text, the
message or a password: the caller logs what it is given (auth-base logs a delivery
failure whole), and a reply such as 550 <ada@…> unknown carries the address.
The SMTP submission client: one message per connection, over TLS, inside one wall-clock deadline (SPEC §5). **The deadline is a watchdog, not a timeout per call.** A virtual thread closes the socket when the deadline passes, which makes whatever is blocked — the connect, the TLS handshake, a read, a write — throw at once. A timeout per read would let a server that drips one byte at a time hold a send for ever; a socket write has no timeout at all. Name resolution, which no socket bounds, runs on a thread of its own and is waited for only until the deadline. **What a failure says.** The stage it happened at and the server's reply code — `refused at RCPT with 550` — and never the recipient, the server's own text, the message or a password: the caller logs what it is given (auth-base logs a delivery failure whole), and a reply such as `550 <ada@…> unknown` carries the address.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |