@fakhyy/retemp 0.1.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 +176 -0
- package/dist/index.d.mts +44 -0
- package/dist/index.mjs +27 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# retemp
|
|
2
|
+
|
|
3
|
+
Typed, lightweight email sending for **Resend** + **React** email templates.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
const email = createEmail({ provider: resend, templates: { ... } });
|
|
7
|
+
|
|
8
|
+
await email.send("welcome", {
|
|
9
|
+
to: "user@example.com",
|
|
10
|
+
props: { name: "Alice" },
|
|
11
|
+
});
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Every `send()` call is **fully type-checked** against your template definitions — template name, props, and subject are all inferred.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
bun add retemp resend react react-dom
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npm install retemp resend react react-dom
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
> **Peer dependencies:** `resend ^6.17.1`, `react ^18 || ^19`, `react-dom ^18 || ^19`, `typescript ^5`.
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { Resend } from "resend";
|
|
32
|
+
import { createEmail, defineTemplates } from "retemp";
|
|
33
|
+
import WelcomeEmail from "./emails/welcome";
|
|
34
|
+
import ResetPassword from "./emails/reset-password";
|
|
35
|
+
|
|
36
|
+
const resend = new Resend(process.env.RESEND_API_KEY);
|
|
37
|
+
|
|
38
|
+
const email = createEmail({
|
|
39
|
+
provider: resend,
|
|
40
|
+
defaults: { from: "Acme <noreply@acme.com>" },
|
|
41
|
+
templates: defineTemplates({
|
|
42
|
+
welcome: {
|
|
43
|
+
component: WelcomeEmail,
|
|
44
|
+
subject: (props) => `Welcome, ${props.name}!`,
|
|
45
|
+
},
|
|
46
|
+
resetPassword: {
|
|
47
|
+
component: ResetPassword,
|
|
48
|
+
subject: "Reset your password",
|
|
49
|
+
},
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await email.send("welcome", {
|
|
54
|
+
to: "alice@example.com",
|
|
55
|
+
props: { name: "Alice" },
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## API
|
|
60
|
+
|
|
61
|
+
### `createEmail(options)`
|
|
62
|
+
|
|
63
|
+
Creates a typed email client.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
function createEmail<TTemplates extends TemplateMap>(
|
|
67
|
+
options: CreateEmailOptions<TTemplates>,
|
|
68
|
+
): { send: SendFunction<TTemplates> };
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
#### Options
|
|
72
|
+
|
|
73
|
+
| Option | Type | Required | Description |
|
|
74
|
+
| ----------- | ------------------------- | -------- | ----------------------------------------- |
|
|
75
|
+
| `provider` | `EmailProvider` | yes | Resend instance (or compatible provider). |
|
|
76
|
+
| `defaults` | `{ from, replyTo? }` | no | Fallback `from` / `replyTo` addresses. |
|
|
77
|
+
| `templates` | `Record<string, TemplateDefinition>` | yes | Map of named templates. |
|
|
78
|
+
|
|
79
|
+
#### `email.send(template, options)`
|
|
80
|
+
|
|
81
|
+
Sends an email using the named template.
|
|
82
|
+
|
|
83
|
+
| Parameter | Type | Required | Description |
|
|
84
|
+
| --------------- | ------------------------ | -------- | ----------------------------------------- |
|
|
85
|
+
| `template` | key of `templates` | yes | Template name. |
|
|
86
|
+
| `to` | `string \| string[]` | yes | Recipient(s). |
|
|
87
|
+
| `props` | `ComponentProps<T>` | yes | Props forwarded to the React component. |
|
|
88
|
+
| `from` | `string` | no | Overrides `defaults.from`. |
|
|
89
|
+
| `replyTo` | `string` | no | Overrides `defaults.replyTo`. |
|
|
90
|
+
| `subject` | `string` | no | Overrides the template's subject. |
|
|
91
|
+
|
|
92
|
+
> Throws if no `from` address is resolved (neither in `defaults` nor per-call).
|
|
93
|
+
|
|
94
|
+
### `defineTemplates(templates)`
|
|
95
|
+
|
|
96
|
+
Identity helper that returns the same object with full type inference. Use it to keep your template definitions colocated and type-safe.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
const templates = defineTemplates({
|
|
100
|
+
confirmSignup: {
|
|
101
|
+
component: ConfirmSignupEmail,
|
|
102
|
+
subject: "Please confirm your email",
|
|
103
|
+
},
|
|
104
|
+
magicLink: {
|
|
105
|
+
component: MagicLinkEmail,
|
|
106
|
+
subject: (props) => `Your sign-in link for ${props.email}`,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### `TemplateDefinition<T>`
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
type TemplateDefinition<T extends EmailComponent> = {
|
|
115
|
+
component: T;
|
|
116
|
+
subject: string | ((props: ComponentProps<T>) => string);
|
|
117
|
+
};
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- **`component`** — A React component that renders your email.
|
|
121
|
+
- **`subject`** — Static string, or a function receiving the component props and returning a dynamic subject line.
|
|
122
|
+
|
|
123
|
+
## How It Works
|
|
124
|
+
|
|
125
|
+
1. You define a map of templates — each pairs a React component with a subject line.
|
|
126
|
+
2. `createEmail` wires the templates to a Resend provider and returns a `send` function.
|
|
127
|
+
3. `send` resolves the `from` address, computes the subject (calling the function if dynamic), renders the component to JSX, and delegates to `resend.emails.send()`.
|
|
128
|
+
|
|
129
|
+
Resend handles the actual rendering (React → HTML) and delivery.
|
|
130
|
+
|
|
131
|
+
## Examples
|
|
132
|
+
|
|
133
|
+
### With a dynamic `from` per call
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
await email.send("welcome", {
|
|
137
|
+
to: "user@example.com",
|
|
138
|
+
from: "Support <support@acme.com>",
|
|
139
|
+
props: { name: "Bob" },
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Multiple recipients
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
await email.send("announcement", {
|
|
147
|
+
to: ["alice@example.com", "bob@example.com"],
|
|
148
|
+
props: { message: "New feature released!" },
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Overriding subject per call
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
await email.send("resetPassword", {
|
|
156
|
+
to: "user@example.com",
|
|
157
|
+
subject: "Your custom subject here",
|
|
158
|
+
props: {},
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Type Safety
|
|
163
|
+
|
|
164
|
+
`retemp` is built for TypeScript. Template names and their prop types are **inferred from your definitions** — the compiler will catch mismatches:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
// ❌ TypeScript error: property "name" is missing in props
|
|
168
|
+
email.send("welcome", { to: "a@b.com", props: {} });
|
|
169
|
+
|
|
170
|
+
// ❌ TypeScript error: "nonexistent" is not a template name
|
|
171
|
+
email.send("nonexistent", { to: "a@b.com", props: {} });
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ComponentProps, ComponentType } from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
type EmailComponent = ComponentType<any>;
|
|
5
|
+
type TemplateDefinition<T extends EmailComponent = EmailComponent> = {
|
|
6
|
+
component: T;
|
|
7
|
+
subject: string | ((props: ComponentProps<T>) => string);
|
|
8
|
+
};
|
|
9
|
+
type TemplateMap = {
|
|
10
|
+
[K: string]: TemplateDefinition<any>;
|
|
11
|
+
};
|
|
12
|
+
interface EmailProvider {
|
|
13
|
+
emails: {
|
|
14
|
+
send: (...args: any[]) => Promise<any>;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
interface CreateEmailOptions<TTemplates extends TemplateMap> {
|
|
18
|
+
provider: EmailProvider;
|
|
19
|
+
defaults?: {
|
|
20
|
+
from: string;
|
|
21
|
+
replyTo?: string;
|
|
22
|
+
};
|
|
23
|
+
templates: TTemplates;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/create-email.d.ts
|
|
27
|
+
declare function createEmail<TTemplates extends TemplateMap>({
|
|
28
|
+
provider,
|
|
29
|
+
defaults,
|
|
30
|
+
templates
|
|
31
|
+
}: CreateEmailOptions<TTemplates>): {
|
|
32
|
+
send: <T extends keyof TTemplates>(template: T, options: {
|
|
33
|
+
to: string | string[];
|
|
34
|
+
props: ComponentProps<TTemplates[T]["component"]>;
|
|
35
|
+
subject?: string;
|
|
36
|
+
from?: string;
|
|
37
|
+
replyTo?: string;
|
|
38
|
+
}) => Promise<any>;
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/define-templates.d.ts
|
|
42
|
+
declare function defineTemplates<T extends TemplateMap>(templates: T): T;
|
|
43
|
+
//#endregion
|
|
44
|
+
export { type CreateEmailOptions, type TemplateDefinition, createEmail, defineTemplates };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
//#region src/create-email.tsx
|
|
3
|
+
function createEmail({ provider, defaults, templates }) {
|
|
4
|
+
async function send(template, options) {
|
|
5
|
+
const definition = templates[template];
|
|
6
|
+
const from = options.from ?? defaults?.from;
|
|
7
|
+
if (!from) throw new Error("No `from` address configured. Provide one in defaults or per send().");
|
|
8
|
+
const replyTo = options.replyTo ?? defaults?.replyTo;
|
|
9
|
+
const subject = options.subject ?? (typeof definition.subject === "function" ? definition.subject(options.props) : definition.subject);
|
|
10
|
+
const Component = definition.component;
|
|
11
|
+
return provider.emails.send({
|
|
12
|
+
from,
|
|
13
|
+
to: options.to,
|
|
14
|
+
replyTo,
|
|
15
|
+
subject,
|
|
16
|
+
react: /* @__PURE__ */ jsx(Component, { ...options.props })
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return { send };
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/define-templates.ts
|
|
23
|
+
function defineTemplates(templates) {
|
|
24
|
+
return templates;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { createEmail, defineTemplates };
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fakhyy/retemp",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "Type-safe email templates for React Email and Resend with automatic prop inference.",
|
|
7
|
+
"types": "./dist/index.d.mts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.mts",
|
|
11
|
+
"import": "./dist/index.mjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsdown",
|
|
19
|
+
"dev": "tsdown --watch",
|
|
20
|
+
"changeset": "changeset",
|
|
21
|
+
"version": "changeset version",
|
|
22
|
+
"release": "bun run build && changeset publish"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@react-email/render": "^2"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@changesets/cli": "^2.31.0",
|
|
29
|
+
"@types/bun": "latest",
|
|
30
|
+
"@types/react": "^19",
|
|
31
|
+
"@types/react-dom": "^19",
|
|
32
|
+
"react": "^18 || ^19",
|
|
33
|
+
"react-dom": "^18 || ^19",
|
|
34
|
+
"tsdown": "^0.22.3",
|
|
35
|
+
"typescript": "^6"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"resend": "^6",
|
|
39
|
+
"react": "^18 || ^19",
|
|
40
|
+
"react-dom": "^18 || ^19"
|
|
41
|
+
},
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/fakhyy/retemp.git"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://retemp.vercel.app",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/fakhyy/retemp/issues"
|
|
49
|
+
},
|
|
50
|
+
"keywords": [
|
|
51
|
+
"resend",
|
|
52
|
+
"react-email",
|
|
53
|
+
"email",
|
|
54
|
+
"typescript",
|
|
55
|
+
"jsx",
|
|
56
|
+
"templates"
|
|
57
|
+
],
|
|
58
|
+
"packageManager": "bun@1.3.14",
|
|
59
|
+
"sideEffects": false
|
|
60
|
+
}
|