howdy/mail/outbox
Keep mail instead of sending it, for development and tests. The admin
area (admin.mail) shows what an outbox holds, and tests can read the
last message to an address.
// dev/my_app_dev.gleam
let assert Ok(box) = outbox.start_in("tmp/mail")
let mailer = mail.mailer(outbox.adapter(box))
Start the outbox once, outside the function howdy/dev rebuilds the
app from, so it survives reloads. With start_in, each message is also
written to the directory as an .eml file any mail client can open, and
a .json file the outbox reads back after a restart. Add the directory
to .gitignore: it holds sign-in tokens and whatever else was sent.
In an app with a supervision tree, supervised and supervised_in
give the outbox as a child instead, so it is restarted if it crashes.
While it is not running, sending to it is Unavailable, like any other
adapter whose provider is away, and reading it is an error rather than
a crash.
Only the newest 500 messages are kept; older ones and their files are removed.
Types
Values
pub fn adapter(box: Outbox) -> mail.Adapter
Keep messages given to this adapter in the outbox. While the outbox is
not running, sending is Unavailable.
pub fn clear(box: Outbox) -> Result(Nil, mail.Error)
Forget every message, and delete their files.
pub fn directory(box: Outbox) -> option.Option(String)
Where messages are written, if anywhere.
pub fn get(box: Outbox, id: String) -> Result(mail.Outgoing, Nil)
The message with this id. Error(Nil) when there is none, or the
outbox is not running.
pub fn latest_to(
box: Outbox,
email: String,
) -> Result(mail.Outgoing, Nil)
The newest message with this address among its recipients. Error(Nil)
when there is none, or the outbox is not running.
pub fn messages(
box: Outbox,
) -> Result(List(mail.Outgoing), mail.Error)
Every message kept, newest first, or Unavailable while the outbox is
not running.
pub fn pid(box: Outbox) -> Result(process.Pid, Nil)
The outbox process, while it is running.
pub fn start() -> Outbox
An outbox in memory: its messages are gone when the VM stops. It is
linked to the caller; see supervised for an app with a supervision
tree.
pub fn start_in(directory: String) -> Result(Outbox, String)
An outbox that also writes each message to directory, creating it if
needed, and starts with the messages already there.
pub fn subscribe(box: Outbox, notify: fn() -> Nil) -> Nil
Call notify whenever a message arrives or the outbox is cleared, until
the calling process exits. It runs in the outbox’s process, so it should
only send a message, as a live component’s dispatch does. Nothing is
subscribed while the outbox is not running, and a restarted outbox
starts without subscribers.
pub fn supervised() -> #(
Outbox,
supervision.ChildSpecification(Outbox),
)
start as a child of a supervisor, with the handle to use it by. The
handle is made here, before the outbox starts, so it can be given to the
app while the supervisor owns the outbox:
let #(box, child) = outbox.supervised()
static_supervisor.new(static_supervisor.OneForOne)
|> static_supervisor.add(child)
|> static_supervisor.add(howdy.supervised(app(outbox.adapter(box))))
A restarted outbox in memory starts empty, and its subscribers have to subscribe again.
pub fn supervised_in(
directory: String,
) -> #(Outbox, supervision.ChildSpecification(Outbox))
start_in as a child of a supervisor. A restarted outbox reads the
directory again, so its messages are kept.