CHAPTER 1 OF 3
Send a message. Return a receipt.
The client calls send(message). Our local EmailSender accepts the message and returns a receipt; no real email leaves this page.
Send a sample message and follow the call and its return.
Appearance
DECORATOR / THE WALKTHROUGH
From one local email sender to independent, composable responsibilities.
CHAPTER 1 OF 3
The client calls send(message). Our local EmailSender accepts the message and returns a receipt; no real email leaves this page.
Send a sample message and follow the call and its return.
ONE CALL, OPTIONAL EXTRAS
This is an offline notification simulator. Tracking records local acceptance, not delivery or a read confirmation.
USES SENDER
send(message)Same call for every combination.
CORE BEHAVIOR
send(message)Accept the message and return a receipt.
Send a sample to see the actual order of calls and side effects.
Choose your extras, then send a local sample message.
THE SIMPLE START
The client depends on the small Sender interface. Our EmailSender implements it. The receipt means this local simulator accepted the message; it says nothing about a real inbox.
Every implementation accepts a message and returns a receipt, or throws an error.
export type Receipt = Readonly<{ id: string; status: "accepted" }>;
export interface Sender {
send(message: string): Receipt;
}The client uses send() without needing to know the concrete sender.
export function notify(sender: Sender, message: string): Receipt {
return sender.send(message);
}This offline implementation validates the message and creates an immutable receipt. No network service is involved.
// Offline stand-in: acceptance is not a delivery or read confirmation.
export class EmailSender implements Sender {
private record: RecordEvent;
private fail: boolean;
private sequence = 0;
constructor(record: RecordEvent, fail = false) {
this.record = record;
this.fail = fail;
}
send(message: string): Receipt {
this.record({
actor: "email",
phase: "call",
text: "Email sender receives the message.",
});
if (!message.trim() || message.length > 160)
throw new Error("Use a message of 1–160 characters.");
if (this.fail) {
this.record({
actor: "email",
phase: "error",
text: "Email sender fails. No receipt is created.",
});
throw new Error("Local email service unavailable.");
}
const receipt = Object.freeze({
id: `local-${++this.sequence}`,
status: "accepted" as const,
});
this.record({
actor: "email",
phase: "return",
text: `Email sender accepts the message: ${receipt.id}.`,
});
return receipt;
}
}Chapter changes start fresh. Theme and language preserve the current example.