@plinthjs/mail
v0.1.0
Published
Mason mailer: mailables, transports, array/log transport for tests (the Illuminate\Mail equivalent).
Maintainers
Readme
@plinthjs/mail
The Mason mail layer — the Illuminate\Mail equivalent. Mailables (fluent or subclassed),
a swappable transport contract, and the mailer that drives the send lifecycle.
No transport here touches the network: ArrayTransport captures messages in memory for
assertions, and LogTransport renders them to an injected sink. Message ids come from an
injected counter, so the whole stack is deterministic in tests.
Usage
import { ArrayTransport, Mailable, Mailer } from '@plinthjs/mail'
const transport = new ArrayTransport()
const mailer = new Mailer(transport)
// Fluent mailable.
await mailer.send(
new Mailable()
.from({ email: '[email protected]', name: 'App' })
.to('[email protected]')
.subject('Welcome')
.html('<h1>Hello</h1>'),
)
// Fluent recipient entry point.
await mailer.to('[email protected]').cc('[email protected]').send(new Mailable().subject('Hi'))
// Quick one-off plain-text mail.
await mailer.raw('Heads up', (m) => m.to('[email protected]').subject('Notice'))
// Assert on what was sent — no network involved.
transport.sent[0].subject // 'Welcome'Subclassing a mailable
Override build() to configure the mailable from its constructor "props"; the mailer invokes it
before sending.
class OrderShipped extends Mailable {
constructor(private readonly orderId: number) {
super()
}
override build(): void {
this.subject(`Order #${this.orderId} shipped`).html('<p>On its way</p>')
}
}
await mailer.to('[email protected]').send(new OrderShipped(42))Transports
import { LogTransport } from '@plinthjs/mail'
// Render every message to a sink instead of delivering it.
const lines: string[] = []
const mailer = new Mailer(new LogTransport((line) => lines.push(line)))Implement the Transport interface (send(message): Promise<SentMessage>) to plug in a real
SMTP/API transport later without touching mailables or the mailer.
Deterministic ids
Inject an id sequence so receipts never depend on a global counter:
let n = 0
const transport = new ArrayTransport({ sequence: () => (n += 1) })