@saas-maker/foundry-email 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/dist/index.d.mts +77 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +151 -0
- package/dist/index.mjs +114 -0
- package/package.json +24 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple {{variable}} template renderer.
|
|
3
|
+
* No dependencies — just string interpolation.
|
|
4
|
+
*/
|
|
5
|
+
declare function renderTemplate(template: string, data: Record<string, unknown>): string;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @saas-maker/foundry-email
|
|
9
|
+
*
|
|
10
|
+
* Lightweight email sending with Resend/SMTP and auto-tracing via @saas-maker/ops.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { configureEmail, email } from '@saas-maker/foundry-email';
|
|
15
|
+
*
|
|
16
|
+
* configureEmail({
|
|
17
|
+
* provider: 'resend',
|
|
18
|
+
* apiKey: env.RESEND_API_KEY,
|
|
19
|
+
* from: 'noreply@yourdomain.com',
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* await email.send({
|
|
23
|
+
* to: 'user@example.com',
|
|
24
|
+
* subject: 'Welcome!',
|
|
25
|
+
* template: 'Hello {{name}}, welcome to {{product}}!',
|
|
26
|
+
* data: { name: 'Sarthak', product: 'Resume Tailor' },
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
type EmailProvider = 'resend' | 'smtp';
|
|
31
|
+
interface ResendEmailConfig {
|
|
32
|
+
provider: 'resend';
|
|
33
|
+
apiKey: string;
|
|
34
|
+
from: string;
|
|
35
|
+
}
|
|
36
|
+
interface SmtpEmailConfig {
|
|
37
|
+
provider: 'smtp';
|
|
38
|
+
host: string;
|
|
39
|
+
port?: number;
|
|
40
|
+
secure?: boolean;
|
|
41
|
+
auth: {
|
|
42
|
+
user: string;
|
|
43
|
+
pass: string;
|
|
44
|
+
};
|
|
45
|
+
from: string;
|
|
46
|
+
}
|
|
47
|
+
type EmailConfig = ResendEmailConfig | SmtpEmailConfig;
|
|
48
|
+
declare function configureEmail(config: EmailConfig): void;
|
|
49
|
+
interface SendOptions {
|
|
50
|
+
to: string | string[];
|
|
51
|
+
subject: string;
|
|
52
|
+
/** Raw HTML body. If template is provided, this is ignored. */
|
|
53
|
+
html?: string;
|
|
54
|
+
/** Plain text body. */
|
|
55
|
+
text?: string;
|
|
56
|
+
/** Template string with {{variable}} placeholders. Rendered with `data`. */
|
|
57
|
+
template?: string;
|
|
58
|
+
/** Data to interpolate into template. */
|
|
59
|
+
data?: Record<string, unknown>;
|
|
60
|
+
replyTo?: string;
|
|
61
|
+
cc?: string | string[];
|
|
62
|
+
bcc?: string | string[];
|
|
63
|
+
/** Project name for tracing (defaults to 'email') */
|
|
64
|
+
project?: string;
|
|
65
|
+
}
|
|
66
|
+
interface SendResult {
|
|
67
|
+
id: string;
|
|
68
|
+
provider: EmailProvider;
|
|
69
|
+
}
|
|
70
|
+
declare function sendOne(options: SendOptions): Promise<SendResult>;
|
|
71
|
+
declare function sendBatch(messages: SendOptions[]): Promise<SendResult[]>;
|
|
72
|
+
declare const email: {
|
|
73
|
+
send: typeof sendOne;
|
|
74
|
+
batch: typeof sendBatch;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export { type EmailConfig, type EmailProvider, type ResendEmailConfig, type SendOptions, type SendResult, type SmtpEmailConfig, configureEmail, email, renderTemplate };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple {{variable}} template renderer.
|
|
3
|
+
* No dependencies — just string interpolation.
|
|
4
|
+
*/
|
|
5
|
+
declare function renderTemplate(template: string, data: Record<string, unknown>): string;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @saas-maker/foundry-email
|
|
9
|
+
*
|
|
10
|
+
* Lightweight email sending with Resend/SMTP and auto-tracing via @saas-maker/ops.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { configureEmail, email } from '@saas-maker/foundry-email';
|
|
15
|
+
*
|
|
16
|
+
* configureEmail({
|
|
17
|
+
* provider: 'resend',
|
|
18
|
+
* apiKey: env.RESEND_API_KEY,
|
|
19
|
+
* from: 'noreply@yourdomain.com',
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* await email.send({
|
|
23
|
+
* to: 'user@example.com',
|
|
24
|
+
* subject: 'Welcome!',
|
|
25
|
+
* template: 'Hello {{name}}, welcome to {{product}}!',
|
|
26
|
+
* data: { name: 'Sarthak', product: 'Resume Tailor' },
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
type EmailProvider = 'resend' | 'smtp';
|
|
31
|
+
interface ResendEmailConfig {
|
|
32
|
+
provider: 'resend';
|
|
33
|
+
apiKey: string;
|
|
34
|
+
from: string;
|
|
35
|
+
}
|
|
36
|
+
interface SmtpEmailConfig {
|
|
37
|
+
provider: 'smtp';
|
|
38
|
+
host: string;
|
|
39
|
+
port?: number;
|
|
40
|
+
secure?: boolean;
|
|
41
|
+
auth: {
|
|
42
|
+
user: string;
|
|
43
|
+
pass: string;
|
|
44
|
+
};
|
|
45
|
+
from: string;
|
|
46
|
+
}
|
|
47
|
+
type EmailConfig = ResendEmailConfig | SmtpEmailConfig;
|
|
48
|
+
declare function configureEmail(config: EmailConfig): void;
|
|
49
|
+
interface SendOptions {
|
|
50
|
+
to: string | string[];
|
|
51
|
+
subject: string;
|
|
52
|
+
/** Raw HTML body. If template is provided, this is ignored. */
|
|
53
|
+
html?: string;
|
|
54
|
+
/** Plain text body. */
|
|
55
|
+
text?: string;
|
|
56
|
+
/** Template string with {{variable}} placeholders. Rendered with `data`. */
|
|
57
|
+
template?: string;
|
|
58
|
+
/** Data to interpolate into template. */
|
|
59
|
+
data?: Record<string, unknown>;
|
|
60
|
+
replyTo?: string;
|
|
61
|
+
cc?: string | string[];
|
|
62
|
+
bcc?: string | string[];
|
|
63
|
+
/** Project name for tracing (defaults to 'email') */
|
|
64
|
+
project?: string;
|
|
65
|
+
}
|
|
66
|
+
interface SendResult {
|
|
67
|
+
id: string;
|
|
68
|
+
provider: EmailProvider;
|
|
69
|
+
}
|
|
70
|
+
declare function sendOne(options: SendOptions): Promise<SendResult>;
|
|
71
|
+
declare function sendBatch(messages: SendOptions[]): Promise<SendResult[]>;
|
|
72
|
+
declare const email: {
|
|
73
|
+
send: typeof sendOne;
|
|
74
|
+
batch: typeof sendBatch;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export { type EmailConfig, type EmailProvider, type ResendEmailConfig, type SendOptions, type SendResult, type SmtpEmailConfig, configureEmail, email, renderTemplate };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
configureEmail: () => configureEmail,
|
|
34
|
+
email: () => email,
|
|
35
|
+
renderTemplate: () => renderTemplate
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
var import_ops = require("@saas-maker/ops");
|
|
39
|
+
|
|
40
|
+
// src/providers/resend.ts
|
|
41
|
+
async function sendViaResend(config, params) {
|
|
42
|
+
const body = {
|
|
43
|
+
from: config.from,
|
|
44
|
+
to: Array.isArray(params.to) ? params.to : [params.to],
|
|
45
|
+
subject: params.subject
|
|
46
|
+
};
|
|
47
|
+
if (params.html) body.html = params.html;
|
|
48
|
+
if (params.text) body.text = params.text;
|
|
49
|
+
if (params.replyTo) body.reply_to = params.replyTo;
|
|
50
|
+
if (params.cc) body.cc = Array.isArray(params.cc) ? params.cc : [params.cc];
|
|
51
|
+
if (params.bcc) body.bcc = Array.isArray(params.bcc) ? params.bcc : [params.bcc];
|
|
52
|
+
const res = await fetch("https://api.resend.com/emails", {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: {
|
|
55
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
56
|
+
"Content-Type": "application/json"
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify(body)
|
|
59
|
+
});
|
|
60
|
+
if (!res.ok) {
|
|
61
|
+
const text = await res.text();
|
|
62
|
+
throw new Error(`Resend error ${res.status}: ${text}`);
|
|
63
|
+
}
|
|
64
|
+
const data = await res.json();
|
|
65
|
+
return { id: data.id, provider: "resend" };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/providers/smtp.ts
|
|
69
|
+
async function sendViaSmtp(config, params) {
|
|
70
|
+
const nodemailer = await import("nodemailer");
|
|
71
|
+
const transporter = nodemailer.createTransport({
|
|
72
|
+
host: config.host,
|
|
73
|
+
port: config.port ?? 587,
|
|
74
|
+
secure: config.secure ?? false,
|
|
75
|
+
auth: config.auth
|
|
76
|
+
});
|
|
77
|
+
const info = await transporter.sendMail({
|
|
78
|
+
from: config.from,
|
|
79
|
+
to: Array.isArray(params.to) ? params.to.join(", ") : params.to,
|
|
80
|
+
subject: params.subject,
|
|
81
|
+
html: params.html,
|
|
82
|
+
text: params.text,
|
|
83
|
+
replyTo: params.replyTo,
|
|
84
|
+
cc: params.cc,
|
|
85
|
+
bcc: params.bcc
|
|
86
|
+
});
|
|
87
|
+
return { id: info.messageId ?? crypto.randomUUID(), provider: "smtp" };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/render.ts
|
|
91
|
+
function renderTemplate(template, data) {
|
|
92
|
+
return template.replace(/\{\{(\s*[\w.]+\s*)\}\}/g, (_match, key) => {
|
|
93
|
+
const trimmed = key.trim();
|
|
94
|
+
const value = trimmed.split(".").reduce((obj, part) => {
|
|
95
|
+
if (obj && typeof obj === "object") return obj[part];
|
|
96
|
+
return void 0;
|
|
97
|
+
}, data);
|
|
98
|
+
return value != null ? String(value) : "";
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/index.ts
|
|
103
|
+
var _config = null;
|
|
104
|
+
function configureEmail(config) {
|
|
105
|
+
_config = config;
|
|
106
|
+
}
|
|
107
|
+
function getConfig() {
|
|
108
|
+
if (!_config) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
"@saas-maker/foundry-email: call configureEmail() before sending emails"
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return _config;
|
|
114
|
+
}
|
|
115
|
+
async function sendOne(options) {
|
|
116
|
+
const config = getConfig();
|
|
117
|
+
const project = options.project ?? "email";
|
|
118
|
+
const html = options.template ? renderTemplate(options.template, options.data ?? {}) : options.html;
|
|
119
|
+
const params = {
|
|
120
|
+
to: options.to,
|
|
121
|
+
subject: options.subject,
|
|
122
|
+
html,
|
|
123
|
+
text: options.text,
|
|
124
|
+
replyTo: options.replyTo,
|
|
125
|
+
cc: options.cc,
|
|
126
|
+
bcc: options.bcc
|
|
127
|
+
};
|
|
128
|
+
return (0, import_ops.trace)(
|
|
129
|
+
"email:send",
|
|
130
|
+
async () => {
|
|
131
|
+
if (config.provider === "resend") {
|
|
132
|
+
return sendViaResend({ apiKey: config.apiKey, from: config.from }, params);
|
|
133
|
+
}
|
|
134
|
+
return sendViaSmtp(config, params);
|
|
135
|
+
},
|
|
136
|
+
{ project, meta: { to: Array.isArray(options.to) ? options.to[0] : options.to } }
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
async function sendBatch(messages) {
|
|
140
|
+
return Promise.all(messages.map(sendOne));
|
|
141
|
+
}
|
|
142
|
+
var email = {
|
|
143
|
+
send: sendOne,
|
|
144
|
+
batch: sendBatch
|
|
145
|
+
};
|
|
146
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
147
|
+
0 && (module.exports = {
|
|
148
|
+
configureEmail,
|
|
149
|
+
email,
|
|
150
|
+
renderTemplate
|
|
151
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { trace } from "@saas-maker/ops";
|
|
3
|
+
|
|
4
|
+
// src/providers/resend.ts
|
|
5
|
+
async function sendViaResend(config, params) {
|
|
6
|
+
const body = {
|
|
7
|
+
from: config.from,
|
|
8
|
+
to: Array.isArray(params.to) ? params.to : [params.to],
|
|
9
|
+
subject: params.subject
|
|
10
|
+
};
|
|
11
|
+
if (params.html) body.html = params.html;
|
|
12
|
+
if (params.text) body.text = params.text;
|
|
13
|
+
if (params.replyTo) body.reply_to = params.replyTo;
|
|
14
|
+
if (params.cc) body.cc = Array.isArray(params.cc) ? params.cc : [params.cc];
|
|
15
|
+
if (params.bcc) body.bcc = Array.isArray(params.bcc) ? params.bcc : [params.bcc];
|
|
16
|
+
const res = await fetch("https://api.resend.com/emails", {
|
|
17
|
+
method: "POST",
|
|
18
|
+
headers: {
|
|
19
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
20
|
+
"Content-Type": "application/json"
|
|
21
|
+
},
|
|
22
|
+
body: JSON.stringify(body)
|
|
23
|
+
});
|
|
24
|
+
if (!res.ok) {
|
|
25
|
+
const text = await res.text();
|
|
26
|
+
throw new Error(`Resend error ${res.status}: ${text}`);
|
|
27
|
+
}
|
|
28
|
+
const data = await res.json();
|
|
29
|
+
return { id: data.id, provider: "resend" };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/providers/smtp.ts
|
|
33
|
+
async function sendViaSmtp(config, params) {
|
|
34
|
+
const nodemailer = await import("nodemailer");
|
|
35
|
+
const transporter = nodemailer.createTransport({
|
|
36
|
+
host: config.host,
|
|
37
|
+
port: config.port ?? 587,
|
|
38
|
+
secure: config.secure ?? false,
|
|
39
|
+
auth: config.auth
|
|
40
|
+
});
|
|
41
|
+
const info = await transporter.sendMail({
|
|
42
|
+
from: config.from,
|
|
43
|
+
to: Array.isArray(params.to) ? params.to.join(", ") : params.to,
|
|
44
|
+
subject: params.subject,
|
|
45
|
+
html: params.html,
|
|
46
|
+
text: params.text,
|
|
47
|
+
replyTo: params.replyTo,
|
|
48
|
+
cc: params.cc,
|
|
49
|
+
bcc: params.bcc
|
|
50
|
+
});
|
|
51
|
+
return { id: info.messageId ?? crypto.randomUUID(), provider: "smtp" };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/render.ts
|
|
55
|
+
function renderTemplate(template, data) {
|
|
56
|
+
return template.replace(/\{\{(\s*[\w.]+\s*)\}\}/g, (_match, key) => {
|
|
57
|
+
const trimmed = key.trim();
|
|
58
|
+
const value = trimmed.split(".").reduce((obj, part) => {
|
|
59
|
+
if (obj && typeof obj === "object") return obj[part];
|
|
60
|
+
return void 0;
|
|
61
|
+
}, data);
|
|
62
|
+
return value != null ? String(value) : "";
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/index.ts
|
|
67
|
+
var _config = null;
|
|
68
|
+
function configureEmail(config) {
|
|
69
|
+
_config = config;
|
|
70
|
+
}
|
|
71
|
+
function getConfig() {
|
|
72
|
+
if (!_config) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
"@saas-maker/foundry-email: call configureEmail() before sending emails"
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return _config;
|
|
78
|
+
}
|
|
79
|
+
async function sendOne(options) {
|
|
80
|
+
const config = getConfig();
|
|
81
|
+
const project = options.project ?? "email";
|
|
82
|
+
const html = options.template ? renderTemplate(options.template, options.data ?? {}) : options.html;
|
|
83
|
+
const params = {
|
|
84
|
+
to: options.to,
|
|
85
|
+
subject: options.subject,
|
|
86
|
+
html,
|
|
87
|
+
text: options.text,
|
|
88
|
+
replyTo: options.replyTo,
|
|
89
|
+
cc: options.cc,
|
|
90
|
+
bcc: options.bcc
|
|
91
|
+
};
|
|
92
|
+
return trace(
|
|
93
|
+
"email:send",
|
|
94
|
+
async () => {
|
|
95
|
+
if (config.provider === "resend") {
|
|
96
|
+
return sendViaResend({ apiKey: config.apiKey, from: config.from }, params);
|
|
97
|
+
}
|
|
98
|
+
return sendViaSmtp(config, params);
|
|
99
|
+
},
|
|
100
|
+
{ project, meta: { to: Array.isArray(options.to) ? options.to[0] : options.to } }
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
async function sendBatch(messages) {
|
|
104
|
+
return Promise.all(messages.map(sendOne));
|
|
105
|
+
}
|
|
106
|
+
var email = {
|
|
107
|
+
send: sendOne,
|
|
108
|
+
batch: sendBatch
|
|
109
|
+
};
|
|
110
|
+
export {
|
|
111
|
+
configureEmail,
|
|
112
|
+
email,
|
|
113
|
+
renderTemplate
|
|
114
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@saas-maker/foundry-email",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Foundry email — lightweight email sending with Resend/SMTP and auto-tracing via @saas-maker/ops",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": { "import": "./dist/index.mjs", "require": "./dist/index.js", "types": "./dist/index.d.ts" }
|
|
10
|
+
},
|
|
11
|
+
"files": ["dist"],
|
|
12
|
+
"scripts": { "build": "tsup src/index.ts --format esm,cjs --dts" },
|
|
13
|
+
"publishConfig": { "access": "public" },
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@saas-maker/ops": "^0.1.1",
|
|
16
|
+
"nodemailer": "^6.9.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"tsup": "^8.0.0",
|
|
20
|
+
"typescript": "^5.9.0",
|
|
21
|
+
"@types/node": "^22.0.0",
|
|
22
|
+
"@types/nodemailer": "^6.4.0"
|
|
23
|
+
}
|
|
24
|
+
}
|