@mailzeno/core 0.1.0 → 0.1.1

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/dist/index.cjs CHANGED
@@ -100,14 +100,14 @@ async function retry(fn, attempts = 3) {
100
100
 
101
101
  // src/render/render-html.ts
102
102
  function renderHtml(html, text) {
103
- const hasHtml = html && html.trim().length > 0;
104
- const hasText = text && text.trim().length > 0;
105
- if (!hasHtml && !hasText) {
103
+ const cleanHtml = html?.trim();
104
+ const cleanText = text?.trim();
105
+ if (!cleanHtml && !cleanText) {
106
106
  throw new Error("Email must contain html or text");
107
107
  }
108
108
  return {
109
- html: hasHtml ? html : void 0,
110
- text: hasText ? text : void 0
109
+ html: cleanHtml || void 0,
110
+ text: cleanText || void 0
111
111
  };
112
112
  }
113
113
 
@@ -171,83 +171,73 @@ async function renderReact(component) {
171
171
 
172
172
  // src/smtp/send.ts
173
173
  async function sendEmail(smtp, options) {
174
+ validateOptions(options);
175
+ const { html, text } = await resolveContent(options);
176
+ const transporter = getTransporter(smtp);
174
177
  try {
175
- if (!options.from || !options.to || !options.subject) {
176
- throw new ValidationError(
177
- "Missing required fields: from, to, subject"
178
- );
179
- }
180
- let html = options.html;
181
- let text = options.text;
182
- if (options.react) {
183
- try {
184
- const rendered2 = await renderReact(options.react);
185
- html = rendered2.html;
186
- } catch (err) {
187
- throw new RenderError("React email rendering failed", err);
188
- }
189
- }
190
- const rendered = renderHtml(html, text);
191
- const transporter = getTransporter(smtp);
192
178
  const info = await retry(
193
179
  () => transporter.sendMail({
194
180
  from: options.from,
195
181
  to: options.to,
196
182
  subject: options.subject,
197
- html: rendered.html,
198
- text: rendered.text
183
+ html: html || void 0,
184
+ text: text || void 0
199
185
  })
200
186
  );
201
187
  return {
202
- success: true,
203
188
  messageId: info.messageId,
204
189
  accepted: info.accepted,
205
190
  rejected: info.rejected,
206
191
  response: info.response
207
192
  };
208
193
  } catch (err) {
209
- if (err instanceof MailZenoError) {
210
- return {
211
- success: false,
212
- error: err.message
213
- };
214
- }
215
- if (err?.code === "EAUTH") {
216
- return {
217
- success: false,
218
- error: new SMTPAuthError(
219
- "SMTP authentication failed",
220
- err
221
- ).message
222
- };
223
- }
224
- if (err?.code === "ETIMEDOUT") {
225
- return {
226
- success: false,
227
- error: new SMTPTimeoutError(
228
- "SMTP connection timed out",
229
- err
230
- ).message
231
- };
194
+ throw mapSMTPError(err);
195
+ }
196
+ }
197
+ function validateOptions(options) {
198
+ if (!options.from?.trim()) {
199
+ throw new ValidationError("Invalid 'from' address");
200
+ }
201
+ if (!options.subject?.trim()) {
202
+ throw new ValidationError("Subject is required");
203
+ }
204
+ if (!options.to || Array.isArray(options.to) && options.to.length === 0) {
205
+ throw new ValidationError("Invalid 'to' address");
206
+ }
207
+ }
208
+ async function resolveContent(options) {
209
+ switch (options.type) {
210
+ case "react": {
211
+ try {
212
+ const rendered = await renderReact(options.react);
213
+ return renderHtml(rendered.html);
214
+ } catch (err) {
215
+ throw new RenderError("React email rendering failed", err);
216
+ }
232
217
  }
233
- if (err?.code === "ECONNECTION") {
234
- return {
235
- success: false,
236
- error: new SMTPConnectionError(
237
- "SMTP connection failed",
238
- err
239
- ).message
240
- };
218
+ case "raw": {
219
+ return renderHtml(options.html, options.text);
241
220
  }
242
- return {
243
- success: false,
244
- error: new SMTPResponseError(
245
- err?.message || "SMTP sending failed",
246
- err
247
- ).message
248
- };
249
221
  }
250
222
  }
223
+ function mapSMTPError(err) {
224
+ if (err instanceof MailZenoError) {
225
+ return err;
226
+ }
227
+ if (err?.code === "EAUTH") {
228
+ return new SMTPAuthError("SMTP authentication failed", err);
229
+ }
230
+ if (err?.code === "ETIMEDOUT") {
231
+ return new SMTPTimeoutError("SMTP connection timed out", err);
232
+ }
233
+ if (err?.code === "ECONNECTION") {
234
+ return new SMTPConnectionError("SMTP connection failed", err);
235
+ }
236
+ if (err?.responseCode) {
237
+ return new SMTPResponseError(`SMTP error ${err.responseCode}`, err);
238
+ }
239
+ return new SMTPResponseError(err?.message || "SMTP sending failed", err);
240
+ }
251
241
  // Annotate the CommonJS export names for ESM import in node:
252
242
  0 && (module.exports = {
253
243
  sendEmail
package/dist/index.d.cts CHANGED
@@ -1,3 +1,8 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * SMTP configuration (engine-level only)
5
+ */
1
6
  interface SMTPConfig {
2
7
  id: string;
3
8
  host: string;
@@ -11,23 +16,44 @@ interface SMTPConfig {
11
16
  maxConnections?: number;
12
17
  maxMessages?: number;
13
18
  }
14
- interface SendEmailOptions {
19
+ /**
20
+ * Raw HTML email content
21
+ */
22
+ type RawContent = {
23
+ type: "raw";
24
+ html?: string;
25
+ text?: string;
26
+ };
27
+ /**
28
+ * React email content
29
+ */
30
+ type ReactContent = {
31
+ type: "react";
32
+ react: React.ReactElement;
33
+ };
34
+ /**
35
+ * Strict email options
36
+ */
37
+ type SendEmailOptions = {
15
38
  from: string;
16
39
  to: string | string[];
17
40
  subject: string;
18
- html?: string;
19
- text?: string;
20
- react?: React.ReactElement;
21
- }
41
+ } & (RawContent | ReactContent);
42
+ /**
43
+ * Engine success response
44
+ * Throws on failure
45
+ */
22
46
  interface SendEmailResponse {
23
- success: boolean;
24
- messageId?: string;
25
- accepted?: string[];
26
- rejected?: string[];
27
- response?: string;
28
- error?: string;
47
+ messageId: string;
48
+ accepted: string[];
49
+ rejected: string[];
50
+ response: string;
29
51
  }
30
52
 
53
+ /**
54
+ * Core email sending engine
55
+ * Throws errors on failure
56
+ */
31
57
  declare function sendEmail(smtp: SMTPConfig, options: SendEmailOptions): Promise<SendEmailResponse>;
32
58
 
33
59
  export { type SMTPConfig, type SendEmailOptions, type SendEmailResponse, sendEmail };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,8 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * SMTP configuration (engine-level only)
5
+ */
1
6
  interface SMTPConfig {
2
7
  id: string;
3
8
  host: string;
@@ -11,23 +16,44 @@ interface SMTPConfig {
11
16
  maxConnections?: number;
12
17
  maxMessages?: number;
13
18
  }
14
- interface SendEmailOptions {
19
+ /**
20
+ * Raw HTML email content
21
+ */
22
+ type RawContent = {
23
+ type: "raw";
24
+ html?: string;
25
+ text?: string;
26
+ };
27
+ /**
28
+ * React email content
29
+ */
30
+ type ReactContent = {
31
+ type: "react";
32
+ react: React.ReactElement;
33
+ };
34
+ /**
35
+ * Strict email options
36
+ */
37
+ type SendEmailOptions = {
15
38
  from: string;
16
39
  to: string | string[];
17
40
  subject: string;
18
- html?: string;
19
- text?: string;
20
- react?: React.ReactElement;
21
- }
41
+ } & (RawContent | ReactContent);
42
+ /**
43
+ * Engine success response
44
+ * Throws on failure
45
+ */
22
46
  interface SendEmailResponse {
23
- success: boolean;
24
- messageId?: string;
25
- accepted?: string[];
26
- rejected?: string[];
27
- response?: string;
28
- error?: string;
47
+ messageId: string;
48
+ accepted: string[];
49
+ rejected: string[];
50
+ response: string;
29
51
  }
30
52
 
53
+ /**
54
+ * Core email sending engine
55
+ * Throws errors on failure
56
+ */
31
57
  declare function sendEmail(smtp: SMTPConfig, options: SendEmailOptions): Promise<SendEmailResponse>;
32
58
 
33
59
  export { type SMTPConfig, type SendEmailOptions, type SendEmailResponse, sendEmail };
package/dist/index.js CHANGED
@@ -64,14 +64,14 @@ async function retry(fn, attempts = 3) {
64
64
 
65
65
  // src/render/render-html.ts
66
66
  function renderHtml(html, text) {
67
- const hasHtml = html && html.trim().length > 0;
68
- const hasText = text && text.trim().length > 0;
69
- if (!hasHtml && !hasText) {
67
+ const cleanHtml = html?.trim();
68
+ const cleanText = text?.trim();
69
+ if (!cleanHtml && !cleanText) {
70
70
  throw new Error("Email must contain html or text");
71
71
  }
72
72
  return {
73
- html: hasHtml ? html : void 0,
74
- text: hasText ? text : void 0
73
+ html: cleanHtml || void 0,
74
+ text: cleanText || void 0
75
75
  };
76
76
  }
77
77
 
@@ -135,83 +135,73 @@ async function renderReact(component) {
135
135
 
136
136
  // src/smtp/send.ts
137
137
  async function sendEmail(smtp, options) {
138
+ validateOptions(options);
139
+ const { html, text } = await resolveContent(options);
140
+ const transporter = getTransporter(smtp);
138
141
  try {
139
- if (!options.from || !options.to || !options.subject) {
140
- throw new ValidationError(
141
- "Missing required fields: from, to, subject"
142
- );
143
- }
144
- let html = options.html;
145
- let text = options.text;
146
- if (options.react) {
147
- try {
148
- const rendered2 = await renderReact(options.react);
149
- html = rendered2.html;
150
- } catch (err) {
151
- throw new RenderError("React email rendering failed", err);
152
- }
153
- }
154
- const rendered = renderHtml(html, text);
155
- const transporter = getTransporter(smtp);
156
142
  const info = await retry(
157
143
  () => transporter.sendMail({
158
144
  from: options.from,
159
145
  to: options.to,
160
146
  subject: options.subject,
161
- html: rendered.html,
162
- text: rendered.text
147
+ html: html || void 0,
148
+ text: text || void 0
163
149
  })
164
150
  );
165
151
  return {
166
- success: true,
167
152
  messageId: info.messageId,
168
153
  accepted: info.accepted,
169
154
  rejected: info.rejected,
170
155
  response: info.response
171
156
  };
172
157
  } catch (err) {
173
- if (err instanceof MailZenoError) {
174
- return {
175
- success: false,
176
- error: err.message
177
- };
178
- }
179
- if (err?.code === "EAUTH") {
180
- return {
181
- success: false,
182
- error: new SMTPAuthError(
183
- "SMTP authentication failed",
184
- err
185
- ).message
186
- };
187
- }
188
- if (err?.code === "ETIMEDOUT") {
189
- return {
190
- success: false,
191
- error: new SMTPTimeoutError(
192
- "SMTP connection timed out",
193
- err
194
- ).message
195
- };
158
+ throw mapSMTPError(err);
159
+ }
160
+ }
161
+ function validateOptions(options) {
162
+ if (!options.from?.trim()) {
163
+ throw new ValidationError("Invalid 'from' address");
164
+ }
165
+ if (!options.subject?.trim()) {
166
+ throw new ValidationError("Subject is required");
167
+ }
168
+ if (!options.to || Array.isArray(options.to) && options.to.length === 0) {
169
+ throw new ValidationError("Invalid 'to' address");
170
+ }
171
+ }
172
+ async function resolveContent(options) {
173
+ switch (options.type) {
174
+ case "react": {
175
+ try {
176
+ const rendered = await renderReact(options.react);
177
+ return renderHtml(rendered.html);
178
+ } catch (err) {
179
+ throw new RenderError("React email rendering failed", err);
180
+ }
196
181
  }
197
- if (err?.code === "ECONNECTION") {
198
- return {
199
- success: false,
200
- error: new SMTPConnectionError(
201
- "SMTP connection failed",
202
- err
203
- ).message
204
- };
182
+ case "raw": {
183
+ return renderHtml(options.html, options.text);
205
184
  }
206
- return {
207
- success: false,
208
- error: new SMTPResponseError(
209
- err?.message || "SMTP sending failed",
210
- err
211
- ).message
212
- };
213
185
  }
214
186
  }
187
+ function mapSMTPError(err) {
188
+ if (err instanceof MailZenoError) {
189
+ return err;
190
+ }
191
+ if (err?.code === "EAUTH") {
192
+ return new SMTPAuthError("SMTP authentication failed", err);
193
+ }
194
+ if (err?.code === "ETIMEDOUT") {
195
+ return new SMTPTimeoutError("SMTP connection timed out", err);
196
+ }
197
+ if (err?.code === "ECONNECTION") {
198
+ return new SMTPConnectionError("SMTP connection failed", err);
199
+ }
200
+ if (err?.responseCode) {
201
+ return new SMTPResponseError(`SMTP error ${err.responseCode}`, err);
202
+ }
203
+ return new SMTPResponseError(err?.message || "SMTP sending failed", err);
204
+ }
215
205
  export {
216
206
  sendEmail
217
207
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mailzeno/core",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "SMTP engine for MailZeno",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,8 +10,7 @@
10
10
  "exports": {
11
11
  ".": {
12
12
  "import": "./dist/index.js",
13
- "require": "./dist/index.cjs",
14
- "types": "./dist/index.d.ts"
13
+ "require": "./dist/index.cjs"
15
14
  }
16
15
  },
17
16
  "files": [