@igniter-js/mail 0.1.11 → 0.1.13

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 CHANGED
@@ -1,407 +1,523 @@
1
- # @igniter-js/mail - AI Agent Instructions
1
+ # AGENTS.md - @igniter-js/mail
2
2
 
3
- > **Package Version:** 0.1.1
4
- > **Last Updated:** 2025-01-22
5
- > **Status:** Ready for Publication
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 Overview
9
+ ## 1. Package Vision & Context
10
10
 
11
- **Name:** `@igniter-js/mail`
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
- ### Core Features
16
- - Type-safe email templates with React Email components
17
- - Runtime validation with StandardSchema support
18
- - Multiple provider adapters (Resend, Postmark, SendGrid, SMTP)
19
- - Mock adapter for unit testing
20
- - Queue integration for async email delivery
21
- - Lifecycle hooks for monitoring and logging
22
- - Builder pattern for fluent configuration
23
- - Server-first design (Node.js, Bun, Deno)
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
- ### Design Principles
35
+ ### 2. FileSystem Topology (Maintenance)
30
36
 
31
- 1. **Type Safety First**
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
- 2. **React Email Integration**
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
- 3. **Adapter-Based Architecture**
42
- - Core defines interfaces, adapters provide implementations
43
- - Easy to add new email providers
44
- - Adapters are peer dependencies (optional)
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
- 4. **Builder Pattern**
47
- - Fluent API for configuration
48
- - Type-safe template registration
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
- ## File Structure
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
- packages/mail/
57
- ├── src/
58
- │ ├── index.ts # Public exports
59
- │ │
60
- │ ├── adapters/
61
- │ │ ├── index.ts # Adapters barrel
62
- │ │ ├── mock.adapter.ts # In-memory mock adapter
63
- │ │ ├── mock.adapter.spec.ts # Mock adapter tests
64
- │ │ ├── postmark.adapter.ts # Postmark provider
65
- │ │ ├── postmark.adapter.spec.ts # Postmark adapter tests
66
- │ │ ├── resend.adapter.ts # Resend provider
67
- │ │ ├── resend.adapter.spec.ts # Resend adapter tests
68
- │ │ ├── sendgrid.adapter.ts # SendGrid provider
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
- ## Key Responsibilities
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
- ### `IgniterMail` (Core Runtime)
107
+ Rendering is a two-pass process handled within `IgniterMailManagerCore`:
115
108
 
116
- The core class handles:
117
- - Template registration and management
118
- - Template data validation using StandardSchema
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
- **Key Invariants:**
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
- ### `MailAdapter` (Infrastructure-only)
115
+ #### 3.3 Queue Integration Logic
133
116
 
134
- Adapters **must**:
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
- **Mental Model:**
141
- - **Core** decides *what* to send and *when*
142
- - **Adapter** executes *how* to talk to the provider
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
- ## Development Guidelines
132
+ ### 4. Operational Flow Mapping (Pipelines)
147
133
 
148
- ### Adding New Features
134
+ #### Method: `IgniterMailBuilder.create()`
149
135
 
150
- 1. **Business Logic Core**
151
- - If it's a rule or decision, it belongs in `IgniterMail`
152
- - Examples: validation, hooks, queuing
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
- 2. **Infrastructure Logic → Adapter**
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
- 3. **Public API Builder or Core**
159
- - If users need to configure it, add to `IgniterMailBuilder`
160
- - If users need to call it at runtime, add to `IgniterMail`
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
- ### Testing Strategy
147
+ #### Method: `IgniterMailBuilder.addTemplate(key, template)`
163
148
 
164
- **Unit Tests** (`src/core/manager.spec.tsx`):
165
- - Use `MockMailAdapter` for isolated core testing
166
- - Test template validation (valid and invalid schemas)
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
- **Adapter Tests** (`src/adapters/*.spec.ts`):
171
- - Test mock adapter tracking behavior
172
- - Mock provider APIs
173
- - Test error handling
153
+ #### Method: `IgniterMailManagerCore.send(params)`
174
154
 
175
- **Builder Tests** (`src/builders/*.spec.ts`):
176
- - Test chaining, hooks, and template builder validation
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
- **Type Tests** (in manager spec):
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
- ### Code Style
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
- - Follow ESLint rules (`npm run lint`)
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
- ### Error Handling
178
+ ### 5. Dependency & Type Graph
192
179
 
193
- - All predictable errors must throw `IgniterMailError`
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
- ### Commit Messages
182
+ #### 5.1 Type Architecture
198
183
 
199
- Follow Conventional Commits:
200
- ```
201
- feat(mail): add SendGrid adapter
202
- fix(mail): handle schema validation errors correctly
203
- docs(mail): update README with queue examples
204
- test(mail): add tests for template validation
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
- ## Adding a New Adapter
210
-
211
- 1. Create new file in `src/adapters/` (e.g., `aws-ses.adapter.ts`)
212
- 2. Create builder class extending pattern (e.g., `AwsSesMailAdapterBuilder`)
213
- 3. Implement `send` method with provider API calls
214
- 4. Export adapter and builder from `src/index.ts`
215
- 5. Add provider to `IgniterMailBuilder.withAdapter()` shorthand (optional)
216
- 6. Add tests for adapter-specific behavior
217
- 7. Update README with adapter documentation
218
- 8. Add peer dependency to `package.json`
219
-
220
- **Adapter Checklist:**
221
- - Implement `MailAdapter` interface
222
- - ✅ Handle provider authentication
223
- - Support `to`, `subject`, `html`, `text` fields
224
- - Optionally support `scheduledAt` if provider supports it
225
- - Throw descriptive errors
226
- - Export builder with `create()` factory
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
- ## Common Tasks
223
+ ### 7. Maintainer Troubleshooting
231
224
 
232
- ### Running Tests
225
+ #### Q: The `schedule` method is throwing "Job not found".
233
226
 
234
- ```bash
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
- # Watch mode
239
- npm run test:watch
229
+ #### Q: TypeScript is complaining that `TTemplates` is not assignable.
240
230
 
241
- # Run specific test file
242
- npm run test -- src/core/manager.spec.tsx
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
- ### Building
259
+ #### 8.3 Browser Shim
246
260
 
247
- ```bash
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
- # Build and watch
252
- npm run dev
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
- ### Type Checking
285
+ #### Sending an Email
256
286
 
257
- ```bash
258
- npm run typecheck
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
- ### Linting
295
+ #### Overriding Subjects
262
296
 
263
- ```bash
264
- npm run lint
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
- ## Environment Variables
308
+ ### 10. Real-World Use Case Library
270
309
 
271
- While the package doesn't directly read environment variables, adapters typically require:
310
+ #### Case 1: High-Performance Verification Emails
272
311
 
273
- **Resend:**
274
- - `RESEND_API_KEY` - Resend API key
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
- **Postmark:**
277
- - `POSTMARK_SERVER_TOKEN` - Postmark server token
315
+ ```typescript
316
+ await mail.send({
317
+ to: user.email,
318
+ template: "verifyCode",
319
+ data: { code: "123456" },
320
+ });
321
+ ```
278
322
 
279
- **SendGrid:**
280
- - `SENDGRID_API_KEY` - SendGrid API key
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
- **SMTP:**
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
- **Validation:**
286
- - Supports any validation library that implements `StandardSchemaV1` (Zod 3.23+ or compatible)
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
- ## Publishing Workflow
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
- ### Pre-Publish Checklist
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
- - [ ] All tests passing (`npm run test`)
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
- ### Version Update Process
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
- **NEVER update version without user approval.**
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
- When changes are ready:
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
- ### Publishing
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
- ```bash
319
- # Login to npm (if not already)
320
- npm login
375
+ #### Case 7: Social Media Mention Alerts
321
376
 
322
- # Publish (from package directory)
323
- cd packages/mail
324
- npm publish --access public
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
- ## Error Codes Reference
330
-
331
- | Code | Reason |
332
- |------|--------|
333
- | `MAIL_PROVIDER_ADAPTER_REQUIRED` | No adapter configured |
334
- | `MAIL_PROVIDER_TEMPLATES_REQUIRED` | No templates registered |
335
- | `MAIL_PROVIDER_TEMPLATE_NOT_FOUND` | Template key doesn't exist |
336
- | `MAIL_PROVIDER_TEMPLATE_DATA_INVALID` | Schema validation failed |
337
- | `MAIL_PROVIDER_SEND_FAILED` | Failed to send email |
338
- | `MAIL_PROVIDER_SCHEDULE_DATE_INVALID` | Schedule date is in the past |
339
- | `MAIL_PROVIDER_SCHEDULE_QUEUE_NOT_CONFIGURED` | Queue adapter is required |
340
- | `MAIL_PROVIDER_SCHEDULE_FAILED` | Failed to schedule email |
341
- | `MAIL_PROVIDER_FROM_REQUIRED` | FROM address not configured |
342
- | `MAIL_PROVIDER_ADAPTER_SECRET_REQUIRED` | Adapter secret not provided |
343
- | `MAIL_PROVIDER_ADAPTER_NOT_FOUND` | Unknown adapter provider |
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
- ## Non-Goals
415
+ ### 12. Best Practices & Anti-Patterns
349
416
 
350
- By design, this package does NOT:
351
- - Implement business rules in adapters (that's the core's job)
352
- - Provide browser-side email sending (server-first)
353
- - Bundle all adapter dependencies (they're peer dependencies)
354
- - Support synchronous email sending (always async)
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
- ## Quick Reference
429
+ ## III. TECHNICAL REFERENCE & RESILIENCE
430
+
431
+ ### 13. Exhaustive API Reference
359
432
 
360
- ### Key Files
433
+ #### Core Classes
361
434
 
362
- | File | Purpose |
363
- |------|---------|
364
- | `src/index.ts` | Public exports |
365
- | `src/core/manager.tsx` | Core runtime logic |
366
- | `src/builders/main.builder.ts` | Builder API |
367
- | `src/builders/template.builder.ts` | Template builder |
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
- ### Key Commands
442
+ #### Adapters
375
443
 
376
- | Command | Purpose |
377
- |---------|---------|
378
- | `npm run dev` | Build and watch |
379
- | `npm run build` | Build once |
380
- | `npm run test` | Run tests |
381
- | `npm run typecheck` | Check types |
382
- | `npm run lint` | Lint code |
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
- ## Support & Communication
462
+ ### 14. Telemetry & Observability Registry
463
+
464
+ The package exposes the following events via the `igniter.mail` namespace:
387
465
 
388
- When working on this package:
389
- - **Read this file first** before making changes
390
- - **Follow existing patterns** - consistency is key
391
- - **Update documentation** after every code change
392
- - **Write tests** for new features and bug fixes
393
- - **Ask for clarification** if requirements are unclear
394
- - **Suggest improvements** to this AGENTS.md if needed
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
- ## Changelog History
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
- ### v0.1.0 (Initial Release)
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**