@hiteshmodi0624/relay
v0.4.0
Published
A messaging / communications service. Delivers a `Message` to a `Recipient` over a `CommsChannel` (email today; SMS / Slack / WhatsApp / push later), with deliverability concerns (suppression, audit log) handled centrally. Consumed as a library/service by
Readme
Relay
A messaging / communications service. Delivers a Message to a Recipient over a CommsChannel
(email today; SMS / Slack / WhatsApp / push later), with deliverability concerns (suppression,
audit log) handled centrally. Consumed as a library/service by other products (Extrack, OpsPilot);
may later be wrapped as a standalone hosted service.
Abstraction is the product. Built like a Java system — ports & adapters, programming to interfaces, dependency inversion. Templates and brand stay with the caller; Relay moves bytes.
Run
yarn— installyarn test— run testsyarn build— typecheck + emityarn demo— dispatch a sample message through the in-memory channel end-to-end
Layout (hexagonal)
src/
domain/ pure value objects + entities (classes)
ports/ interfaces (CommsChannel, SuppressionStore, DeliveryLog, Clock)
application/ use-case services (MessageDispatcher, ChannelRegistry)
adapters/ concrete port implementations (in-memory + SES email + DynamoDB stores)
infra/ reusable CDK construct provisioning Relay's DynamoDB tables (RelayTables)
demo.ts composition root for the demoSee CLAUDE.md for the strict rules and docs/guidelines/architecture.md for the patterns.
Provisioning the durable stores (CDK)
In dynamo mode the suppression + delivery-log adapters target two DynamoDB tables. Relay is a
consumed library, not a deployable, so it ships the tables as a reusable CDK construct that you
instantiate inside your own stack. aws-cdk-lib / constructs are peer dependencies (you pin
the CDK version); the construct lives outside the core barrel, so importing Relay's dispatcher never
pulls in CDK.
import { RelayTables } from 'relay/dist/infra/relayTables.js';
const tables = new RelayTables(this, 'RelayTables', {
suppressionTableName: 'relay-suppression',
deliveryLogTableName: 'relay-delivery-log',
// removalPolicy defaults to RETAIN (suppression/audit data must survive a teardown)
});
myLambda.addEnvironment('RELAY_SUPPRESSION_MODE', 'dynamo');
myLambda.addEnvironment('RELAY_SUPPRESSION_TABLE', tables.suppressionTable.tableName);
myLambda.addEnvironment('RELAY_DELIVERY_LOG_MODE', 'dynamo');
myLambda.addEnvironment('RELAY_DELIVERY_LOG_TABLE', tables.deliveryLogTable.tableName);
tables.suppressionTable.grantReadWriteData(myLambda);
tables.deliveryLogTable.grantWriteData(myLambda);Each table is keyed on a single String partition key pk, billed PAY_PER_REQUEST, with no TTL and
no secondary index — the exact shape the Dynamo adapters assume.
