@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 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
@@ -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
+ }