← All @molecule/* packages · App templates
@molecule/api-resource-subscriberAPI resource · subscribers · API (Node) · v1.0.1 · Apache-2.0
Tokenized email/sms/webhook subscriber records with confirm/unsubscribe links.
npm install @molecule/api-resource-subscriber@molecule/api-resource-subscriber is an API resource: the routes, validation and storage for subscribers, built on the database and auth cores so it runs on whichever providers your app has bonded.
import { routes, requestHandlerMap } from '@molecule/api-resource-subscriber'
for (const route of routes) {
// Only the admin-only routes declare `middlewares` (routes is a const union).
const names = 'middlewares' in route ? route.middlewares : []
const middlewares = names.map((name) => requestHandlerMap[name])
app[route.method](route.path, ...middlewares, requestHandlerMap[route.handler])
}Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Subscriber resource for molecule.dev.
Tokenized email/sms/webhook subscriber records with confirm/unsubscribe links.
Designed for status-page-style "subscribe to incident updates" and newsletter
signup flows. A subscriber is created in pending status and confirmed via a
one-time token; opt-out is one-click via a separate unsubscribe token. Both
tokens are returned exactly once on creation and are never exposed via the
public listing/read endpoints.
The list, read, and del routes are admin-only — always apply their
declared middlewares (the requireAdmin authorizer) when wiring, as shown
below. Each handler also re-checks admin authorization internally, so the gate
holds even if the middlewares are omitted; "admin" resolves via an admin
session claim or an @molecule/api-permissions grant, fail-closed otherwise.
import { routes, requestHandlerMap } from '@molecule/api-resource-subscriber'
for (const route of routes) {
// Only the admin-only routes declare `middlewares` (routes is a const union).
const names = 'middlewares' in route ? route.middlewares : []
const middlewares = names.map((name) => requestHandlerMap[name])
app[route.method](route.path, ...middlewares, requestHandlerMap[route.handler])
}
resource
npm install @molecule/api-resource-subscriber @molecule/api-database @molecule/api-i18n @molecule/api-logger @molecule/api-permissions @molecule/api-resource
CreateSubscriberInputInput for creating a subscriber.
interface CreateSubscriberInput {
/** Delivery channel. */
channel: SubscriberChannel
/** Channel-specific address. */
address: string
/** Optional grouping topic. */
topic?: string | null
/** Arbitrary subscriber metadata. */
metadata?: Record<string, unknown> | null
}
CreateSubscriberResultResult of creating a subscriber. Includes the one-time confirm token so the caller can build the confirmation link before the token disappears.
interface CreateSubscriberResult {
/** The created subscriber. */
subscriber: PublicSubscriber
/** One-time token to confirm the subscription. Returned only on creation. */
confirmToken: string
/** Token to unsubscribe. Returned only on creation. */
unsubscribeToken: string
}
PaginatedResultA paginated result set.
interface PaginatedResult<T> {
/** The page of results. */
data: T[]
/** Total number of matching records. */
total: number
/** Current page number. */
page: number
/** Page size. */
limit: number
}
SubscriberA subscriber record.
interface Subscriber {
/** Unique subscriber identifier. */
id: string
/** Delivery channel (email, sms, or webhook). */
channel: SubscriberChannel
/** Channel-specific address: email address, E.164 phone number, or webhook URL. */
address: string
/**
* Optional grouping topic — e.g. `"incident-updates"`, `"weekly-newsletter"`,
* or `"service:api"`. The same address may subscribe to multiple topics
* (each as its own record).
*/
topic: string | null
/** Lifecycle status. */
status: SubscriberStatus
/** Arbitrary subscriber metadata (locale, source page, etc.). */
metadata: Record<string, unknown> | null
/** Timestamp the subscription was confirmed (null while pending). */
confirmedAt: string | null
/** Timestamp the subscriber unsubscribed (null while still subscribed). */
unsubscribedAt: string | null
/** Creation timestamp. */
createdAt: string
/** Last modification timestamp. */
updatedAt: string
}
SubscriberQueryQuery options for listing subscribers.
interface SubscriberQuery {
/** Filter by channel. */
channel?: SubscriberChannel
/** Filter by status. */
status?: SubscriberStatus
/** Filter by topic. */
topic?: string
/** Page number (1-based). */
page?: number
/** Items per page. */
limit?: number
}
SubscriberRowInternal database row for a subscriber. Includes private token fields.
interface SubscriberRow {
/** Unique subscriber identifier. */
id: string
/** Delivery channel. */
channel: string
/** Channel-specific address. */
address: string
/** Optional grouping topic. */
topic: string | null
/** Lifecycle status. */
status: string
/** Confirm token (private — never returned via the public listing endpoint). */
confirmToken: string
/** Unsubscribe token (private). */
unsubscribeToken: string
/** JSON-serialized metadata. */
metadata: string | null
/** Timestamp the subscription was confirmed. */
confirmedAt: string | null
/** Timestamp the subscriber unsubscribed. */
unsubscribedAt: string | null
/** Creation timestamp. */
createdAt: string
/** Last modification timestamp. */
updatedAt: string
}
PublicSubscriberPublic-safe view of a subscriber. Tokens are intentionally omitted — they are returned exactly once on creation and otherwise never leave the database.
type PublicSubscriber = Subscriber
SubscriberChannelDelivery channels supported for subscribers.
type SubscriberChannel = 'email' | 'sms' | 'webhook'
SubscriberStatusLifecycle status of a subscriber.
pending — created but not yet confirmed via the confirm token.confirmed — confirmed and eligible to receive deliveries.unsubscribed — opted out via the unsubscribe token; preserved for audit
so the same address cannot silently re-subscribe and re-trigger sends.type SubscriberStatus = 'pending' | 'confirmed' | 'unsubscribed'
confirm(req, res)Confirms a subscriber by token.
function confirm(req: MoleculeRequest, res: MoleculeResponse): Promise<void>
req — Request with :token path parameter.res — Response. On success returns the confirmed subscriber (no token).del(req, res)Hard-deletes a subscriber by id.
function del(req: MoleculeRequest, res: MoleculeResponse): Promise<void>
req — Request with :id path parameter.res — Response. Returns 204 on success or 404 if no row matched.generateToken(byteLength)Generates a cryptographically random URL-safe token suitable for a one-time confirm or unsubscribe link.
function generateToken(byteLength?: number): string
byteLength — Number of random bytes (default 32 → 43 base64url chars).Returns: A URL-safe random token.
isSubscriberAdmin(res)Resolves whether the current request's session belongs to an actor authorized
to administer subscribers (list/read/delete PII). Fail-closed: returns false
when there is no authenticated session, and otherwise only true when the
session carries an admin claim or a bonded permissions provider grants the
manage subscriber permission.
Use this for in-handler defense-in-depth (it does not depend on the route middleware being preserved by the injector).
function isSubscriberAdmin(res: MoleculeResponse): Promise<boolean>
res — The response whose locals.session is inspected.Returns: true when the session is an authorized subscriber admin.
isSubscriberChannel(value)Type guard for {@link SubscriberChannel}.
function isSubscriberChannel(value: unknown): boolean
value — Value to test.Returns: True if the value is a valid channel.
isSubscriberStatus(value)Type guard for {@link SubscriberStatus}.
function isSubscriberStatus(value: unknown): boolean
value — Value to test.Returns: True if the value is a valid status.
isValidAddress(channel, address)Validates a channel-specific address (email, E.164 phone, or HTTP(S) URL).
function isValidAddress(channel: SubscriberChannel, address: string): boolean
channel — The delivery channel.address — The address to validate.Returns: True if the address is structurally valid for the channel.
list(req, res)Lists subscribers with optional filtering and pagination.
function list(req: MoleculeRequest, res: MoleculeResponse): Promise<void>
req — Request with optional channel, status, topic, page, and limit query params.res — Response. On success returns a {@link PaginatedResult} of {@link PublicSubscriber}.parseMetadata(raw)Parses a JSON-serialized metadata column.
function parseMetadata(raw: string | Record<string, unknown> | null): Record<string, unknown> | null
raw — Raw column value (string, object, or null).Returns: Parsed metadata object, or null on missing/invalid input.
read(req, res)Reads a single subscriber by id.
function read(req: MoleculeRequest, res: MoleculeResponse): Promise<void>
req — Request with :id path parameter.res — Response.requireAdmin()Route middleware that gates the admin-only subscriber routes (list, read,
del). Calls next() only for an authenticated admin; otherwise forwards an
error to the framework error handler — Unauthorized when no session is
present, Forbidden when the session is authenticated but not an admin.
Exposed as a requestHandlerMap key so the injector's route scanner keeps it
(unlike the inert global 'authenticate' string, which is dropped).
function requireAdmin(): MoleculeRequestHandler
Returns: An Express-compatible middleware function.
subscribe(req, res)Creates a new pending subscriber. Re-issuing a subscription against an
existing (channel, address, topic) triple is rejected with 409 — callers
should resend the confirmation link out-of-band rather than mint a new one.
function subscribe(req: MoleculeRequest, res: MoleculeResponse): Promise<void>
req — Request whose body matches {@link CreateSubscriberInput}.res — Response. On success returns 201 with { subscriber, confirmToken, unsubscribeToken }.toPublicSubscriber(row)Converts a database row into the public-safe {@link PublicSubscriber} view. Strips the private confirm/unsubscribe tokens.
function toPublicSubscriber(row: SubscriberRow): Subscriber
row — Raw database row.Returns: Public subscriber view (no tokens).
unsubscribe(req, res)Unsubscribes a subscriber by token.
function unsubscribe(req: MoleculeRequest, res: MoleculeResponse): Promise<void>
req — Request with :token path parameter.res — Response. On success returns the unsubscribed subscriber.i18nRegisteredWhether i18n registration has been attempted. Always true; this module is
a placeholder for symmetry with locale-bonded resources.
const i18nRegistered: true
requestHandlerMapHandler map for subscriber resource routes.
requireAdmin is the admin authorizer middleware referenced by the
list/read/del routes. It must live here (as a real handler-map key) so
the mlcl injector's route scanner preserves it — a bare middleware string that
isn't a handler-map key is silently dropped.
const requestHandlerMap: {
readonly subscribe: typeof subscribe
readonly confirm: typeof confirm
readonly unsubscribe: typeof unsubscribe
readonly list: typeof list
readonly read: typeof read
readonly del: typeof del
readonly requireAdmin: MoleculeRequestHandler
}
routesRoutes for the subscriber resource. Public endpoints (subscribe, confirm,
unsubscribe) intentionally have no middleware so anonymous visitors of a
status page or newsletter form can use them.
Listing, reading, and deletion expose / mutate subscriber PII and are gated
admin-only by the requireAdmin middleware (a real requestHandlerMap
key — see {@link requireAdmin} — so the injector preserves it; the previously
declared global 'authenticate' string was silently dropped by the route
scanner, leaving these routes open to any authenticated user). Each handler
additionally re-checks admin authorization internally, so the gate holds even
if a consumer wires the routes without applying these middlewares.
const routes: readonly [
{ readonly method: 'post'; readonly path: '/subscribers'; readonly handler: 'subscribe' },
{
readonly method: 'get'
readonly path: '/subscribers/confirm/:token'
readonly handler: 'confirm'
},
{
readonly method: 'post'
readonly path: '/subscribers/unsubscribe/:token'
readonly handler: 'unsubscribe'
},
{
readonly method: 'get'
readonly path: '/subscribers'
readonly handler: 'list'
readonly middlewares: readonly ['requireAdmin']
},
{
readonly method: 'get'
readonly path: '/subscribers/:id'
readonly handler: 'read'
readonly middlewares: readonly ['requireAdmin']
},
{
readonly method: 'delete'
readonly path: '/subscribers/:id'
readonly handler: 'del'
readonly middlewares: readonly ['requireAdmin']
},
]
SUBSCRIBER_ADMIN_PERMISSIONSession-claim permission string ('subscriber:manage') that, when present in a
session's permissions array, grants subscriber administration without a bonded
permissions provider.
const SUBSCRIBER_ADMIN_PERMISSION: 'subscriber:manage'
SUBSCRIBER_CHANNELSAll valid subscriber channels.
const SUBSCRIBER_CHANNELS: readonly SubscriberChannel[]
SUBSCRIBER_PERMISSION_ACTIONPermission action checked against @molecule/api-permissions for subscriber
administration.
const SUBSCRIBER_PERMISSION_ACTION: 'manage'
SUBSCRIBER_PERMISSION_RESOURCEPermission resource checked against @molecule/api-permissions for subscriber
administration.
const SUBSCRIBER_PERMISSION_RESOURCE: 'subscriber'
SUBSCRIBER_STATUSESAll valid subscriber statuses.
const SUBSCRIBER_STATUSES: readonly SubscriberStatus[]
Peer dependencies:
@molecule/api-database ^1.0.1@molecule/api-i18n ^1.0.1@molecule/api-logger ^1.0.1@molecule/api-permissions ^1.0.1@molecule/api-resource ^1.0.1@molecule/api-database@molecule/api-i18n@molecule/api-logger@molecule/api-permissions@molecule/api-resourceDelivery is YOUR app's concern: subscribe stores the record and returns
{ subscriber, confirmToken, unsubscribeToken } exactly once (201) — this
package sends nothing. Build the confirm/unsubscribe links from those tokens
and deliver them via your email/SMS/webhook bond. Re-subscribing an existing
(channel, address, topic) returns 409.
Tables: src/__setup__/subscribers.sql creates subscribers (unique per
(channel, address, topic), unique token indexes). An mlcl-scaffolded API
replays __setup__/*.sql automatically on migrate; anywhere else run it
once — nothing at runtime creates them.
Lifecycle + authorization checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows (newsletter form, status-page "subscribe to updates", the confirm + unsubscribe links), and check every box off one by one. A box you can't check is an integration bug to fix — not a skip. This is double-opt-in LIST membership (email/sms/webhook), NOT paid billing — statuses are pending → confirmed → unsubscribed:
pending (NOT yet
eligible for sends) and the app delivers a confirmation link. The sandbox
CAPTURES outbound email/sms instead of sending — read it with the
read_activity tool to get the confirm link; never mock the delivery.confirmed (confirmedAt
set) and only a confirmed subscriber receives topic deliveries; visiting the
same link again is idempotent — still confirmed, no error, no re-timestamp.unsubscribed
(unsubscribedAt set) and stops all further sends to that address; the UI
reflects the opt-out and re-posting the same link is idempotent (200).