@cool-ai/beach-transport-email
v0.2.0
Published
IMAP and SMTP transport adapters for Beach — the wire layer underneath @cool-ai/beach-channel-email.
Readme
@cool-ai/beach-transport-email
Home: cool-ai.org · Documentation: cool-ai.org/docs
IMAP and SMTP transport adapters for Beach. Wire layer only — connection management, polling, parsing, sending. Channel-shaped concerns (envelope translation, threading, the Missive part-bag) live in @cool-ai/beach-channel-email.
When to use this package directly
- You are writing a custom email channel layered on a different missive shape.
- You need to swap a formatter on the SMTP adapter without taking the whole channel package.
- You are reimplementing parts of
@cool-ai/beach-channel-emailand only want the transport pieces.
If any of those does not match, use @cool-ai/beach-channel-email instead — it bundles these adapters with the canonical envelope-to-message translation and is what most consumers want.
Quickstart
import { ImapInboundAdapter, SmtpOutboundAdapter } from '@cool-ai/beach-transport-email';
const inbound = new ImapInboundAdapter({
config: {
host: 'imap.example.com',
port: 993,
secure: true,
auth: { user: process.env.IMAP_USER!, pass: process.env.IMAP_PASS! },
},
pollIntervalMs: 60_000,
uidState: {
get: (mailbox) => redis.get(`beach-email:lastUid:${mailbox}`).then((v) => (v ? Number(v) : undefined)),
set: (mailbox, uid) => { await redis.set(`beach-email:lastUid:${mailbox}`, String(uid)); },
},
onMessage: async (parsed) => {
/* parsed is RFC 822-shaped — translate to a Missive in your channel layer */
},
});
await inbound.start();
const outbound = new SmtpOutboundAdapter({
host: 'smtp.example.com',
port: 465,
secure: true,
auth: { user: process.env.SMTP_USER!, pass: process.env.SMTP_PASS! },
from: '[email protected]',
});
await outbound.send({
to: ['[email protected]'],
subject: 'Hello',
text: 'World',
});What this package contains
ImapInboundAdapter— long-lived IMAP connection, configurable polling interval, UID-state callback, RFC 822 parsing viamailparser. Hands aParsedInboundEmailto youronMessagecallback.SmtpOutboundAdapter—nodemailertransporter, singlesend(email)entry point. Owns the connection lifecycle.
That is the whole surface.
SMTP without authentication (trusted local relay)
SmtpTransportConfig.auth is optional. When omitted the adapter connects without authentication — suitable only for trusted-network local relays such as a Postfix daemon on localhost accepting unauthenticated connections from the loopback interface and signing outbound with DKIM via OpenDKIM.
const outbound = new SmtpOutboundAdapter({
host: 'localhost',
port: 25,
secure: false,
from: '[email protected]',
});The adapter emits a startup warning when auth is absent so the opt-out is visible in logs. Never use this mode against a remote SMTP server — anything on the network can relay through it. The IMAP side still requires auth; only the SMTP path supports the no-auth shape.
