@igniter-js/mail 0.1.1 → 0.1.12
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/AGENTS.md +418 -302
- package/dist/adapters/index.d.mts +1 -229
- package/dist/adapters/index.d.ts +1 -229
- package/dist/index-CbiH0sth.d.mts +286 -0
- package/dist/index-CbiH0sth.d.ts +286 -0
- package/dist/index.d.mts +5 -12
- package/dist/index.d.ts +5 -12
- package/dist/index.js +36 -22
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +32 -23
- package/dist/index.mjs.map +1 -1
- package/dist/telemetry/index.d.mts +74 -8
- package/dist/telemetry/index.d.ts +74 -8
- package/dist/telemetry/index.js.map +1 -1
- package/dist/telemetry/index.mjs.map +1 -1
- package/package.json +9 -9
- package/dist/adapter-BhnIsrlh.d.mts +0 -60
- package/dist/adapter-BhnIsrlh.d.ts +0 -60
package/AGENTS.md
CHANGED
|
@@ -1,407 +1,523 @@
|
|
|
1
|
-
# @igniter-js/mail
|
|
1
|
+
# AGENTS.md - @igniter-js/mail
|
|
2
2
|
|
|
3
|
-
> **
|
|
4
|
-
> **
|
|
5
|
-
> **
|
|
3
|
+
> **Last Updated:** 2025-12-23
|
|
4
|
+
> **Version:** 0.1.11
|
|
5
|
+
> **Goal:** This document serves as the complete operational manual for Code Agents working on the @igniter-js/mail package.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
## Package
|
|
9
|
+
## 1. Package Vision & Context
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
**Purpose:** Type-safe email library for Igniter.js with React Email templates and multiple provider adapters
|
|
13
|
-
**Type:** Standalone Library (can be used independently or with Igniter.js)
|
|
11
|
+
**@igniter-js/mail** is the definitive transactional email solution for the Igniter.js ecosystem. It is built on the premise that email templates should be as maintainable, type-safe, and expressive as the rest of an application's UI. By marrying **React Email** components with **StandardSchemaV1** validation and a robust **Adapter pattern**, this package ensures that sending an email is as predictable as rendering a web page.
|
|
14
12
|
|
|
15
|
-
###
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
|
|
13
|
+
### The Problem Space
|
|
14
|
+
|
|
15
|
+
In traditional backend development, email management is often an afterthought, leading to:
|
|
16
|
+
|
|
17
|
+
- **String Concatenation Mess:** Building HTML emails with string templates is error-prone and hard to debug.
|
|
18
|
+
- **Payload Uncertainty:** Backend services often pass data to templates that doesn't match the expected structure, resulting in broken emails.
|
|
19
|
+
- **Provider Rigidity:** Changing from one ESP (Email Service Provider) to another usually involves breaking changes across the codebase.
|
|
20
|
+
- **Observability Gaps:** Developers often don't know when an email fails, why it failed, or how long it took to render.
|
|
21
|
+
|
|
22
|
+
### The Igniter.js Vision
|
|
23
|
+
|
|
24
|
+
@igniter-js/mail provides a unified interface that abstracts away the complexities of email delivery:
|
|
25
|
+
|
|
26
|
+
1. **Expressive Templates:** Leverage the full power of React and Tailwind CSS (via React Email) to build beautiful, responsive emails.
|
|
27
|
+
2. **Contract-Based Sending:** Every template defines a schema. The package enforces this contract at the boundaries, ensuring that only valid data reaches your templates.
|
|
28
|
+
3. **Provider Agnostic:** Swap adapters (Resend, Postmark, SendGrid, SMTP) with a single line of configuration.
|
|
29
|
+
4. **Mission-Critical Reliability:** Built-in hooks for success/error tracking and deep integration with @igniter-js/jobs for guaranteed delivery via background queues.
|
|
24
30
|
|
|
25
31
|
---
|
|
26
32
|
|
|
27
|
-
## Architecture
|
|
33
|
+
## I. MAINTAINER GUIDE (Internal Architecture)
|
|
28
34
|
|
|
29
|
-
###
|
|
35
|
+
### 2. FileSystem Topology (Maintenance)
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
- End-to-end TypeScript inference from templates to send params
|
|
33
|
-
- Template payload validation with StandardSchema
|
|
34
|
-
- No `any` types in public APIs
|
|
37
|
+
Maintaining `@igniter-js/mail` requires understanding its modular structure. The package is divided into functional domains that strictly separate configuration, logic, and infrastructure.
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
- Templates are React components
|
|
38
|
-
- Automatic HTML and plain-text rendering
|
|
39
|
-
- Leverage React Email's component library
|
|
39
|
+
#### Directory Breakdown
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
- **`src/adapters/`**: Infrastructure layer.
|
|
42
|
+
- `index.ts`: Central export for all built-in adapters.
|
|
43
|
+
- `resend.adapter.ts`: Wrapper for the Resend SDK.
|
|
44
|
+
- `postmark.adapter.ts`: Native fetch-based implementation for Postmark (minimal dependency footprint).
|
|
45
|
+
- `sendgrid.adapter.ts`: Native fetch-based implementation for SendGrid.
|
|
46
|
+
- `smtp.adapter.ts`: Nodemailer-based implementation for traditional SMTP servers.
|
|
47
|
+
- `mock.adapter.ts`: In-memory implementation that records all outgoing emails for assertion in unit tests.
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- Compile-time validation of configuration
|
|
49
|
+
- **`src/builders/`**: The Configuration API.
|
|
50
|
+
- `main.builder.ts`: Implements `IgniterMailBuilder`. This is the entry point where `from`, `adapter`, and `queue` are configured. It uses a recursive generic pattern to accumulate template types.
|
|
51
|
+
- `template.builder.ts`: Implements `IgniterMailTemplateBuilder`. A specialized builder for defining a template's metadata (subject, schema) and its React renderer.
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
- **`src/core/`**: The Processing Engine.
|
|
54
|
+
- `manager.tsx`: Home of `IgniterMailManagerCore`. This class is the heart of the package. It handles the template registry, executes rendering, coordinates validation, and manages the execution of lifecycle hooks.
|
|
52
55
|
|
|
53
|
-
|
|
56
|
+
- **`src/errors/`**: Standardization of Failures.
|
|
57
|
+
- `mail.error.ts`: Defines the `IgniterMailError` class and the `IgniterMailErrorCode` union. All errors within the package are normalized to this format.
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
│ │ ├── sendgrid.adapter.spec.ts # SendGrid adapter tests
|
|
70
|
-
│ │ ├── smtp.adapter.ts # SMTP provider
|
|
71
|
-
│ │ └── smtp.adapter.spec.ts # SMTP adapter tests
|
|
72
|
-
│ │
|
|
73
|
-
│ ├── builders/
|
|
74
|
-
│ │ ├── main.builder.ts # Main builder
|
|
75
|
-
│ │ ├── main.builder.spec.ts # Builder tests
|
|
76
|
-
│ │ ├── template.builder.ts # Template builder
|
|
77
|
-
│ │ └── template.builder.spec.ts # Template builder tests
|
|
78
|
-
│ │
|
|
79
|
-
│ ├── core/
|
|
80
|
-
│ │ ├── manager.tsx # Core runtime logic
|
|
81
|
-
│ │ └── manager.spec.tsx # Core tests
|
|
82
|
-
│ │
|
|
83
|
-
│ ├── errors/
|
|
84
|
-
│ │ └── mail.error.ts # Main error class
|
|
85
|
-
│ │
|
|
86
|
-
│ ├── types/
|
|
87
|
-
│ │ ├── adapter.ts # Adapter types
|
|
88
|
-
│ │ ├── provider.ts # Provider types
|
|
89
|
-
│ │ ├── telemetry.ts # Telemetry types
|
|
90
|
-
│ │ └── templates.ts # Template types
|
|
91
|
-
│ │
|
|
92
|
-
│ ├── telemetry/
|
|
93
|
-
│ │ └── index.ts # Telemetry events
|
|
94
|
-
│ │
|
|
95
|
-
│ ├── utils/
|
|
96
|
-
│ │ ├── schema.ts # StandardSchema utilities
|
|
97
|
-
│ │ └── schema.spec.ts # Schema tests
|
|
98
|
-
│ │
|
|
99
|
-
│ └── shim.ts # Browser/client shim
|
|
100
|
-
│
|
|
101
|
-
├── package.json
|
|
102
|
-
├── tsconfig.json
|
|
103
|
-
├── tsup.config.ts
|
|
104
|
-
├── vitest.config.ts
|
|
105
|
-
├── README.md
|
|
106
|
-
├── AGENTS.md # This file
|
|
107
|
-
└── CHANGELOG.md
|
|
108
|
-
```
|
|
59
|
+
- **`src/telemetry/`**: Observability Registry.
|
|
60
|
+
- `index.ts`: Defines the `IgniterMailTelemetryEvents` namespace. It captures the start, success, and error states of both immediate (`send`) and scheduled (`schedule`) operations.
|
|
61
|
+
|
|
62
|
+
- **`src/types/`**: Pure Type Definitions.
|
|
63
|
+
- `adapter.ts`: Defines the `IgniterMailAdapter` interface and associated parameter types.
|
|
64
|
+
- `templates.ts`: Defines the structure of built templates and helper types for key/payload extraction.
|
|
65
|
+
- `provider.ts`: Defines the main public interfaces (`IIgniterMail`) and initialization options.
|
|
66
|
+
|
|
67
|
+
- **`src/utils/`**: Shared Utilities.
|
|
68
|
+
- `schema.ts`: Implements the `IgniterMailSchema` class, which provides agnostic handling of StandardSchemaV1 (supporting Zod and others).
|
|
69
|
+
|
|
70
|
+
- **`src/index.ts`**: The public entry point. It follows a strict barrel pattern with grouping comments.
|
|
71
|
+
|
|
72
|
+
- **`src/shim.ts`**: A critical file that prevents server-only logic from being included in client-side bundles.
|
|
109
73
|
|
|
110
74
|
---
|
|
111
75
|
|
|
112
|
-
|
|
76
|
+
### 3. Architecture Deep-Dive
|
|
77
|
+
|
|
78
|
+
#### 3.1 The Builder Accumulation Pattern
|
|
79
|
+
|
|
80
|
+
The `IgniterMailBuilder` is designed as an immutable state machine.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
export class IgniterMailBuilder<
|
|
84
|
+
TTemplates extends Record<string, IgniterMailTemplateBuilt<any>>,
|
|
85
|
+
> {
|
|
86
|
+
// ...
|
|
87
|
+
addTemplate<
|
|
88
|
+
TKey extends string,
|
|
89
|
+
TTemplate extends IgniterMailTemplateBuilt<any>,
|
|
90
|
+
>(
|
|
91
|
+
key: TKey,
|
|
92
|
+
template: TTemplate,
|
|
93
|
+
): IgniterMailBuilder<TTemplates & { [K in TKey]: TTemplate }> {
|
|
94
|
+
// ...
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Each call to `addTemplate` returns a **new instance** of the builder where the generic `TTemplates` is extended. This ensures that:
|
|
100
|
+
|
|
101
|
+
1. The final `build()` method returns a manager instance that is fully aware of its registered templates.
|
|
102
|
+
2. The user gets perfect IDE autocomplete for template keys.
|
|
103
|
+
3. The `data` parameter in `mail.send()` is typed specifically to the selected template's schema.
|
|
104
|
+
|
|
105
|
+
#### 3.2 The Rendering Pipeline Internals
|
|
113
106
|
|
|
114
|
-
|
|
107
|
+
Rendering is a two-pass process handled within `IgniterMailManagerCore`:
|
|
115
108
|
|
|
116
|
-
The
|
|
117
|
-
-
|
|
118
|
-
|
|
119
|
-
- React Email rendering (HTML + plain text)
|
|
120
|
-
- Adapter orchestration
|
|
121
|
-
- Queue integration for scheduled sends (requires queue adapter)
|
|
122
|
-
- Lifecycle hooks (onSendStarted, onSendSuccess, onSendError)
|
|
123
|
-
- Error handling and normalization
|
|
124
|
-
- Telemetry emission via `IgniterMailTelemetryEvents`
|
|
109
|
+
1. **React Element Creation:** The template's `render` function is called with the validated data.
|
|
110
|
+
2. **HTML Generation:** `@react-email/components`' `render` is called on the element.
|
|
111
|
+
3. **Text Generation:** The same element is passed to `render` with the `plainText: true` option.
|
|
125
112
|
|
|
126
|
-
|
|
127
|
-
- All templates must have a `subject`, `schema`, and `render` function
|
|
128
|
-
- Template data is validated before rendering
|
|
129
|
-
- All predictable failures throw `IgniterMailError` with stable `code`
|
|
130
|
-
- Hooks are always invoked (started → success/error)
|
|
113
|
+
This duality ensures high deliverability, as many email clients and spam filters penalize emails that lack a plain-text version.
|
|
131
114
|
|
|
132
|
-
|
|
115
|
+
#### 3.3 Queue Integration Logic
|
|
133
116
|
|
|
134
|
-
|
|
135
|
-
- Implement the `send` method with `MailAdapterSendParams`
|
|
136
|
-
- Handle provider-specific API calls and authentication
|
|
137
|
-
- NOT implement business logic (no validation, no hooks, no queuing)
|
|
138
|
-
- Return cleanly or throw errors (which core will normalize)
|
|
117
|
+
The `schedule` method provides a seamless bridge to `@igniter-js/jobs`.
|
|
139
118
|
|
|
140
|
-
|
|
141
|
-
-
|
|
142
|
-
-
|
|
119
|
+
- If a queue adapter is provided, the manager automatically registers a "send" job on that queue.
|
|
120
|
+
- This job handler is a simple wrapper that calls `manager.send()` with the provided payload.
|
|
121
|
+
- This allows developers to move heavy email rendering and network calls out of the request lifecycle with zero boilerplate.
|
|
122
|
+
|
|
123
|
+
#### 3.4 Telemetry and Observability
|
|
124
|
+
|
|
125
|
+
The package integrates deeply with `@igniter-js/telemetry`. Every public method emits a `started` event, followed by either a `success` or `error` event.
|
|
126
|
+
|
|
127
|
+
- **Attributes:** Events carry normalized attributes like `mail.to`, `mail.template`, and `mail.duration_ms`.
|
|
128
|
+
- **Privacy:** By default, PII (like email content or raw data objects) is **never** included in telemetry attributes.
|
|
143
129
|
|
|
144
130
|
---
|
|
145
131
|
|
|
146
|
-
|
|
132
|
+
### 4. Operational Flow Mapping (Pipelines)
|
|
147
133
|
|
|
148
|
-
|
|
134
|
+
#### Method: `IgniterMailBuilder.create()`
|
|
149
135
|
|
|
150
|
-
1. **
|
|
151
|
-
|
|
152
|
-
|
|
136
|
+
1. **Intent:** Initialize a fresh configuration chain.
|
|
137
|
+
2. **Execution:** Creates a new `IgniterMailBuilder` with default empty state.
|
|
138
|
+
3. **State:** `templates` is `{}`.
|
|
153
139
|
|
|
154
|
-
|
|
155
|
-
- If it's provider-specific, it belongs in the adapter
|
|
156
|
-
- Examples: API calls, authentication, retry logic
|
|
140
|
+
#### Method: `IgniterMailBuilder.withAdapter(provider, secret)`
|
|
157
141
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
142
|
+
1. **Validation:** Checks if `provider` is a known string ('resend', 'postmark', etc.).
|
|
143
|
+
2. **Instantiation:** Calls the static `create()` method of the corresponding adapter class.
|
|
144
|
+
3. **Assignment:** Stores the adapter instance in the builder's state.
|
|
145
|
+
4. **Returns:** The updated builder instance.
|
|
161
146
|
|
|
162
|
-
|
|
147
|
+
#### Method: `IgniterMailBuilder.addTemplate(key, template)`
|
|
163
148
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
- Test hooks lifecycle (started, success, error)
|
|
168
|
-
- Test queue integration (enqueue)
|
|
149
|
+
1. **Type Mapping:** Takes a `key` string and a template object (built via `IgniterMailTemplate.create()`).
|
|
150
|
+
2. **Merging:** Clones the current registry and adds the new template.
|
|
151
|
+
3. **Casting:** Returns a new builder instance cast to the intersection of the old templates and the new one.
|
|
169
152
|
|
|
170
|
-
|
|
171
|
-
- Test mock adapter tracking behavior
|
|
172
|
-
- Mock provider APIs
|
|
173
|
-
- Test error handling
|
|
153
|
+
#### Method: `IgniterMailManagerCore.send(params)`
|
|
174
154
|
|
|
175
|
-
|
|
176
|
-
|
|
155
|
+
1. **Entry:** User calls `mail.send({ to: '...', template: '...', data: { ... } })`.
|
|
156
|
+
2. **Telemetry (Started):** Emits `igniter.mail.send.started`.
|
|
157
|
+
3. **Hook (Started):** Awaits the execution of the `onSendStarted` hook.
|
|
158
|
+
4. **Template Retrieval:** Checks if the template exists. If not, throws `MAIL_PROVIDER_TEMPLATE_NOT_FOUND`.
|
|
159
|
+
5. **Validation:** Passes `params.data` through the template's schema.
|
|
160
|
+
- If validation fails, throws `MAIL_PROVIDER_TEMPLATE_DATA_INVALID` with the schema issues in the `details` property.
|
|
161
|
+
6. **Rendering:** Executes the React component and generates HTML and Text strings.
|
|
162
|
+
7. **Adapter Execution:** Awaits `adapter.send()`.
|
|
163
|
+
- If the adapter throws, the manager catches it, normalizes it to `MAIL_PROVIDER_SEND_FAILED`, and proceeds to the error flow.
|
|
164
|
+
8. **Telemetry (Success):** Emits `igniter.mail.send.success` with the calculated duration.
|
|
165
|
+
9. **Hook (Success):** Awaits the `onSendSuccess` hook.
|
|
177
166
|
|
|
178
|
-
|
|
179
|
-
- Verify template key type inference
|
|
180
|
-
- Verify payload type inference
|
|
181
|
-
- Verify builder fluent API type safety
|
|
167
|
+
#### Method: `IgniterMailManagerCore.schedule(params, date)`
|
|
182
168
|
|
|
183
|
-
|
|
169
|
+
1. **Entry:** User calls `mail.schedule(params, futureDate)`.
|
|
170
|
+
2. **Validation:** Ensures `futureDate` is > `now()`.
|
|
171
|
+
3. **Queue Check:** Ensures `this.queue` is configured.
|
|
172
|
+
4. **Idempotent Registration:** Calls `ensureQueueJobRegistered()`. If the job isn't yet registered on the queue adapter, it does so now.
|
|
173
|
+
5. **Invocation:** Calls `queue.adapter.invoke()` with a delay calculated from the target date.
|
|
174
|
+
6. **Telemetry:** Emits `igniter.mail.schedule.success`.
|
|
184
175
|
|
|
185
|
-
|
|
186
|
-
- Use JSDoc comments for public APIs (in English)
|
|
187
|
-
- Prefer explicit types over inference in public APIs
|
|
188
|
-
- Use `readonly` for immutable properties
|
|
189
|
-
- Use `async/await` over raw Promises
|
|
176
|
+
---
|
|
190
177
|
|
|
191
|
-
###
|
|
178
|
+
### 5. Dependency & Type Graph
|
|
192
179
|
|
|
193
|
-
|
|
194
|
-
- Use stable error codes (e.g., `MAIL_PROVIDER_TEMPLATE_NOT_FOUND`)
|
|
195
|
-
- Include relevant context in `error.metadata`
|
|
180
|
+
Understanding the type flow is essential for maintaining the "automatic" feel of the package.
|
|
196
181
|
|
|
197
|
-
|
|
182
|
+
#### 5.1 Type Architecture
|
|
198
183
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
184
|
+
- **`IgniterMailAdapter`**: The base interface. Any object with a `send` method matching the signature can be used as an adapter.
|
|
185
|
+
- **`IgniterMailTemplateBuilt<TSchema>`**: Represents a template that is ready for consumption. It carries the generic `TSchema` so it can be extracted later.
|
|
186
|
+
- **`IgniterMailInfer<TTemplates>`**: This is a utility type that transforms the template registry into a consumable format for other packages:
|
|
187
|
+
- `.Templates`: A union of string literal keys (e.g., `'welcome' | 'goodbye'`).
|
|
188
|
+
- `.Payloads`: A map of keys to their inferred schema input types.
|
|
189
|
+
|
|
190
|
+
#### 5.2 Dependency Management
|
|
191
|
+
|
|
192
|
+
The package keeps a "Zero-Bloat" policy for production:
|
|
193
|
+
|
|
194
|
+
- **`react` and `@react-email/components`**: Required for template rendering.
|
|
195
|
+
- **`resend`, `nodemailer`**: Peer dependencies. They are not bundled, keeping the core package small.
|
|
196
|
+
- **`@igniter-js/core`**: Provides the foundational error class and job interfaces.
|
|
197
|
+
- **`@igniter-js/telemetry`**: Optional peer dependency. If missing, telemetry emission is safely skipped.
|
|
206
198
|
|
|
207
199
|
---
|
|
208
200
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
201
|
+
### 6. Maintenance Checklist
|
|
202
|
+
|
|
203
|
+
#### Feature Addition Workflow
|
|
204
|
+
|
|
205
|
+
1. [ ] Define the interface in `src/types/`.
|
|
206
|
+
2. [ ] Add configuration method to `IgniterMailBuilder` (ensure immutability).
|
|
207
|
+
3. [ ] Implement the logic in `IgniterMailManagerCore`.
|
|
208
|
+
4. [ ] If the feature involves a new error state, add the code to `IgniterMailErrorCode`.
|
|
209
|
+
5. [ ] Define new telemetry events if needed.
|
|
210
|
+
6. [ ] Add unit tests in the corresponding `.spec.ts` file.
|
|
211
|
+
7. [ ] Run `npm run build` to verify that types are correctly exported.
|
|
212
|
+
|
|
213
|
+
#### Bugfix Workflow
|
|
214
|
+
|
|
215
|
+
1. [ ] Create a reproduction test case in `src/core/manager.spec.tsx`.
|
|
216
|
+
2. [ ] Identify the layer (Builder, Core, or Adapter).
|
|
217
|
+
3. [ ] Apply the fix.
|
|
218
|
+
4. [ ] Verify that `npm run typecheck` still passes (inference is fragile).
|
|
219
|
+
5. [ ] Update `CHANGELOG.md` with a descriptive message following Conventional Commits.
|
|
227
220
|
|
|
228
221
|
---
|
|
229
222
|
|
|
230
|
-
|
|
223
|
+
### 7. Maintainer Troubleshooting
|
|
231
224
|
|
|
232
|
-
|
|
225
|
+
#### Q: The `schedule` method is throwing "Job not found".
|
|
233
226
|
|
|
234
|
-
|
|
235
|
-
# Run all tests
|
|
236
|
-
npm run test
|
|
227
|
+
**A:** This usually happens if the queue adapter's name resolution is inconsistent. Check `manager.tsx`'s `ensureQueueJobRegistered` method. Ensure that the name used in `register` matches exactly the name used in `invoke`.
|
|
237
228
|
|
|
238
|
-
|
|
239
|
-
npm run test:watch
|
|
229
|
+
#### Q: TypeScript is complaining that `TTemplates` is not assignable.
|
|
240
230
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
231
|
+
**A:** This happens in the `addTemplate` method of the builder. Because we are using an intersection type (`TTemplates & { ... }`), sometimes TypeScript gets lost if the registry gets too deep. Ensure you are using `as any` only where strictly necessary to "bridge" the generic transition.
|
|
232
|
+
|
|
233
|
+
#### Q: StandardSchemaV1 validation is being skipped.
|
|
234
|
+
|
|
235
|
+
**A:** Check `src/utils/schema.ts`. It looks for the `~standard` property on the schema object. If the user provided a plain object instead of a Zod schema (or a compatible one), the package defaults to a "passthrough" mode.
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## II. CONSUMER GUIDE (Developer Manual)
|
|
240
|
+
|
|
241
|
+
### 8. Distribution Anatomy (Consumption)
|
|
242
|
+
|
|
243
|
+
`@igniter-js/mail` is architected to be runtime-agnostic but server-safe.
|
|
244
|
+
|
|
245
|
+
#### 8.1 Module Formats
|
|
246
|
+
|
|
247
|
+
- **ESM (`.mjs`)**: For modern Node.js, Bun, and Edge runtimes.
|
|
248
|
+
- **CommonJS (`.js`)**: For legacy Node.js support.
|
|
249
|
+
- **DTS (`.d.ts`)**: Full type definitions including all internal interfaces.
|
|
250
|
+
|
|
251
|
+
#### 8.2 Subpath Exports
|
|
252
|
+
|
|
253
|
+
We provide dedicated subpaths to optimize bundle size and organization:
|
|
254
|
+
|
|
255
|
+
- `@igniter-js/mail`: Main entry point (Builder, Manager).
|
|
256
|
+
- `@igniter-js/mail/adapters`: Direct access to adapter classes.
|
|
257
|
+
- `@igniter-js/mail/telemetry`: Telemetry event definitions.
|
|
244
258
|
|
|
245
|
-
|
|
259
|
+
#### 8.3 Browser Shim
|
|
246
260
|
|
|
247
|
-
|
|
248
|
-
# Build once
|
|
249
|
-
npm run build
|
|
261
|
+
The `shim.ts` ensures that your frontend bundle doesn't accidentally include `nodemailer` or other heavy server dependencies. If you see a "Server-only" error in your browser console, you've imported the mail service into a client component.
|
|
250
262
|
|
|
251
|
-
|
|
252
|
-
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
### 9. Quick Start & Common Patterns
|
|
266
|
+
|
|
267
|
+
#### Initializing the Service
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import { IgniterMail } from "@igniter-js/mail";
|
|
271
|
+
import { z } from "zod";
|
|
272
|
+
import { WelcomeEmail } from "./emails/WelcomeEmail";
|
|
273
|
+
|
|
274
|
+
export const mail = IgniterMail.create()
|
|
275
|
+
.withFrom("system@myapp.com")
|
|
276
|
+
.withAdapter("resend", process.env.RESEND_API_KEY!)
|
|
277
|
+
.addTemplate("welcome", {
|
|
278
|
+
subject: "Welcome to the Team!",
|
|
279
|
+
schema: z.object({ name: z.string() }),
|
|
280
|
+
render: WelcomeEmail,
|
|
281
|
+
})
|
|
282
|
+
.build();
|
|
253
283
|
```
|
|
254
284
|
|
|
255
|
-
|
|
285
|
+
#### Sending an Email
|
|
256
286
|
|
|
257
|
-
```
|
|
258
|
-
|
|
287
|
+
```typescript
|
|
288
|
+
await mail.send({
|
|
289
|
+
to: "user@example.com",
|
|
290
|
+
template: "welcome",
|
|
291
|
+
data: { name: "John Doe" },
|
|
292
|
+
});
|
|
259
293
|
```
|
|
260
294
|
|
|
261
|
-
|
|
295
|
+
#### Overriding Subjects
|
|
262
296
|
|
|
263
|
-
```
|
|
264
|
-
|
|
297
|
+
```typescript
|
|
298
|
+
await mail.send({
|
|
299
|
+
to: "user@example.com",
|
|
300
|
+
template: "welcome",
|
|
301
|
+
subject: "Special welcome for John!", // This overrides the default 'Welcome to the Team!'
|
|
302
|
+
data: { name: "John" },
|
|
303
|
+
});
|
|
265
304
|
```
|
|
266
305
|
|
|
267
306
|
---
|
|
268
307
|
|
|
269
|
-
|
|
308
|
+
### 10. Real-World Use Case Library
|
|
270
309
|
|
|
271
|
-
|
|
310
|
+
#### Case 1: High-Performance Verification Emails
|
|
272
311
|
|
|
273
|
-
**
|
|
274
|
-
|
|
312
|
+
**Scenario:** A user signs up and needs an OTP code immediately.
|
|
313
|
+
**Solution:** Use the Resend adapter for ultra-low latency and send directly in the request.
|
|
275
314
|
|
|
276
|
-
|
|
277
|
-
|
|
315
|
+
```typescript
|
|
316
|
+
await mail.send({
|
|
317
|
+
to: user.email,
|
|
318
|
+
template: "verifyCode",
|
|
319
|
+
data: { code: "123456" },
|
|
320
|
+
});
|
|
321
|
+
```
|
|
278
322
|
|
|
279
|
-
|
|
280
|
-
|
|
323
|
+
#### Case 2: Bulk Newsletter Delivery
|
|
324
|
+
|
|
325
|
+
**Scenario:** Sending a weekly update to 10,000 users.
|
|
326
|
+
**Solution:** Use `.withQueue()` and `mail.schedule()` to avoid timing out the main server.
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
for (const user of subscribers) {
|
|
330
|
+
await mail.schedule(
|
|
331
|
+
{
|
|
332
|
+
to: user.email,
|
|
333
|
+
template: "weeklyUpdate",
|
|
334
|
+
data: { content: updateContent },
|
|
335
|
+
},
|
|
336
|
+
new Date(),
|
|
337
|
+
); // Immediate but async via queue
|
|
338
|
+
}
|
|
339
|
+
```
|
|
281
340
|
|
|
282
|
-
|
|
283
|
-
- Uses a connection URL passed via adapter credentials (e.g., `smtps://user:pass@host:465`)
|
|
341
|
+
#### Case 3: SaaS Multi-Tenant Support
|
|
284
342
|
|
|
285
|
-
**
|
|
286
|
-
|
|
343
|
+
**Scenario:** Different companies using the same platform need different "From" addresses.
|
|
344
|
+
**Solution:** Use a dynamic adapter instance or the `subject` override.
|
|
287
345
|
|
|
288
|
-
|
|
346
|
+
#### Case 4: Healthcare Compliance Reminders
|
|
289
347
|
|
|
290
|
-
|
|
348
|
+
**Scenario:** Patients must receive a HIPAA notice exactly 48 hours before surgery.
|
|
349
|
+
**Solution:** Use `mail.schedule()` with a precise target date.
|
|
291
350
|
|
|
292
|
-
|
|
351
|
+
```typescript
|
|
352
|
+
const surgeryDate = new Date(...);
|
|
353
|
+
const reminderDate = new Date(surgeryDate.getTime() - (48 * 60 * 60 * 1000));
|
|
354
|
+
await mail.schedule(params, reminderDate);
|
|
355
|
+
```
|
|
293
356
|
|
|
294
|
-
|
|
295
|
-
- [ ] Build succeeds (`npm run build`)
|
|
296
|
-
- [ ] TypeScript compiles (`npm run typecheck`)
|
|
297
|
-
- [ ] Linting passes (`npm run lint`)
|
|
298
|
-
- [ ] README.md is up-to-date
|
|
299
|
-
- [ ] CHANGELOG.md is updated with version changes
|
|
300
|
-
- [ ] Version in `package.json` is correct
|
|
301
|
-
- [ ] All exports in `src/index.ts` are correct
|
|
357
|
+
#### Case 5: E-commerce Order Tracking
|
|
302
358
|
|
|
303
|
-
|
|
359
|
+
**Scenario:** Providing real-time updates as a package moves.
|
|
360
|
+
**Solution:** Use hooks to update the database state when an email is successfully sent.
|
|
304
361
|
|
|
305
|
-
|
|
362
|
+
```typescript
|
|
363
|
+
.onSendSuccess(async (params) => {
|
|
364
|
+
if (params.template === 'shippingUpdate') {
|
|
365
|
+
await db.orders.update({ where: { id: params.data.orderId }, data: { notified: true } });
|
|
366
|
+
}
|
|
367
|
+
})
|
|
368
|
+
```
|
|
306
369
|
|
|
307
|
-
|
|
308
|
-
1. Review changes made
|
|
309
|
-
2. Suggest version bump options (patch/minor/major)
|
|
310
|
-
3. Wait for user approval
|
|
311
|
-
4. Update `package.json` version
|
|
312
|
-
5. Update `CHANGELOG.md`
|
|
313
|
-
6. Run quality checks
|
|
314
|
-
7. Ask about publishing
|
|
370
|
+
#### Case 6: Education Platform Course Onboarding
|
|
315
371
|
|
|
316
|
-
|
|
372
|
+
**Scenario:** A sequence of 5 emails over 5 days.
|
|
373
|
+
**Solution:** Use `mail.schedule()` for each day in the sequence upon enrollment.
|
|
317
374
|
|
|
318
|
-
|
|
319
|
-
# Login to npm (if not already)
|
|
320
|
-
npm login
|
|
375
|
+
#### Case 7: Social Media Mention Alerts
|
|
321
376
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
377
|
+
**Scenario:** Frequent, small emails.
|
|
378
|
+
**Solution:** Use the Postmark adapter for its excellent deliverability and simple JSON API.
|
|
379
|
+
|
|
380
|
+
#### Case 8: HR Portal Application Status
|
|
381
|
+
|
|
382
|
+
**Scenario:** High PII (Personally Identifiable Information) sensitivity.
|
|
383
|
+
**Solution:** Use the SMTP adapter with a private company mail server to keep traffic internal.
|
|
384
|
+
|
|
385
|
+
#### Case 9: Logistics "Proof of Delivery"
|
|
386
|
+
|
|
387
|
+
**Scenario:** Attaching a link to a signed document.
|
|
388
|
+
**Solution:** React Email's `Link` component and Zod's `.url()` validation.
|
|
389
|
+
|
|
390
|
+
#### Case 10: Fintech Fraud Alerts
|
|
391
|
+
|
|
392
|
+
**Scenario:** Every millisecond counts.
|
|
393
|
+
**Solution:** Use the `onSendStarted` hook to immediately log the attempt to a high-speed audit log.
|
|
326
394
|
|
|
327
395
|
---
|
|
328
396
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
| `MAIL_ADAPTER_CONFIGURATION_INVALID` | Adapter configuration error |
|
|
397
|
+
### 11. Domain-Specific Guidance
|
|
398
|
+
|
|
399
|
+
#### Fintech and Security
|
|
400
|
+
|
|
401
|
+
- **Strict Validation:** Use `z.object({ ... }).strict()` in your templates to prevent passing extra data that might be logged.
|
|
402
|
+
- **Telemetry:** Avoid putting transaction IDs or account numbers in the subject line, as they will be captured by telemetry.
|
|
403
|
+
|
|
404
|
+
#### Marketing and Newsletters
|
|
405
|
+
|
|
406
|
+
- **Spam Score:** Always provide a plain-text fallback. Igniter.js does this automatically, but ensure your component renders meaningful text (no "click here" only).
|
|
407
|
+
- **Unsubscribe:** Use the `headers` property (coming soon) to add the `List-Unsubscribe` header.
|
|
408
|
+
|
|
409
|
+
#### E-commerce
|
|
410
|
+
|
|
411
|
+
- **HTML Size:** Some email clients (like Gmail) clip emails larger than 1024KB. Keep your React components lean and use hosted images instead of base64.
|
|
345
412
|
|
|
346
413
|
---
|
|
347
414
|
|
|
348
|
-
|
|
415
|
+
### 12. Best Practices & Anti-Patterns
|
|
349
416
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
417
|
+
| Practice | Why? | Example |
|
|
418
|
+
| ------------------------------------ | ------------------------------------------------- | --------------------------------- |
|
|
419
|
+
| ✅ **Always define schemas** | Prevents runtime rendering errors. | `z.object({ name: z.string() })` |
|
|
420
|
+
| ✅ **Use `MockMailAdapter` in CI** | Fast, deterministic, and free. | `MockMailAdapter.create()` |
|
|
421
|
+
| ✅ **Await `send` calls** | Ensures errors are caught in the current context. | `await mail.send(...)` |
|
|
422
|
+
| ✅ **Set `scheduledAt` for queues** | Better visibility in your job manager. | `mail.schedule(p, date)` |
|
|
423
|
+
| ❌ **Don't use `any` in data** | Breaks the primary value of this package. | `data: { name: 123 } as any` |
|
|
424
|
+
| ❌ **Don't put secrets in subjects** | Telemetry and logs will expose them. | `subject: 'Your secret: 123'` |
|
|
425
|
+
| ❌ **Don't hardcode FROM addresses** | Makes it hard to change environments. | `withFrom(process.env.MAIL_FROM)` |
|
|
355
426
|
|
|
356
427
|
---
|
|
357
428
|
|
|
358
|
-
##
|
|
429
|
+
## III. TECHNICAL REFERENCE & RESILIENCE
|
|
430
|
+
|
|
431
|
+
### 13. Exhaustive API Reference
|
|
359
432
|
|
|
360
|
-
|
|
433
|
+
#### Core Classes
|
|
361
434
|
|
|
362
|
-
|
|
|
363
|
-
|
|
364
|
-
| `
|
|
365
|
-
| `
|
|
366
|
-
| `
|
|
367
|
-
| `
|
|
368
|
-
| `src/adapters/*.adapter.ts` | Provider adapters |
|
|
369
|
-
| `src/telemetry/index.ts` | Telemetry events |
|
|
370
|
-
| `src/shim.ts` | Browser/client shim |
|
|
371
|
-
| `src/types/*.ts` | Public types |
|
|
372
|
-
| `src/errors/mail.error.ts` | Error classes |
|
|
435
|
+
| Symbol | Responsibility | Key Methods |
|
|
436
|
+
| ------------------------ | -------------------- | --------------------------------------------------- |
|
|
437
|
+
| `IgniterMail` | Primary entry point | `create()` |
|
|
438
|
+
| `IgniterMailBuilder` | Configuration engine | `withAdapter`, `withFrom`, `addTemplate`, `build` |
|
|
439
|
+
| `IgniterMailManagerCore` | Runtime engine | `send`, `schedule`, `$Infer` |
|
|
440
|
+
| `IgniterMailTemplate` | Template definition | `create`, `withSubject`, `withSchema`, `withRender` |
|
|
373
441
|
|
|
374
|
-
|
|
442
|
+
#### Adapters
|
|
375
443
|
|
|
376
|
-
|
|
|
377
|
-
|
|
378
|
-
| `
|
|
379
|
-
| `
|
|
380
|
-
| `
|
|
381
|
-
| `
|
|
382
|
-
| `
|
|
444
|
+
| Adapter | Provider | Required Credential |
|
|
445
|
+
| --------------------- | ---------------- | ---------------------------- |
|
|
446
|
+
| `ResendMailAdapter` | Resend.com | API Key |
|
|
447
|
+
| `PostmarkMailAdapter` | Postmarkapp.com | Server Token |
|
|
448
|
+
| `SendGridMailAdapter` | Sendgrid.com | API Key |
|
|
449
|
+
| `SmtpMailAdapter` | Generic SMTP | Connection URL (smtps://...) |
|
|
450
|
+
| `MockMailAdapter` | Memory (Testing) | None |
|
|
451
|
+
|
|
452
|
+
#### Type Helpers
|
|
453
|
+
|
|
454
|
+
| Type | Purpose | Usage |
|
|
455
|
+
| ------------------ | ------------------------ | ---------------------------------------------------- |
|
|
456
|
+
| `$Infer.Templates` | Union of valid keys | `type Keys = typeof mail.$Infer.Templates` |
|
|
457
|
+
| `$Infer.Payloads` | Map of payloads | `type Data = typeof mail.$Infer.Payloads['welcome']` |
|
|
458
|
+
| `$Infer.SendInput` | Union of all send params | For reusable utility functions |
|
|
383
459
|
|
|
384
460
|
---
|
|
385
461
|
|
|
386
|
-
|
|
462
|
+
### 14. Telemetry & Observability Registry
|
|
463
|
+
|
|
464
|
+
The package exposes the following events via the `igniter.mail` namespace:
|
|
387
465
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
466
|
+
#### Group: `send`
|
|
467
|
+
|
|
468
|
+
| Event | Attributes | Context |
|
|
469
|
+
| --------- | ----------------------------- | ----------------------------------------- |
|
|
470
|
+
| `started` | `mail.to`, `mail.template` | Emitted when `.send()` is called. |
|
|
471
|
+
| `success` | `mail.to`, `mail.duration_ms` | Emitted when the ESP accepts the email. |
|
|
472
|
+
| `error` | `mail.to`, `mail.error.code` | Emitted on validation or network failure. |
|
|
473
|
+
|
|
474
|
+
#### Group: `schedule`
|
|
475
|
+
|
|
476
|
+
| Event | Attributes | Context |
|
|
477
|
+
| --------- | ------------------------------- | ------------------------------------- |
|
|
478
|
+
| `started` | `mail.to`, `mail.scheduled_at` | Emitted when `.schedule()` is called. |
|
|
479
|
+
| `success` | `mail.to`, `mail.queue_id` | Emitted when the job is enqueued. |
|
|
480
|
+
| `error` | `mail.to`, `mail.error.message` | Emitted if the queue adapter fails. |
|
|
395
481
|
|
|
396
482
|
---
|
|
397
483
|
|
|
398
|
-
|
|
484
|
+
### 15. Troubleshooting & Error Code Library
|
|
485
|
+
|
|
486
|
+
#### `MAIL_PROVIDER_ADAPTER_REQUIRED`
|
|
487
|
+
|
|
488
|
+
- **Context:** Occurs during `.build()`.
|
|
489
|
+
- **Cause:** You called `build()` before telling the service how to actually send emails.
|
|
490
|
+
- **Mitigation:** Ensure `.withAdapter()` is present in your builder chain.
|
|
491
|
+
- **Solution:** `mail.withAdapter('resend', 'key').build()`.
|
|
492
|
+
|
|
493
|
+
#### `MAIL_PROVIDER_TEMPLATE_NOT_FOUND`
|
|
494
|
+
|
|
495
|
+
- **Context:** Occurs during `.send()`.
|
|
496
|
+
- **Cause:** The string key provided for `template` does not exist in the builder's registry.
|
|
497
|
+
- **Mitigation:** Double-check typos and ensure `addTemplate` was called for that key.
|
|
498
|
+
- **Solution:** Check the `mail.addTemplate('welcom', ...)` typo.
|
|
499
|
+
|
|
500
|
+
#### `MAIL_PROVIDER_TEMPLATE_DATA_INVALID`
|
|
501
|
+
|
|
502
|
+
- **Context:** Occurs before rendering.
|
|
503
|
+
- **Cause:** The data object does not satisfy the template's schema (e.g., missing required fields).
|
|
504
|
+
- **Mitigation:** Check the `details` array of the error to see which Zod/Schema path failed.
|
|
505
|
+
- **Solution:** `data: { name: 'Valid String' }`.
|
|
506
|
+
|
|
507
|
+
#### `MAIL_PROVIDER_SEND_FAILED`
|
|
508
|
+
|
|
509
|
+
- **Context:** Occurs after rendering, during adapter execution.
|
|
510
|
+
- **Cause:** The ESP rejected the request (e.g., invalid API key, bounce, suppression list).
|
|
511
|
+
- **Mitigation:** Check the `metadata` property of the error for the raw provider response.
|
|
512
|
+
- **Solution:** Verify your API key and sender domain authorization.
|
|
513
|
+
|
|
514
|
+
#### `MAIL_PROVIDER_SCHEDULE_DATE_INVALID`
|
|
515
|
+
|
|
516
|
+
- **Context:** Occurs during `.schedule()`.
|
|
517
|
+
- **Cause:** The date provided is in the past.
|
|
518
|
+
- **Mitigation:** Ensure your scheduling logic accounts for clock drift and processing time.
|
|
519
|
+
- **Solution:** `mail.schedule(params, new Date(Date.now() + 5000))`.
|
|
520
|
+
|
|
521
|
+
---
|
|
399
522
|
|
|
400
|
-
|
|
401
|
-
- Core email functionality with React Email
|
|
402
|
-
- Type-safe templates with StandardSchema validation
|
|
403
|
-
- Multiple adapters (Resend, Postmark, SendGrid, SMTP) and mock adapter for tests
|
|
404
|
-
- Queue integration for scheduled sends
|
|
405
|
-
- Lifecycle hooks
|
|
406
|
-
- Builder pattern API
|
|
407
|
-
- Comprehensive documentation
|
|
523
|
+
**End of AGENTS.md for @igniter-js/mail**
|