Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ The auth server currently needs messaging support for exactly these flows:
- email OTP
- SMS OTP
- magic link email
- bootstrap invite email

Treat those as the primary domain API for `0.1.0`.

Expand Down Expand Up @@ -106,7 +105,7 @@ Keep the first release intentionally narrow.
- a provider-agnostic core package
- AWS email and SMS support
- Twilio SMS support
- the four auth flows already used by SeamlessAuth
- the three auth flows already used by SeamlessAuth
- docs and examples that show how an adopter API should consume the package

### Out of scope
Expand All @@ -133,7 +132,6 @@ Bias toward APIs that would let the auth server keep call sites similar to:
- `sendOtpEmail(...)`
- `sendOtpSms(...)`
- `sendMagicLinkEmail(...)`
- `sendBootstrapInviteEmail(...)`

## Style Guidance

Expand Down
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ It gives SeamlessAuth a small, explicit way to deliver auth emails and SMS witho
For `0.1.0`, the scope is intentionally narrow:

- TypeScript packages only
- the four auth flows already used by SeamlessAuth
- the three auth flows already used by SeamlessAuth
- AWS SES email
- AWS SNS SMS
- Twilio SMS
Expand All @@ -25,7 +25,6 @@ SeamlessAuth needs to send exactly these auth-related messages today:
- email OTP
- SMS OTP
- magic link email
- bootstrap invite email

This repo packages that responsibility into:

Expand All @@ -51,7 +50,6 @@ The core service keeps the auth-domain API small:
- `sendOtpEmail(...)`
- `sendOtpSms(...)`
- `sendMagicLinkEmail(...)`
- `sendBootstrapInviteEmail(...)`

## Install

Expand Down
1 change: 0 additions & 1 deletion docs/adopter-user-story.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ The intended flow is:
- OTP email
- OTP SMS
- magic link email
- bootstrap invite email
5. SeamlessAuth delivers a good default experience without forcing the adopter to own message rendering.

## Why This Model Works
Expand Down
4 changes: 1 addition & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ Provide a public auth-messaging package system for SeamlessAuth that sits behind

## Product Boundary

SeamlessAuth currently needs messaging support for four auth workflows:
SeamlessAuth currently needs messaging support for three auth workflows:

- OTP email
- OTP SMS
- magic link email
- bootstrap invite email

That is the right boundary for this repo. It should stay focused on auth delivery, not expand into a generic notification platform.

Expand Down Expand Up @@ -112,7 +111,6 @@ Public operations:
- `sendOtpEmail`
- `sendOtpSms`
- `sendMagicLinkEmail`
- `sendBootstrapInviteEmail`

This keeps the API aligned with current SeamlessAuth responsibilities.

Expand Down
14 changes: 5 additions & 9 deletions docs/provider-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@ The provider model should separate:

## Launch Matrix

| Flow | Channel | AWS | Twilio |
| ---------------- | ------- | --- | ------ |
| Email OTP | Email | Yes | No |
| SMS OTP | SMS | Yes | Yes |
| Magic link | Email | Yes | No |
| Bootstrap invite | Email | Yes | No |
| Flow | Channel | AWS | Twilio |
| ---------- | ------- | --- | ------ |
| Email OTP | Email | Yes | No |
| SMS OTP | SMS | Yes | Yes |
| Magic link | Email | Yes | No |

This is why a channel-based composition model is safer than a single `provider: "aws" | "twilio"` switch.

Expand All @@ -46,14 +45,12 @@ export interface AuthMessagingHandlers {
sendOtpEmail?: (input: SendOtpEmailInput) => Promise<DeliveryResult>;
sendOtpSms?: (input: SendOtpSmsInput) => Promise<DeliveryResult>;
sendMagicLinkEmail?: (input: SendMagicLinkEmailInput) => Promise<DeliveryResult>;
sendBootstrapInviteEmail?: (input: SendBootstrapInviteEmailInput) => Promise<DeliveryResult>;
}

export interface AuthMessagingService {
sendOtpEmail(input: SendOtpEmailInput): Promise<DeliveryResult>;
sendOtpSms(input: SendOtpSmsInput): Promise<DeliveryResult>;
sendMagicLinkEmail(input: SendMagicLinkEmailInput): Promise<DeliveryResult>;
sendBootstrapInviteEmail(input: SendBootstrapInviteEmailInput): Promise<DeliveryResult>;
}
```

Expand Down Expand Up @@ -96,7 +93,6 @@ Today that means:
- OTP email subject/body
- OTP SMS body
- magic link email subject/body
- bootstrap invite email subject/body

Transports deliver rendered content. They do not own auth business logic.

Expand Down
2 changes: 1 addition & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Ship and stabilize a realistic `0.1.0` for TypeScript users integrating Seamless

- TypeScript packages publishable to npm
- official support for AWS email/SMS and Twilio SMS
- support for the four current SeamlessAuth message flows
- support for the three current SeamlessAuth message flows
- documented integration path for SeamlessAuth server-side adapters
- tests for core logic and provider adapters
- README and package docs that make setup understandable without reading source
Expand Down
5 changes: 0 additions & 5 deletions examples/seamless-review-api/src/localSmoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,6 @@ async function main() {
to: "reviewer@example.com",
magicLinkUrl: "https://review.example.com/verify-magic-link?token=xyz",
});

await authMessaging.sendBootstrapInviteEmail({
to: "admin@example.com",
inviteUrl: "https://review.example.com/login?bootstrapToken=bootstrap-token",
});
}

void main();
22 changes: 1 addition & 21 deletions examples/seamless-review-api/src/withAuthMessageAudit.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type {
MessagingClient,
SendBootstrapInviteEmailInput,
SendMagicLinkEmailInput,
SendOtpEmailInput,
SendOtpSmsInput,
Expand Down Expand Up @@ -32,15 +31,7 @@ export type AuthMessageAuditEntry =
messageId?: string;
}
| {
operation: "sendBootstrapInviteEmail";
status: "sent";
to: string;
provider: string;
channel: "email" | "sms";
messageId?: string;
}
| {
operation: "sendOtpEmail" | "sendOtpSms" | "sendMagicLinkEmail" | "sendBootstrapInviteEmail";
operation: "sendOtpEmail" | "sendOtpSms" | "sendMagicLinkEmail";
status: "failed";
to: string;
error: unknown;
Expand Down Expand Up @@ -115,16 +106,5 @@ export function withAuthMessageAudit(
throw error;
}
},

async sendBootstrapInviteEmail(input: SendBootstrapInviteEmailInput) {
try {
const result = await messaging.sendBootstrapInviteEmail(input);
await auditSuccess(audit, "sendBootstrapInviteEmail", input.to, result);
return result;
} catch (error) {
await auditFailure(audit, "sendBootstrapInviteEmail", input.to, error);
throw error;
}
},
};
}
1 change: 0 additions & 1 deletion packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ This package owns the shared messaging contracts and the auth-focused service su
- OTP email
- OTP SMS
- magic link email
- bootstrap invite email

It exposes:

Expand Down
7 changes: 0 additions & 7 deletions packages/core/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,3 @@ export interface SendMagicLinkEmailInput {
from?: string;
subject?: string;
}

export interface SendBootstrapInviteEmailInput {
to: string;
inviteUrl: string;
from?: string;
subject?: string;
}
47 changes: 0 additions & 47 deletions packages/core/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,25 +69,6 @@ function buildMagicLinkHtml(appName: string, magicLinkUrl: string): string {
].join("\n");
}

function buildBootstrapInviteText(appName: string, inviteUrl: string): string {
return [
`You have been invited to bootstrap ${appName}.`,
"",
"Use the link below to continue:",
inviteUrl,
].join("\n");
}

function buildBootstrapInviteHtml(appName: string, inviteUrl: string): string {
return [
"<div>",
` <h1>Bootstrap invite for ${appName}</h1>`,
" <p>Use the link below to continue:</p>",
` <p><a href="${inviteUrl}">${inviteUrl}</a></p>`,
"</div>",
].join("\n");
}

function buildOtpSmsText(appName: string, token: string | number): string {
return `Your ${appName} verification code is: ${token}. No one will ever ask you for this code. Do not share it.`;
}
Expand Down Expand Up @@ -207,34 +188,6 @@ export function createAuthMessagingService(

return email.send(message);
},

async sendBootstrapInviteEmail(input) {
if (handlers?.sendBootstrapInviteEmail) {
return handlers.sendBootstrapInviteEmail(input);
}

if (!email) {
throw new UnsupportedChannelError("email");
}

assertLikelyEmail(input.to, "input.to");
assertLikelyUrl(input.inviteUrl, "input.inviteUrl");

const message = applyEmailOverride(
overrides?.bootstrapInviteEmail,
input,
{
to: input.to,
from: input.from ?? defaults?.emailFrom,
subject: input.subject ?? `${appName} - Bootstrap invite`,
text: buildBootstrapInviteText(appName, input.inviteUrl),
html: buildBootstrapInviteHtml(appName, input.inviteUrl),
},
context,
);

return email.send(message);
},
};
}

Expand Down
14 changes: 1 addition & 13 deletions packages/core/src/transports.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import type {
SendBootstrapInviteEmailInput,
SendMagicLinkEmailInput,
SendOtpEmailInput,
SendOtpSmsInput,
} from "./messages.js";
import type { SendMagicLinkEmailInput, SendOtpEmailInput, SendOtpSmsInput } from "./messages.js";
import type { DeliveryResult, EmailMessage, SmsMessage } from "./types.js";

export interface EmailTransport {
Expand All @@ -20,7 +15,6 @@ export interface AuthMessagingHandlers {
sendOtpEmail(input: SendOtpEmailInput): Promise<DeliveryResult>;
sendOtpSms(input: SendOtpSmsInput): Promise<DeliveryResult>;
sendMagicLinkEmail(input: SendMagicLinkEmailInput): Promise<DeliveryResult>;
sendBootstrapInviteEmail(input: SendBootstrapInviteEmailInput): Promise<DeliveryResult>;
}

export interface AuthMessageOverrideContext {
Expand All @@ -43,18 +37,12 @@ export interface AuthMessageOverrides {
defaults: EmailMessage,
context: AuthMessageOverrideContext,
) => EmailMessage;
bootstrapInviteEmail?: (
input: SendBootstrapInviteEmailInput,
defaults: EmailMessage,
context: AuthMessageOverrideContext,
) => EmailMessage;
}

export interface AuthMessagingService {
sendOtpEmail(input: SendOtpEmailInput): Promise<DeliveryResult>;
sendOtpSms(input: SendOtpSmsInput): Promise<DeliveryResult>;
sendMagicLinkEmail(input: SendMagicLinkEmailInput): Promise<DeliveryResult>;
sendBootstrapInviteEmail(input: SendBootstrapInviteEmailInput): Promise<DeliveryResult>;
}

export type EmailProvider = EmailTransport;
Expand Down
Loading