@resq-systems/email-templates 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +236 -0
- package/lib/contract.d.ts +125 -0
- package/lib/contract.d.ts.map +1 -0
- package/lib/contract.js +29 -0
- package/lib/contract.js.map +1 -0
- package/lib/emails/incident-alert.d.ts +8 -0
- package/lib/emails/incident-alert.d.ts.map +1 -0
- package/lib/emails/incident-alert.js +44 -0
- package/lib/emails/incident-alert.js.map +1 -0
- package/lib/emails/index.d.ts +13 -0
- package/lib/emails/index.js +13 -0
- package/lib/emails/mission-approval.d.ts +8 -0
- package/lib/emails/mission-approval.d.ts.map +1 -0
- package/lib/emails/mission-approval.js +46 -0
- package/lib/emails/mission-approval.js.map +1 -0
- package/lib/emails/new-device-login.d.ts +8 -0
- package/lib/emails/new-device-login.d.ts.map +1 -0
- package/lib/emails/new-device-login.js +47 -0
- package/lib/emails/new-device-login.js.map +1 -0
- package/lib/emails/notification.d.ts +8 -0
- package/lib/emails/notification.d.ts.map +1 -0
- package/lib/emails/notification.js +33 -0
- package/lib/emails/notification.js.map +1 -0
- package/lib/emails/org-invitation.d.ts +8 -0
- package/lib/emails/org-invitation.d.ts.map +1 -0
- package/lib/emails/org-invitation.js +41 -0
- package/lib/emails/org-invitation.js.map +1 -0
- package/lib/emails/otp.d.ts +11 -0
- package/lib/emails/otp.d.ts.map +1 -0
- package/lib/emails/otp.js +37 -0
- package/lib/emails/otp.js.map +1 -0
- package/lib/emails/password-changed.d.ts +8 -0
- package/lib/emails/password-changed.d.ts.map +1 -0
- package/lib/emails/password-changed.js +36 -0
- package/lib/emails/password-changed.js.map +1 -0
- package/lib/emails/password-reset.d.ts +8 -0
- package/lib/emails/password-reset.d.ts.map +1 -0
- package/lib/emails/password-reset.js +37 -0
- package/lib/emails/password-reset.js.map +1 -0
- package/lib/emails/primitives.d.ts +94 -0
- package/lib/emails/primitives.d.ts.map +1 -0
- package/lib/emails/primitives.js +233 -0
- package/lib/emails/primitives.js.map +1 -0
- package/lib/emails/theme.d.ts +89 -0
- package/lib/emails/theme.d.ts.map +1 -0
- package/lib/emails/theme.js +93 -0
- package/lib/emails/theme.js.map +1 -0
- package/lib/emails/tokens.d.ts +65 -0
- package/lib/emails/tokens.d.ts.map +1 -0
- package/lib/emails/tokens.js +46 -0
- package/lib/emails/tokens.js.map +1 -0
- package/lib/emails/welcome.d.ts +8 -0
- package/lib/emails/welcome.d.ts.map +1 -0
- package/lib/emails/welcome.js +32 -0
- package/lib/emails/welcome.js.map +1 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.js +8 -0
- package/lib/mailer.d.ts +89 -0
- package/lib/mailer.d.ts.map +1 -0
- package/lib/mailer.js +85 -0
- package/lib/mailer.js.map +1 -0
- package/lib/node_modules/@react-email/tailwind/dist/index.d.ts +9 -0
- package/lib/node_modules/@react-email/tailwind/dist/index.d.ts.map +1 -0
- package/lib/node_modules/tailwindcss/dist/colors.d.ts +349 -0
- package/lib/node_modules/tailwindcss/dist/colors.d.ts.map +1 -0
- package/lib/node_modules/tailwindcss/dist/lib.d.ts +6 -0
- package/lib/node_modules/tailwindcss/dist/lib.d.ts.map +1 -0
- package/lib/node_modules/tailwindcss/dist/resolve-config-QUZ9b-Gn.d.ts +29 -0
- package/lib/node_modules/tailwindcss/dist/resolve-config-QUZ9b-Gn.d.ts.map +1 -0
- package/lib/node_modules/tailwindcss/dist/types-DWdTiksJ.d.ts +126 -0
- package/lib/node_modules/tailwindcss/dist/types-DWdTiksJ.d.ts.map +1 -0
- package/lib/registry.d.ts +25 -0
- package/lib/registry.d.ts.map +1 -0
- package/lib/registry.js +26 -0
- package/lib/registry.js.map +1 -0
- package/lib/render.d.ts +13 -0
- package/lib/render.d.ts.map +1 -0
- package/lib/render.js +29 -0
- package/lib/render.js.map +1 -0
- package/lib/schemas.d.ts +86 -0
- package/lib/schemas.d.ts.map +1 -0
- package/lib/schemas.js +104 -0
- package/lib/schemas.js.map +1 -0
- package/lib/send/index.d.ts +4 -0
- package/lib/send/index.js +3 -0
- package/lib/send/resend-sender.d.ts +14 -0
- package/lib/send/resend-sender.d.ts.map +1 -0
- package/lib/send/resend-sender.js +76 -0
- package/lib/send/resend-sender.js.map +1 -0
- package/lib/send/send-email.d.ts +19 -0
- package/lib/send/send-email.d.ts.map +1 -0
- package/lib/send/send-email.js +67 -0
- package/lib/send/send-email.js.map +1 -0
- package/lib/send/sender.d.ts +58 -0
- package/lib/send/sender.d.ts.map +1 -0
- package/lib/send/sender.js +0 -0
- package/lib/suite.d.ts +130 -0
- package/lib/suite.d.ts.map +1 -0
- package/lib/suite.js +28 -0
- package/lib/suite.js.map +1 -0
- package/lib/templates.d.ts +131 -0
- package/lib/templates.d.ts.map +1 -0
- package/lib/templates.js +98 -0
- package/lib/templates.js.map +1 -0
- package/package.json +95 -0
package/README.md
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Copyright 2026 ResQ Systems, Inc.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
-->
|
|
16
|
+
|
|
17
|
+
# @resq-systems/email-templates
|
|
18
|
+
|
|
19
|
+
Type-safe transactional email templates for React apps and backend pipelines.
|
|
20
|
+
|
|
21
|
+
- **One contract.** Every email is a validated `{ name, to, data }` payload, described by an [Effect Schema](https://effect.website) discriminated union. Nothing else can be enqueued or rendered.
|
|
22
|
+
- **React Email components.** Templates are React components built on [React Email](https://react.email) with a `<Tailwind>` theme mapped to the dark-first ResQ Systems brand — red primary, Syne/DM Sans/DM Mono — with oklch tokens converted to email-safe hex.
|
|
23
|
+
- **Headless render.** `renderEmail(payload)` returns `{ to, subject, html, text }` with no DOM — safe to call from queue workers, cron jobs, and other pipelines.
|
|
24
|
+
- **Pluggable sending.** A provider-agnostic `EmailSender` port with an optional Resend adapter under `@resq-systems/email-templates/send`.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
bun add @resq-systems/email-templates effect react react-dom
|
|
30
|
+
# only if you use the ./send Resend adapter:
|
|
31
|
+
bun add resend
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`effect`, `react`, and `react-dom` are peer dependencies; `resend` is an optional peer needed only for `./send`.
|
|
35
|
+
|
|
36
|
+
## Subpaths
|
|
37
|
+
|
|
38
|
+
| Import | Contents | Runtime |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `@resq-systems/email-templates` | `EmailPayload` schema, types, `decodeEmailPayload`, `registry`, `renderEmail` | browser + server |
|
|
41
|
+
| `@resq-systems/email-templates/emails` | `Email` primitives, `emailColors`, and the template components | browser + server |
|
|
42
|
+
| `@resq-systems/email-templates/send` | `EmailSender` port, `createResendSender`, `sendEmail` | **server only** |
|
|
43
|
+
|
|
44
|
+
## Pipeline / worker usage
|
|
45
|
+
|
|
46
|
+
Validate an untrusted payload, render it, and send it:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { renderEmail } from "@resq-systems/email-templates";
|
|
50
|
+
import { createResendSender, sendEmail } from "@resq-systems/email-templates/send";
|
|
51
|
+
|
|
52
|
+
const sender = createResendSender(); // reads RESEND_API_KEY
|
|
53
|
+
|
|
54
|
+
// One-shot: render + send
|
|
55
|
+
const result = await sendEmail(
|
|
56
|
+
sender,
|
|
57
|
+
{ name: "otp", to: "user@example.com", data: { code: "123456", firstName: "Ada" } },
|
|
58
|
+
{ from: "ResQ Systems <noreply@resq.example>" },
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (!result.ok) {
|
|
62
|
+
// result.error = { name, message }
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Or render now and hand the HTML to any transport:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { renderEmail } from "@resq-systems/email-templates";
|
|
70
|
+
|
|
71
|
+
const { subject, html, text } = await renderEmail({
|
|
72
|
+
name: "password-reset",
|
|
73
|
+
to: "user@example.com",
|
|
74
|
+
data: { firstName: "Ada", resetUrl: "https://app.resq.example/reset?token=…" },
|
|
75
|
+
});
|
|
76
|
+
// pass subject/html/text to SES, Postmark, Nodemailer, etc.
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The payload is validated at the boundary — an unknown `name` or missing required `data` throws `EmailValidationError`.
|
|
80
|
+
|
|
81
|
+
## React app usage
|
|
82
|
+
|
|
83
|
+
Import a template component directly (for in-app previews or your own rendering):
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { WelcomeEmail } from "@resq-systems/email-templates/emails";
|
|
87
|
+
|
|
88
|
+
<WelcomeEmail firstName="Ada" verifyUrl="https://app.resq.example/verify" />;
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Templates
|
|
92
|
+
|
|
93
|
+
| `name` | `data` |
|
|
94
|
+
| --- | --- |
|
|
95
|
+
| `otp` | `code`, `firstName?`, `expiresInMinutes?` |
|
|
96
|
+
| `welcome` | `firstName`, `verifyUrl?` |
|
|
97
|
+
| `password-reset` | `firstName?`, `resetUrl`, `expiresInMinutes?` |
|
|
98
|
+
| `notification` | `title`, `body`, `severity?`, `actionUrl?`, `actionLabel?` |
|
|
99
|
+
| `incident-alert` | `incidentId`, `title`, `severity`, `summary`, `location?`, `detectedAt?`, `dashboardUrl` |
|
|
100
|
+
|
|
101
|
+
## Theming
|
|
102
|
+
|
|
103
|
+
Templates render in the dark-first ResQ Systems brand by default, sourced from
|
|
104
|
+
[`@resq-systems/constants`](../constants)`/tokens`. Rebrand any render without forking:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const { html } = await renderEmail(payload, {
|
|
108
|
+
theme: { colors: { primary: "#0ea5e9" }, fontsHref: null },
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Or wrap React usage in a provider:
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import { EmailThemeContext, mergeEmailTheme } from "@resq-systems/email-templates";
|
|
116
|
+
|
|
117
|
+
<EmailThemeContext.Provider value={mergeEmailTheme({ colors: { primary: "#0ea5e9" } })}>
|
|
118
|
+
<WelcomeEmail firstName="Ada" />
|
|
119
|
+
</EmailThemeContext.Provider>;
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`colors` map to Tailwind `theme.extend.colors`; unset keys fall back to the ResQ Systems
|
|
123
|
+
defaults. Change the brand for every app at once by editing `@resq-systems/constants`.
|
|
124
|
+
|
|
125
|
+
## Custom template suites
|
|
126
|
+
|
|
127
|
+
Compose your own typed `{ name, to, data }` contract — spread the built-in
|
|
128
|
+
`resqEmailTemplates` and add your own, or start fresh:
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
import { createMailer, defineEmailTemplate, Email, resqEmailTemplates } from "@resq-systems/email-templates";
|
|
132
|
+
import { Schema } from "effect";
|
|
133
|
+
|
|
134
|
+
const shiftReminder = defineEmailTemplate({
|
|
135
|
+
name: "shift-reminder",
|
|
136
|
+
data: Schema.Struct({ operator: Schema.NonEmptyString, startsAt: Schema.String }),
|
|
137
|
+
subject: (d) => `Shift at ${d.startsAt}`,
|
|
138
|
+
Component: (d) => (
|
|
139
|
+
<Email.Shell preview="Your shift">
|
|
140
|
+
<Email.Title>Hi {d.operator}</Email.Title>
|
|
141
|
+
<Email.Paragraph>Your shift starts at {d.startsAt}.</Email.Paragraph>
|
|
142
|
+
</Email.Shell>
|
|
143
|
+
),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const mailer = createMailer([...resqEmailTemplates, shiftReminder]);
|
|
147
|
+
const { html, text } = await mailer.renderEmail({
|
|
148
|
+
name: "shift-reminder",
|
|
149
|
+
to: "op@resq.software",
|
|
150
|
+
data: { operator: "Ada", startsAt: "18:00" },
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`mailer` exposes `{ schema, registry, names, decode, renderEmail }`, each fully
|
|
155
|
+
typed over your template set — unknown names and bad `data` are rejected at decode.
|
|
156
|
+
|
|
157
|
+
## Adding a built-in template
|
|
158
|
+
|
|
159
|
+
1. Add the `data` schema (and its inferred type) in [`src/schemas.ts`](src/schemas.ts).
|
|
160
|
+
2. Create the component in `src/emails/<name>.tsx` from `Email.*` primitives.
|
|
161
|
+
3. Register it in [`src/templates.tsx`](src/templates.tsx) via `defineEmailTemplate`, then add it to `resqEmailTemplates`.
|
|
162
|
+
4. Export the component from `src/emails/index.ts` and add a preview in `emails/<name>.tsx`.
|
|
163
|
+
|
|
164
|
+
## Custom sender
|
|
165
|
+
|
|
166
|
+
Implement the `EmailSender` port to use any provider:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import type { EmailSender } from "@resq-systems/email-templates/send";
|
|
170
|
+
|
|
171
|
+
export const sesSender: EmailSender = {
|
|
172
|
+
async send({ from, to, subject, html, text }) {
|
|
173
|
+
// call your provider, then:
|
|
174
|
+
return { ok: true, id: "…" };
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Preview
|
|
180
|
+
|
|
181
|
+
```sh
|
|
182
|
+
bun --filter @resq-systems/email-templates email:dev # http://localhost:3000
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Runtime support
|
|
186
|
+
|
|
187
|
+
`renderEmail()` runs in **Node and Bun** (it uses `react-dom/server`). It does **not** run on Cloudflare Workers / `workerd` — `@react-email/render` resolves to its Node build there and throws at runtime (OpenNext #1205). For edge/Workers delivery, pre-render at build time (`bun --filter @resq-systems/email-templates email:export`) or render in a Node/Bun pipeline and ship the resulting HTML/text.
|
|
188
|
+
|
|
189
|
+
Sender config follows the ResQ Systems convention: `RESEND_API_KEY` (required) and `RESEND_FROM` (verified sender, e.g. `ResQ Systems <updates@send.resq.software>`).
|
|
190
|
+
|
|
191
|
+
## Email-client safety
|
|
192
|
+
|
|
193
|
+
Email clients drop `oklch()`, `color-mix()`, and CSS custom properties, and many ignore `rem` and flex/grid. Templates therefore:
|
|
194
|
+
|
|
195
|
+
- use the hex `emailColors` snapshot (keep it in sync with the oklch design tokens);
|
|
196
|
+
- pass `pixelBasedPreset` to `<Tailwind>` so utilities emit `px`;
|
|
197
|
+
- avoid responsive prefixes and flex/grid — use `Section` for layout.
|
|
198
|
+
|
|
199
|
+
## Prerequisites
|
|
200
|
+
|
|
201
|
+
- **Runtime**: Bun 1.1+ or Node.js 20+
|
|
202
|
+
- **Peer Dependencies**: `react`, `react-dom` (v19+ recommended)
|
|
203
|
+
- **Services**: Requires a Resend API key for direct delivery.
|
|
204
|
+
|
|
205
|
+
## Configuration
|
|
206
|
+
|
|
207
|
+
- **Resend Token**: Supply `RESEND_API_KEY` to the environment.
|
|
208
|
+
- **SMTP Transport**: If not using Resend, implement a custom `EmailSender` (e.g., using Nodemailer or SES).
|
|
209
|
+
|
|
210
|
+
## Testing
|
|
211
|
+
|
|
212
|
+
```sh
|
|
213
|
+
bun --filter @resq-systems/email-templates test
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Coverage includes behavioural tests (contract validation, subject/link presence,
|
|
217
|
+
theme overrides) plus **HTML/text regression snapshots** for every built-in
|
|
218
|
+
template in [`tests/snapshots.test.ts`](tests/snapshots.test.ts). Because
|
|
219
|
+
`renderEmail()` is deterministic for fixed input, the committed
|
|
220
|
+
`tests/__snapshots__` baseline catches unintended changes to markup, inline
|
|
221
|
+
styles, or the plaintext fallback. When a change is intentional, re-baseline and
|
|
222
|
+
review the diff:
|
|
223
|
+
|
|
224
|
+
```sh
|
|
225
|
+
bun --filter @resq-systems/email-templates test -u
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Snapshots are the email-appropriate substitute for Storybook/Chromatic here:
|
|
229
|
+
React Email renders to client-accurate HTML (tables + inline styles), which a
|
|
230
|
+
browser-oriented Storybook would not represent faithfully. For a live, mail-client
|
|
231
|
+
preview during development, use `email:dev` (see [Preview](#preview)).
|
|
232
|
+
|
|
233
|
+
## Troubleshooting
|
|
234
|
+
|
|
235
|
+
- **Rendering Failures**: Next.js Server Components might struggle with client-side React Email components. Render mailers asynchronously on the server.
|
|
236
|
+
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { EmailValidationError } from "./mailer.js";
|
|
2
|
+
import { resqMailer } from "./suite.js";
|
|
3
|
+
//#region src/contract.d.ts
|
|
4
|
+
/** Effect Schema union for every built-in ResQ Systems email `{ name, to, data }`. */
|
|
5
|
+
declare const EmailPayload: import("effect/Schema").Top;
|
|
6
|
+
/** The validated payload type for the built-in ResQ Systems templates. */
|
|
7
|
+
type EmailPayload = ReturnType<typeof resqMailer.decode>;
|
|
8
|
+
/** Every built-in template name (the union discriminant). */
|
|
9
|
+
type EmailName = EmailPayload["name"];
|
|
10
|
+
/** The `data` shape for a given built-in template name. */
|
|
11
|
+
type EmailTemplateData<N extends EmailName> = Extract<EmailPayload, {
|
|
12
|
+
name: N;
|
|
13
|
+
}>["data"];
|
|
14
|
+
/**
|
|
15
|
+
* Validate an untrusted `{ name, to, data }` payload at the system boundary.
|
|
16
|
+
* Throws {@link EmailValidationError} on failure.
|
|
17
|
+
*/
|
|
18
|
+
declare const decodeEmailPayload: (input: unknown) => {
|
|
19
|
+
readonly name: "incident-alert";
|
|
20
|
+
readonly to: string;
|
|
21
|
+
readonly data: {
|
|
22
|
+
readonly incidentId: string;
|
|
23
|
+
readonly title: string;
|
|
24
|
+
readonly severity: "critical" | "info" | "warning";
|
|
25
|
+
readonly summary: string;
|
|
26
|
+
readonly location?: string | undefined;
|
|
27
|
+
readonly detectedAt?: string | undefined;
|
|
28
|
+
readonly dashboardUrl: string;
|
|
29
|
+
};
|
|
30
|
+
readonly category?: "transactional" | "marketing";
|
|
31
|
+
readonly unsubscribeUrl?: string;
|
|
32
|
+
} | {
|
|
33
|
+
readonly name: "mission-approval";
|
|
34
|
+
readonly to: string;
|
|
35
|
+
readonly data: {
|
|
36
|
+
readonly missionId: string;
|
|
37
|
+
readonly title: string;
|
|
38
|
+
readonly summary?: string | undefined;
|
|
39
|
+
readonly requestedBy?: string | undefined;
|
|
40
|
+
readonly severity?: "critical" | "info" | "warning" | undefined;
|
|
41
|
+
readonly approveUrl: string;
|
|
42
|
+
readonly expiresInMinutes?: number | undefined;
|
|
43
|
+
};
|
|
44
|
+
readonly category?: "transactional" | "marketing";
|
|
45
|
+
readonly unsubscribeUrl?: string;
|
|
46
|
+
} | {
|
|
47
|
+
readonly name: "new-device-login";
|
|
48
|
+
readonly to: string;
|
|
49
|
+
readonly data: {
|
|
50
|
+
readonly firstName?: string | undefined;
|
|
51
|
+
readonly device?: string | undefined;
|
|
52
|
+
readonly location?: string | undefined;
|
|
53
|
+
readonly ipAddress?: string | undefined;
|
|
54
|
+
readonly at?: string | undefined;
|
|
55
|
+
readonly secureAccountUrl?: string | undefined;
|
|
56
|
+
};
|
|
57
|
+
readonly category?: "transactional" | "marketing";
|
|
58
|
+
readonly unsubscribeUrl?: string;
|
|
59
|
+
} | {
|
|
60
|
+
readonly name: "notification";
|
|
61
|
+
readonly to: string;
|
|
62
|
+
readonly data: {
|
|
63
|
+
readonly title: string;
|
|
64
|
+
readonly body: string;
|
|
65
|
+
readonly severity?: "error" | "info" | "success" | "warning" | undefined;
|
|
66
|
+
readonly actionUrl?: string | undefined;
|
|
67
|
+
readonly actionLabel?: string | undefined;
|
|
68
|
+
};
|
|
69
|
+
readonly category?: "transactional" | "marketing";
|
|
70
|
+
readonly unsubscribeUrl?: string;
|
|
71
|
+
} | {
|
|
72
|
+
readonly name: "org-invitation";
|
|
73
|
+
readonly to: string;
|
|
74
|
+
readonly data: {
|
|
75
|
+
readonly orgName: string;
|
|
76
|
+
readonly inviterName?: string | undefined;
|
|
77
|
+
readonly orgRole?: string | undefined;
|
|
78
|
+
readonly acceptUrl: string;
|
|
79
|
+
readonly expiresInDays?: number | undefined;
|
|
80
|
+
};
|
|
81
|
+
readonly category?: "transactional" | "marketing";
|
|
82
|
+
readonly unsubscribeUrl?: string;
|
|
83
|
+
} | {
|
|
84
|
+
readonly name: "otp";
|
|
85
|
+
readonly to: string;
|
|
86
|
+
readonly data: {
|
|
87
|
+
readonly code: string;
|
|
88
|
+
readonly firstName?: string | undefined;
|
|
89
|
+
readonly expiresInMinutes?: number | undefined;
|
|
90
|
+
};
|
|
91
|
+
readonly category?: "transactional" | "marketing";
|
|
92
|
+
readonly unsubscribeUrl?: string;
|
|
93
|
+
} | {
|
|
94
|
+
readonly name: "password-changed";
|
|
95
|
+
readonly to: string;
|
|
96
|
+
readonly data: {
|
|
97
|
+
readonly firstName?: string | undefined;
|
|
98
|
+
readonly changedAt?: string | undefined;
|
|
99
|
+
readonly secureAccountUrl?: string | undefined;
|
|
100
|
+
};
|
|
101
|
+
readonly category?: "transactional" | "marketing";
|
|
102
|
+
readonly unsubscribeUrl?: string;
|
|
103
|
+
} | {
|
|
104
|
+
readonly name: "password-reset";
|
|
105
|
+
readonly to: string;
|
|
106
|
+
readonly data: {
|
|
107
|
+
readonly firstName?: string | undefined;
|
|
108
|
+
readonly resetUrl: string;
|
|
109
|
+
readonly expiresInMinutes?: number | undefined;
|
|
110
|
+
};
|
|
111
|
+
readonly category?: "transactional" | "marketing";
|
|
112
|
+
readonly unsubscribeUrl?: string;
|
|
113
|
+
} | {
|
|
114
|
+
readonly name: "welcome";
|
|
115
|
+
readonly to: string;
|
|
116
|
+
readonly data: {
|
|
117
|
+
readonly firstName: string;
|
|
118
|
+
readonly verifyUrl?: string | undefined;
|
|
119
|
+
};
|
|
120
|
+
readonly category?: "transactional" | "marketing";
|
|
121
|
+
readonly unsubscribeUrl?: string;
|
|
122
|
+
};
|
|
123
|
+
//#endregion
|
|
124
|
+
export { EmailName, EmailPayload, EmailTemplateData, EmailValidationError, decodeEmailPayload };
|
|
125
|
+
//# sourceMappingURL=contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.d.ts","names":[],"sources":["../src/contract.ts"],"mappings":";;;;cAqBa,sCAAY;;KAGb,eAAe,kBAAkB,WAAW;;KAG5C,YAAY;;KAGZ,kBAAkB,UAAU,aAAa,QAAQ;EAAgB,MAAM;;;;;;cAMtE,qBAAkB"}
|
package/lib/contract.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { EmailValidationError } from "./mailer.js";
|
|
2
|
+
import { resqMailer } from "./suite.js";
|
|
3
|
+
//#region src/contract.ts
|
|
4
|
+
/**
|
|
5
|
+
* Copyright 2026 ResQ Systems, Inc.
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
* you may not use this file except in compliance with the License.
|
|
9
|
+
* You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/
|
|
19
|
+
/** Effect Schema union for every built-in ResQ Systems email `{ name, to, data }`. */
|
|
20
|
+
const EmailPayload = resqMailer.schema;
|
|
21
|
+
/**
|
|
22
|
+
* Validate an untrusted `{ name, to, data }` payload at the system boundary.
|
|
23
|
+
* Throws {@link EmailValidationError} on failure.
|
|
24
|
+
*/
|
|
25
|
+
const decodeEmailPayload = resqMailer.decode;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { EmailPayload, EmailValidationError, decodeEmailPayload };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","names":[],"sources":["../src/contract.ts"],"sourcesContent":["/**\n * Copyright 2026 ResQ Systems, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { resqMailer } from \"./suite.js\";\n\nexport { EmailValidationError } from \"./mailer.js\";\n\n/** Effect Schema union for every built-in ResQ Systems email `{ name, to, data }`. */\nexport const EmailPayload = resqMailer.schema;\n\n/** The validated payload type for the built-in ResQ Systems templates. */\nexport type EmailPayload = ReturnType<typeof resqMailer.decode>;\n\n/** Every built-in template name (the union discriminant). */\nexport type EmailName = EmailPayload[\"name\"];\n\n/** The `data` shape for a given built-in template name. */\nexport type EmailTemplateData<N extends EmailName> = Extract<EmailPayload, { name: N }>[\"data\"];\n\n/**\n * Validate an untrusted `{ name, to, data }` payload at the system boundary.\n * Throws {@link EmailValidationError} on failure.\n */\nexport const decodeEmailPayload = resqMailer.decode;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqBA,MAAa,eAAe,WAAW;;;;;AAevC,MAAa,qBAAqB,WAAW"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { IncidentAlertData } from "../schemas.js";
|
|
2
|
+
//#region src/emails/incident-alert.d.ts
|
|
3
|
+
type IncidentAlertEmailProps = IncidentAlertData;
|
|
4
|
+
/** Incident / dispatch alert for ResQ Systems disaster-response operators. */
|
|
5
|
+
declare function IncidentAlertEmail({ incidentId, title, severity, summary, location, detectedAt, dashboardUrl }: IncidentAlertEmailProps): import("react").JSX.Element;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { IncidentAlertEmail, IncidentAlertEmailProps };
|
|
8
|
+
//# sourceMappingURL=incident-alert.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"incident-alert.d.ts","names":[],"sources":["../../src/emails/incident-alert.tsx"],"mappings":";;KAmBY,0BAA0B;;iBAStB,qBACf,YACA,OACA,UACA,SACA,UACA,YACA,gBACE,0CAAuB,IAAA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Email } from "./primitives.js";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
//#region src/emails/incident-alert.tsx
|
|
4
|
+
const severityClass = {
|
|
5
|
+
info: "text-info",
|
|
6
|
+
warning: "text-warning",
|
|
7
|
+
critical: "text-danger"
|
|
8
|
+
};
|
|
9
|
+
/** Incident / dispatch alert for ResQ Systems disaster-response operators. */
|
|
10
|
+
function IncidentAlertEmail({ incidentId, title, severity = "warning", summary, location, detectedAt, dashboardUrl = "https://app.resq.software/incidents" }) {
|
|
11
|
+
const meta = [
|
|
12
|
+
`Incident ${incidentId}`,
|
|
13
|
+
location,
|
|
14
|
+
detectedAt
|
|
15
|
+
].filter(Boolean).join(" · ");
|
|
16
|
+
return /* @__PURE__ */ jsxs(Email.Shell, {
|
|
17
|
+
preview: `[${severity.toUpperCase()}] ${title}`,
|
|
18
|
+
children: [
|
|
19
|
+
/* @__PURE__ */ jsx(Email.Header, {}),
|
|
20
|
+
/* @__PURE__ */ jsxs(Email.Text, {
|
|
21
|
+
className: `mb-2 font-mono text-xs font-medium uppercase tracking-wide ${severityClass[severity]}`,
|
|
22
|
+
children: [severity, " incident"]
|
|
23
|
+
}),
|
|
24
|
+
/* @__PURE__ */ jsx(Email.Title, { children: title }),
|
|
25
|
+
/* @__PURE__ */ jsx(Email.Paragraph, { children: summary }),
|
|
26
|
+
/* @__PURE__ */ jsx(Email.Text, {
|
|
27
|
+
className: "mb-2 font-mono text-xs uppercase tracking-wide text-muted",
|
|
28
|
+
children: meta
|
|
29
|
+
}),
|
|
30
|
+
/* @__PURE__ */ jsx(Email.CTA, {
|
|
31
|
+
href: dashboardUrl,
|
|
32
|
+
children: "Open incident dashboard"
|
|
33
|
+
}),
|
|
34
|
+
/* @__PURE__ */ jsx(Email.LegalFooter, {
|
|
35
|
+
category: "transactional",
|
|
36
|
+
reason: "You are receiving this alert because you are on-call for ResQ Systems disaster response."
|
|
37
|
+
})
|
|
38
|
+
]
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { IncidentAlertEmail };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=incident-alert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"incident-alert.js","names":[],"sources":["../../src/emails/incident-alert.tsx"],"sourcesContent":["/**\n * Copyright 2026 ResQ Systems, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { IncidentAlertData } from \"../schemas.js\";\nimport { Email } from \"./primitives.js\";\n\nexport type IncidentAlertEmailProps = IncidentAlertData;\n\nconst severityClass: Record<\"info\" | \"warning\" | \"critical\", string> = {\n\tinfo: \"text-info\",\n\twarning: \"text-warning\",\n\tcritical: \"text-danger\",\n};\n\n/** Incident / dispatch alert for ResQ Systems disaster-response operators. */\nexport function IncidentAlertEmail({\n\tincidentId,\n\ttitle,\n\tseverity = \"warning\",\n\tsummary,\n\tlocation,\n\tdetectedAt,\n\tdashboardUrl = \"https://app.resq.software/incidents\",\n}: IncidentAlertEmailProps) {\n\tconst meta = [`Incident ${incidentId}`, location, detectedAt].filter(Boolean).join(\" · \");\n\n\treturn (\n\t\t<Email.Shell preview={`[${severity.toUpperCase()}] ${title}`}>\n\t\t\t<Email.Header />\n\t\t\t<Email.Text\n\t\t\t\tclassName={`mb-2 font-mono text-xs font-medium uppercase tracking-wide ${severityClass[severity]}`}\n\t\t\t>\n\t\t\t\t{severity} incident\n\t\t\t</Email.Text>\n\t\t\t<Email.Title>{title}</Email.Title>\n\t\t\t<Email.Paragraph>{summary}</Email.Paragraph>\n\t\t\t<Email.Text className=\"mb-2 font-mono text-xs uppercase tracking-wide text-muted\">\n\t\t\t\t{meta}\n\t\t\t</Email.Text>\n\t\t\t<Email.CTA href={dashboardUrl}>Open incident dashboard</Email.CTA>\n\t\t\t<Email.LegalFooter\n\t\t\t\tcategory=\"transactional\"\n\t\t\t\treason=\"You are receiving this alert because you are on-call for ResQ Systems disaster response.\"\n\t\t\t/>\n\t\t</Email.Shell>\n\t);\n}\n"],"mappings":";;;AAqBA,MAAM,gBAAiE;CACtE,MAAM;CACN,SAAS;CACT,UAAU;AACX;;AAGA,SAAgB,mBAAmB,EAClC,YACA,OACA,WAAW,WACX,SACA,UACA,YACA,eAAe,yCACY;CAC3B,MAAM,OAAO;EAAC,YAAY;EAAc;EAAU;CAAU,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,KAAK;CAExF,OACC,qBAAC,MAAM,OAAP;EAAa,SAAS,IAAI,SAAS,YAAY,EAAE,IAAI;YAArD;GACC,oBAAC,MAAM,QAAP,CAAe,CAAA;GACf,qBAAC,MAAM,MAAP;IACC,WAAW,8DAA8D,cAAc;cADxF,CAGE,UAAS,WACC;;GACZ,oBAAC,MAAM,OAAP,EAAA,UAAc,MAAmB,CAAA;GACjC,oBAAC,MAAM,WAAP,EAAA,UAAkB,QAAyB,CAAA;GAC3C,oBAAC,MAAM,MAAP;IAAY,WAAU;cACpB;GACU,CAAA;GACZ,oBAAC,MAAM,KAAP;IAAW,MAAM;cAAc;GAAkC,CAAA;GACjE,oBAAC,MAAM,aAAP;IACC,UAAS;IACT,QAAO;GACP,CAAA;EACW;;AAEf"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { EmailMessage, EmailMessageContext, EmailOrgIdentity, EmailTheme, EmailThemeContext, EmailThemeFonts, EmailThemeOverride, buildTailwindConfig, defaultEmailTheme, mergeEmailTheme, resolveEmailTheme, withEmailMessage, withEmailTheme } from "./theme.js";
|
|
2
|
+
import { IncidentAlertEmail, IncidentAlertEmailProps } from "./incident-alert.js";
|
|
3
|
+
import { EmailColorToken, emailColors, emailFonts, emailOrg } from "./tokens.js";
|
|
4
|
+
import { Email } from "./primitives.js";
|
|
5
|
+
import { OtpEmail, OtpEmailProps } from "./otp.js";
|
|
6
|
+
import { WelcomeEmail, WelcomeEmailProps } from "./welcome.js";
|
|
7
|
+
import { PasswordResetEmail, PasswordResetEmailProps } from "./password-reset.js";
|
|
8
|
+
import { NotificationEmail, NotificationEmailProps } from "./notification.js";
|
|
9
|
+
import { PasswordChangedEmail, PasswordChangedEmailProps } from "./password-changed.js";
|
|
10
|
+
import { NewDeviceLoginEmail, NewDeviceLoginEmailProps } from "./new-device-login.js";
|
|
11
|
+
import { MissionApprovalEmail, MissionApprovalEmailProps } from "./mission-approval.js";
|
|
12
|
+
import { OrgInvitationEmail, OrgInvitationEmailProps } from "./org-invitation.js";
|
|
13
|
+
export { Email, EmailColorToken, EmailMessage, EmailMessageContext, EmailOrgIdentity, EmailTheme, EmailThemeContext, EmailThemeFonts, EmailThemeOverride, IncidentAlertEmail, IncidentAlertEmailProps, MissionApprovalEmail, MissionApprovalEmailProps, NewDeviceLoginEmail, NewDeviceLoginEmailProps, NotificationEmail, NotificationEmailProps, OrgInvitationEmail, OrgInvitationEmailProps, OtpEmail, OtpEmailProps, PasswordChangedEmail, PasswordChangedEmailProps, PasswordResetEmail, PasswordResetEmailProps, WelcomeEmail, WelcomeEmailProps, buildTailwindConfig, defaultEmailTheme, emailColors, emailFonts, emailOrg, mergeEmailTheme, resolveEmailTheme, withEmailMessage, withEmailTheme };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { emailColors, emailFonts, emailOrg } from "./tokens.js";
|
|
2
|
+
import { EmailMessageContext, EmailThemeContext, buildTailwindConfig, defaultEmailTheme, mergeEmailTheme, resolveEmailTheme, withEmailMessage, withEmailTheme } from "./theme.js";
|
|
3
|
+
import { Email } from "./primitives.js";
|
|
4
|
+
import { IncidentAlertEmail } from "./incident-alert.js";
|
|
5
|
+
import { MissionApprovalEmail } from "./mission-approval.js";
|
|
6
|
+
import { NewDeviceLoginEmail } from "./new-device-login.js";
|
|
7
|
+
import { NotificationEmail } from "./notification.js";
|
|
8
|
+
import { OrgInvitationEmail } from "./org-invitation.js";
|
|
9
|
+
import { OtpEmail } from "./otp.js";
|
|
10
|
+
import { PasswordChangedEmail } from "./password-changed.js";
|
|
11
|
+
import { PasswordResetEmail } from "./password-reset.js";
|
|
12
|
+
import { WelcomeEmail } from "./welcome.js";
|
|
13
|
+
export { Email, EmailMessageContext, EmailThemeContext, IncidentAlertEmail, MissionApprovalEmail, NewDeviceLoginEmail, NotificationEmail, OrgInvitationEmail, OtpEmail, PasswordChangedEmail, PasswordResetEmail, WelcomeEmail, buildTailwindConfig, defaultEmailTheme, emailColors, emailFonts, emailOrg, mergeEmailTheme, resolveEmailTheme, withEmailMessage, withEmailTheme };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MissionApprovalData } from "../schemas.js";
|
|
2
|
+
//#region src/emails/mission-approval.d.ts
|
|
3
|
+
type MissionApprovalEmailProps = MissionApprovalData;
|
|
4
|
+
/** Approver sign-off request for a ResQ Systems mission / plan execution. */
|
|
5
|
+
declare function MissionApprovalEmail({ missionId, title, summary, requestedBy, severity, approveUrl, expiresInMinutes }: MissionApprovalEmailProps): import("react").JSX.Element;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { MissionApprovalEmail, MissionApprovalEmailProps };
|
|
8
|
+
//# sourceMappingURL=mission-approval.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mission-approval.d.ts","names":[],"sources":["../../src/emails/mission-approval.tsx"],"mappings":";;KAmBY,4BAA4B;;iBASxB,uBACf,WACA,OACA,SACA,aACA,UACA,YACA,oBACE,4CAAyB,IAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Email } from "./primitives.js";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
//#region src/emails/mission-approval.tsx
|
|
4
|
+
const severityClass = {
|
|
5
|
+
info: "text-info",
|
|
6
|
+
warning: "text-warning",
|
|
7
|
+
critical: "text-danger"
|
|
8
|
+
};
|
|
9
|
+
/** Approver sign-off request for a ResQ Systems mission / plan execution. */
|
|
10
|
+
function MissionApprovalEmail({ missionId, title, summary, requestedBy, severity, approveUrl = "https://app.example.com/missions/approve", expiresInMinutes }) {
|
|
11
|
+
const meta = [`Mission ${missionId}`, requestedBy ? `Requested by ${requestedBy}` : null].filter(Boolean).join(" · ");
|
|
12
|
+
return /* @__PURE__ */ jsxs(Email.Shell, {
|
|
13
|
+
preview: `Mission approval needed: ${title}`,
|
|
14
|
+
children: [
|
|
15
|
+
/* @__PURE__ */ jsx(Email.Header, {}),
|
|
16
|
+
severity ? /* @__PURE__ */ jsxs(Email.Text, {
|
|
17
|
+
className: `mb-2 font-mono text-xs font-medium uppercase tracking-wide ${severityClass[severity]}`,
|
|
18
|
+
children: [severity, " priority"]
|
|
19
|
+
}) : null,
|
|
20
|
+
/* @__PURE__ */ jsx(Email.Title, { children: title }),
|
|
21
|
+
/* @__PURE__ */ jsx(Email.Paragraph, { children: "A mission needs your sign-off before it can execute." }),
|
|
22
|
+
summary ? /* @__PURE__ */ jsx(Email.Paragraph, { children: summary }) : null,
|
|
23
|
+
/* @__PURE__ */ jsx(Email.Text, {
|
|
24
|
+
className: "mb-2 font-mono text-xs uppercase tracking-wide text-muted",
|
|
25
|
+
children: meta
|
|
26
|
+
}),
|
|
27
|
+
/* @__PURE__ */ jsx(Email.CTA, {
|
|
28
|
+
href: approveUrl,
|
|
29
|
+
children: "Review & approve"
|
|
30
|
+
}),
|
|
31
|
+
expiresInMinutes ? /* @__PURE__ */ jsxs(Email.Paragraph, { children: [
|
|
32
|
+
"This approval request expires in ",
|
|
33
|
+
expiresInMinutes,
|
|
34
|
+
" minutes."
|
|
35
|
+
] }) : null,
|
|
36
|
+
/* @__PURE__ */ jsx(Email.LegalFooter, {
|
|
37
|
+
category: "transactional",
|
|
38
|
+
reason: "You are receiving this email because you are an approver for ResQ Systems mission operations."
|
|
39
|
+
})
|
|
40
|
+
]
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { MissionApprovalEmail };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=mission-approval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mission-approval.js","names":[],"sources":["../../src/emails/mission-approval.tsx"],"sourcesContent":["/**\n * Copyright 2026 ResQ Systems, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { MissionApprovalData } from \"../schemas.js\";\nimport { Email } from \"./primitives.js\";\n\nexport type MissionApprovalEmailProps = MissionApprovalData;\n\nconst severityClass: Record<\"info\" | \"warning\" | \"critical\", string> = {\n\tinfo: \"text-info\",\n\twarning: \"text-warning\",\n\tcritical: \"text-danger\",\n};\n\n/** Approver sign-off request for a ResQ Systems mission / plan execution. */\nexport function MissionApprovalEmail({\n\tmissionId,\n\ttitle,\n\tsummary,\n\trequestedBy,\n\tseverity,\n\tapproveUrl = \"https://app.example.com/missions/approve\",\n\texpiresInMinutes,\n}: MissionApprovalEmailProps) {\n\tconst meta = [`Mission ${missionId}`, requestedBy ? `Requested by ${requestedBy}` : null]\n\t\t.filter(Boolean)\n\t\t.join(\" · \");\n\n\treturn (\n\t\t<Email.Shell preview={`Mission approval needed: ${title}`}>\n\t\t\t<Email.Header />\n\t\t\t{severity ? (\n\t\t\t\t<Email.Text\n\t\t\t\t\tclassName={`mb-2 font-mono text-xs font-medium uppercase tracking-wide ${severityClass[severity]}`}\n\t\t\t\t>\n\t\t\t\t\t{severity} priority\n\t\t\t\t</Email.Text>\n\t\t\t) : null}\n\t\t\t<Email.Title>{title}</Email.Title>\n\t\t\t<Email.Paragraph>A mission needs your sign-off before it can execute.</Email.Paragraph>\n\t\t\t{summary ? <Email.Paragraph>{summary}</Email.Paragraph> : null}\n\t\t\t<Email.Text className=\"mb-2 font-mono text-xs uppercase tracking-wide text-muted\">\n\t\t\t\t{meta}\n\t\t\t</Email.Text>\n\t\t\t<Email.CTA href={approveUrl}>Review & approve</Email.CTA>\n\t\t\t{expiresInMinutes ? (\n\t\t\t\t<Email.Paragraph>\n\t\t\t\t\tThis approval request expires in {expiresInMinutes} minutes.\n\t\t\t\t</Email.Paragraph>\n\t\t\t) : null}\n\t\t\t<Email.LegalFooter\n\t\t\t\tcategory=\"transactional\"\n\t\t\t\treason=\"You are receiving this email because you are an approver for ResQ Systems mission operations.\"\n\t\t\t/>\n\t\t</Email.Shell>\n\t);\n}\n"],"mappings":";;;AAqBA,MAAM,gBAAiE;CACtE,MAAM;CACN,SAAS;CACT,UAAU;AACX;;AAGA,SAAgB,qBAAqB,EACpC,WACA,OACA,SACA,aACA,UACA,aAAa,4CACb,oBAC6B;CAC7B,MAAM,OAAO,CAAC,WAAW,aAAa,cAAc,gBAAgB,gBAAgB,IAAI,CAAC,CACvF,OAAO,OAAO,CAAC,CACf,KAAK,KAAK;CAEZ,OACC,qBAAC,MAAM,OAAP;EAAa,SAAS,4BAA4B;YAAlD;GACC,oBAAC,MAAM,QAAP,CAAe,CAAA;GACd,WACA,qBAAC,MAAM,MAAP;IACC,WAAW,8DAA8D,cAAc;cADxF,CAGE,UAAS,WACC;QACT;GACJ,oBAAC,MAAM,OAAP,EAAA,UAAc,MAAmB,CAAA;GACjC,oBAAC,MAAM,WAAP,EAAA,UAAiB,uDAAqE,CAAA;GACrF,UAAU,oBAAC,MAAM,WAAP,EAAA,UAAkB,QAAyB,CAAA,IAAI;GAC1D,oBAAC,MAAM,MAAP;IAAY,WAAU;cACpB;GACU,CAAA;GACZ,oBAAC,MAAM,KAAP;IAAW,MAAM;cAAY;GAA+B,CAAA;GAC3D,mBACA,qBAAC,MAAM,WAAP,EAAA,UAAA;IAAiB;IACkB;IAAiB;GACnC,EAAA,CAAA,IACd;GACJ,oBAAC,MAAM,aAAP;IACC,UAAS;IACT,QAAO;GACP,CAAA;EACW;;AAEf"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { NewDeviceLoginData } from "../schemas.js";
|
|
2
|
+
//#region src/emails/new-device-login.d.ts
|
|
3
|
+
type NewDeviceLoginEmailProps = NewDeviceLoginData;
|
|
4
|
+
/** Security alert: a new device or location signed in to the account. */
|
|
5
|
+
declare function NewDeviceLoginEmail({ firstName, device, location, ipAddress, at, secureAccountUrl }: NewDeviceLoginEmailProps): import("react").JSX.Element;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { NewDeviceLoginEmail, NewDeviceLoginEmailProps };
|
|
8
|
+
//# sourceMappingURL=new-device-login.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"new-device-login.d.ts","names":[],"sources":["../../src/emails/new-device-login.tsx"],"mappings":";;KAmBY,2BAA2B;;iBAGvB,sBACf,WACA,QACA,UACA,WACA,IACA,oBACE,2CAAwB,IAAA"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Email } from "./primitives.js";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
//#region src/emails/new-device-login.tsx
|
|
4
|
+
/** Security alert: a new device or location signed in to the account. */
|
|
5
|
+
function NewDeviceLoginEmail({ firstName, device, location, ipAddress, at, secureAccountUrl }) {
|
|
6
|
+
const meta = [
|
|
7
|
+
device,
|
|
8
|
+
location,
|
|
9
|
+
ipAddress,
|
|
10
|
+
at
|
|
11
|
+
].filter(Boolean).join(" · ");
|
|
12
|
+
return /* @__PURE__ */ jsxs(Email.Shell, {
|
|
13
|
+
preview: "New sign-in to your ResQ Systems account",
|
|
14
|
+
children: [
|
|
15
|
+
/* @__PURE__ */ jsx(Email.Header, {}),
|
|
16
|
+
/* @__PURE__ */ jsx(Email.Title, { children: "New sign-in to your account" }),
|
|
17
|
+
firstName ? /* @__PURE__ */ jsxs(Email.Paragraph, { children: [
|
|
18
|
+
"Hi ",
|
|
19
|
+
firstName,
|
|
20
|
+
","
|
|
21
|
+
] }) : null,
|
|
22
|
+
/* @__PURE__ */ jsx(Email.Paragraph, { children: "We noticed a new sign-in to your ResQ Systems account." }),
|
|
23
|
+
meta ? /* @__PURE__ */ jsx(Email.Text, {
|
|
24
|
+
className: "mb-4 font-mono text-xs uppercase tracking-wide text-muted",
|
|
25
|
+
children: meta
|
|
26
|
+
}) : null,
|
|
27
|
+
/* @__PURE__ */ jsxs(Email.Paragraph, { children: [
|
|
28
|
+
"If this was you, no action is needed.",
|
|
29
|
+
" ",
|
|
30
|
+
secureAccountUrl ? "If you don't recognize this sign-in, secure your account now." : "If you don't recognize this sign-in, please contact support immediately."
|
|
31
|
+
] }),
|
|
32
|
+
secureAccountUrl ? /* @__PURE__ */ jsx(Email.CTA, {
|
|
33
|
+
href: secureAccountUrl,
|
|
34
|
+
children: "Secure your account"
|
|
35
|
+
}) : null,
|
|
36
|
+
/* @__PURE__ */ jsx(Email.SupportLine, { children: "Don't recognize this sign-in?" }),
|
|
37
|
+
/* @__PURE__ */ jsx(Email.LegalFooter, {
|
|
38
|
+
category: "transactional",
|
|
39
|
+
reason: "You are receiving this email because a new sign-in to your ResQ Systems account was detected."
|
|
40
|
+
})
|
|
41
|
+
]
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { NewDeviceLoginEmail };
|
|
46
|
+
|
|
47
|
+
//# sourceMappingURL=new-device-login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"new-device-login.js","names":[],"sources":["../../src/emails/new-device-login.tsx"],"sourcesContent":["/**\n * Copyright 2026 ResQ Systems, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { NewDeviceLoginData } from \"../schemas.js\";\nimport { Email } from \"./primitives.js\";\n\nexport type NewDeviceLoginEmailProps = NewDeviceLoginData;\n\n/** Security alert: a new device or location signed in to the account. */\nexport function NewDeviceLoginEmail({\n\tfirstName,\n\tdevice,\n\tlocation,\n\tipAddress,\n\tat,\n\tsecureAccountUrl,\n}: NewDeviceLoginEmailProps) {\n\tconst meta = [device, location, ipAddress, at].filter(Boolean).join(\" · \");\n\n\treturn (\n\t\t<Email.Shell preview=\"New sign-in to your ResQ Systems account\">\n\t\t\t<Email.Header />\n\t\t\t<Email.Title>New sign-in to your account</Email.Title>\n\t\t\t{firstName ? <Email.Paragraph>Hi {firstName},</Email.Paragraph> : null}\n\t\t\t<Email.Paragraph>We noticed a new sign-in to your ResQ Systems account.</Email.Paragraph>\n\t\t\t{meta ? (\n\t\t\t\t<Email.Text className=\"mb-4 font-mono text-xs uppercase tracking-wide text-muted\">\n\t\t\t\t\t{meta}\n\t\t\t\t</Email.Text>\n\t\t\t) : null}\n\t\t\t<Email.Paragraph>\n\t\t\t\tIf this was you, no action is needed.{\" \"}\n\t\t\t\t{secureAccountUrl\n\t\t\t\t\t? \"If you don't recognize this sign-in, secure your account now.\"\n\t\t\t\t\t: \"If you don't recognize this sign-in, please contact support immediately.\"}\n\t\t\t</Email.Paragraph>\n\t\t\t{secureAccountUrl ? <Email.CTA href={secureAccountUrl}>Secure your account</Email.CTA> : null}\n\t\t\t<Email.SupportLine>Don't recognize this sign-in?</Email.SupportLine>\n\t\t\t<Email.LegalFooter\n\t\t\t\tcategory=\"transactional\"\n\t\t\t\treason=\"You are receiving this email because a new sign-in to your ResQ Systems account was detected.\"\n\t\t\t/>\n\t\t</Email.Shell>\n\t);\n}\n"],"mappings":";;;;AAsBA,SAAgB,oBAAoB,EACnC,WACA,QACA,UACA,WACA,IACA,oBAC4B;CAC5B,MAAM,OAAO;EAAC;EAAQ;EAAU;EAAW;CAAE,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,KAAK;CAEzE,OACC,qBAAC,MAAM,OAAP;EAAa,SAAQ;YAArB;GACC,oBAAC,MAAM,QAAP,CAAe,CAAA;GACf,oBAAC,MAAM,OAAP,EAAA,UAAa,8BAAwC,CAAA;GACpD,YAAY,qBAAC,MAAM,WAAP,EAAA,UAAA;IAAiB;IAAI;IAAU;GAAkB,EAAA,CAAA,IAAI;GAClE,oBAAC,MAAM,WAAP,EAAA,UAAiB,yDAAuE,CAAA;GACvF,OACA,oBAAC,MAAM,MAAP;IAAY,WAAU;cACpB;GACU,CAAA,IACT;GACJ,qBAAC,MAAM,WAAP,EAAA,UAAA;IAAiB;IACsB;IACrC,mBACE,kEACA;GACa,EAAA,CAAA;GAChB,mBAAmB,oBAAC,MAAM,KAAP;IAAW,MAAM;cAAkB;GAA8B,CAAA,IAAI;GACzF,oBAAC,MAAM,aAAP,EAAA,UAAmB,gCAAgD,CAAA;GACnE,oBAAC,MAAM,aAAP;IACC,UAAS;IACT,QAAO;GACP,CAAA;EACW;;AAEf"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { NotificationData } from "../schemas.js";
|
|
2
|
+
//#region src/emails/notification.d.ts
|
|
3
|
+
type NotificationEmailProps = NotificationData;
|
|
4
|
+
/** Generic notification / alert email with an optional call-to-action. */
|
|
5
|
+
declare function NotificationEmail({ title, body, severity, actionUrl, actionLabel }: NotificationEmailProps): import("react").JSX.Element;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { NotificationEmail, NotificationEmailProps };
|
|
8
|
+
//# sourceMappingURL=notification.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification.d.ts","names":[],"sources":["../../src/emails/notification.tsx"],"mappings":";;KAmBY,yBAAyB;;iBAUrB,oBACf,OACA,MACA,UACA,WACA,eACE,yCAAsB,IAAA"}
|