Channels
WIPOutbound message delivery - email, SMS, WebPush. Console + Test for dev, SMTP + Resend + SES + Twilio + WebPush for production.
What is a channel?
A channel is an outbound transport for messages - magic links,
verification emails, password resets, MFA codes, anomaly alerts. Every
channel implements AuthChannel.IChannel:
interface IChannel {
id: string
kind: 'email' | 'sms' | 'webpush'
send(input: {
templateId: string
identity: { id: string }
tenant: { tenantId?: string }
vars: Record<string, unknown>
}): Promise<{ ok: boolean; providerMessageId: string }>
}
Wire channels into providers that need outbound delivery (most notably magic-link):
import { magicLink } from '@gentleduck/auth/providers/magic-link'
import { AuthResendChannel } from '@gentleduck/auth/channels/resend'
import { AuthTwilioChannel } from '@gentleduck/auth/channels/twilio'
magicLink({
channels: {
email: new AuthResendChannel({ apiKey: ..., from: 'no-reply@example.com' }),
sms: new AuthTwilioChannel({ accountSid, authToken, from }),
},
// ...
})
Shipped channels
| Channel | Import | Kind |
|---|---|---|
AuthConsoleChannel | @gentleduck/auth/channels/console | email / sms / webpush - dev |
AuthSmtpChannel | @gentleduck/auth/channels/smtp | email - any nodemailer-compatible SMTP relay |
AuthResendChannel | @gentleduck/auth/channels/resend | email - Resend transactional API |
AuthSesChannel | @gentleduck/auth/channels/ses | email - AWS SES |
AuthTwilioChannel | @gentleduck/auth/channels/twilio | sms - Twilio Programmable Messaging |
AuthWebPushChannel | @gentleduck/auth/channels/webpush | webpush - Web Push protocol with VAPID |
AuthTestChannel | @gentleduck/auth/channels/console | captures sends into an outbox array - for tests |
AuthNoopChannel | @gentleduck/auth/channels/console | discards sends, always returns ok - for benchmarks |
Templating
duck-auth does not template messages itself. The channel receives a
typed vars payload from the runtime; rendering the body is your
channel's responsibility:
class CustomResendChannel implements AuthChannel.IChannel {
id = 'custom-resend'
kind = 'email' as const
async send({ templateId, identity, vars }) {
const html = mjml(`<mjml>...${vars.url}...</mjml>`).html
const res = await resend.emails.send({
from: 'no-reply@example.com',
to: vars.email as string,
subject: this.subjectFor(templateId, vars),
html,
})
return { ok: true, providerMessageId: res.id }
}
subjectFor(templateId: string, vars: Record<string, unknown>) {
switch (templateId) {
case 'magic-link': return `Sign in to ${vars.appName}`
case 'password-reset': return `Reset your ${vars.appName} password`
case 'email-verify': return `Verify your email`
default: return 'Notification'
}
}
}
For most apps, the shipped channels with their default templates are enough; for full control, implement your own.
Multi-channel magic-link
magicLink({
channels: {
email: new AuthResendChannel({ ... }),
sms: new AuthTwilioChannel({ ... }),
webpush: new AuthWebPushChannel({ ... }),
},
})
// Client picks one:
await client.beginProvider('magic-link', { email, channel: 'email' })
await client.beginProvider('magic-link', { email, channel: 'sms' })
await client.beginProvider('magic-link', { email, channel: 'webpush' })
The provider routes to channels[channel].send(...). If a channel
fails (transient SMTP error), the provider raises AUTH/PROVIDER_FAILED
and the user can retry.
Audit trail
Every channel call emits a channel.sent (or channel.failed) event
on the configured event bus:
auth.events.on('channel.sent', (event) => {
console.log('sent', event.templateId, 'to', event.identity.id)
})
HIPAA and SOC2 compliance presets require the audit event to be persisted; they automatically wire a webhook deliverer to ship every channel event to your audit log.