ajo

Ecosystem

Mail with a clear boundary

Validated messages, HTTP delivery and a development mailbox.

Choose a transport

Mail validates every message, bounds concurrency and enforces a delivery deadline. Use the HTTP provider transport in an engine App, or capture for development.

Terminal
pnpm add ajo-kit-mail@0.3.0

Capture messages locally

configure() connects the transport to this package and Kit’s mail seam. The bounded in-memory capture transport is refused in production.

Development setup
import { configure, send } from 'ajo-kit-mail'
import { capture } from 'ajo-kit-mail/capture'

const mailbox = capture()
configure({ transport: mailbox, from: 'hello@example.com' })
await send({
  to: 'reader@example.com',
  subject: 'Your first message',
  text: 'Hello from Ajo.',
})

Map an HTTP provider

The HTTP transport uses fetch and maps a validated envelope into your provider’s JSON schema. Keep credentials in a server-side environment accessor. The optional nodemailer SMTP transport is a Node-host integration; do not include it in the engine’s production graph.

Provider transport · implement the credential accessor
import { http } from 'ajo-kit-mail/http'

const transport = http({
  url: 'https://api.provider.example/send',
  headers: () => ({ Authorization: readProviderAuthorization() }),
  body: mail => ({
    from: mail.from.address, to: mail.to.address,
    subject: mail.subject, text: mail.text,
  }),
})

Handle delivery outcomes

Each delivery makes one attempt. send() throws a sanitized error; deliver() returns an outcome for code that decides whether to retry. An idempotency key is forwarded to providers that support one.