emdash-aws-ses 1.0.3 → 1.0.5

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 ADDED
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ awsSesPlugin: () => awsSesPlugin
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+ function awsSesPlugin() {
27
+ return {
28
+ id: "emdash-aws-ses",
29
+ version: "1.0.0",
30
+ format: "standard",
31
+ entrypoint: "emdash-aws-ses/sandbox",
32
+ options: {},
33
+ capabilities: ["hooks.email-transport:register"],
34
+ allowedHosts: [],
35
+ adminPages: [
36
+ { path: "/settings", label: "AWS SES Settings", icon: "mail" },
37
+ { path: "/test", label: "AWS SES Test & Status", icon: "send" }
38
+ ]
39
+ };
40
+ }
41
+ // Annotate the CommonJS export names for ESM import in node:
42
+ 0 && (module.exports = {
43
+ awsSesPlugin
44
+ });
@@ -0,0 +1,2 @@
1
+ import type { PluginDescriptor } from "emdash";
2
+ export declare function awsSesPlugin(): PluginDescriptor;
@@ -1,6 +1,5 @@
1
- import type { PluginDescriptor } from "emdash";
2
-
3
- export function awsSesPlugin(): PluginDescriptor {
1
+ // src/index.ts
2
+ function awsSesPlugin() {
4
3
  return {
5
4
  id: "emdash-aws-ses",
6
5
  version: "1.0.0",
@@ -11,7 +10,10 @@ export function awsSesPlugin(): PluginDescriptor {
11
10
  allowedHosts: [],
12
11
  adminPages: [
13
12
  { path: "/settings", label: "AWS SES Settings", icon: "mail" },
14
- { path: "/test", label: "AWS SES Test & Status", icon: "send" },
15
- ],
13
+ { path: "/test", label: "AWS SES Test & Status", icon: "send" }
14
+ ]
16
15
  };
17
16
  }
17
+ export {
18
+ awsSesPlugin
19
+ };
@@ -0,0 +1,390 @@
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/sandbox-entry.ts
31
+ var sandbox_entry_exports = {};
32
+ __export(sandbox_entry_exports, {
33
+ default: () => sandbox_entry_default
34
+ });
35
+ module.exports = __toCommonJS(sandbox_entry_exports);
36
+ var import_nodemailer = __toESM(require("nodemailer"), 1);
37
+ async function getSettings(ctx) {
38
+ const defaults = {
39
+ smtpHost: "email-smtp.us-east-1.amazonaws.com",
40
+ smtpPort: 587,
41
+ region: "us-east-1",
42
+ defaultFrom: "",
43
+ fromName: "",
44
+ replyTo: "",
45
+ smtpUser: "",
46
+ smtpPassword: ""
47
+ };
48
+ const [smtpHost, smtpPortRaw, smtpUser, smtpPassword, region, defaultFrom, fromName, replyTo] = await Promise.all([
49
+ ctx.kv.get("settings:smtpHost"),
50
+ ctx.kv.get("settings:smtpPort"),
51
+ ctx.kv.get("settings:smtpUser"),
52
+ ctx.kv.get("settings:smtpPassword"),
53
+ ctx.kv.get("settings:region"),
54
+ ctx.kv.get("settings:defaultFrom"),
55
+ ctx.kv.get("settings:fromName"),
56
+ ctx.kv.get("settings:replyTo")
57
+ ]);
58
+ return {
59
+ smtpHost: smtpHost ?? defaults.smtpHost,
60
+ smtpPort: Number(smtpPortRaw ?? defaults.smtpPort),
61
+ smtpUser: smtpUser ?? defaults.smtpUser,
62
+ smtpPassword: smtpPassword ?? defaults.smtpPassword,
63
+ region: region ?? defaults.region,
64
+ defaultFrom: defaultFrom ?? defaults.defaultFrom,
65
+ fromName: fromName ?? defaults.fromName,
66
+ replyTo: replyTo ?? defaults.replyTo
67
+ };
68
+ }
69
+ function createTransporter(settings) {
70
+ const secure = settings.smtpPort === 465;
71
+ const requireTLS = settings.smtpPort === 587 || settings.smtpPort === 25 || settings.smtpPort === 2587;
72
+ return import_nodemailer.default.createTransport({
73
+ host: settings.smtpHost,
74
+ port: settings.smtpPort,
75
+ secure,
76
+ auth: {
77
+ user: settings.smtpUser,
78
+ pass: settings.smtpPassword
79
+ },
80
+ tls: {
81
+ rejectUnauthorized: true
82
+ },
83
+ requireTLS,
84
+ connectionTimeout: 1e4,
85
+ greetingTimeout: 1e4,
86
+ socketTimeout: 1e4
87
+ });
88
+ }
89
+ async function validateSMTP(ctx) {
90
+ try {
91
+ const settings = await getSettings(ctx);
92
+ if (!settings.smtpHost || !settings.smtpUser || !settings.smtpPassword) {
93
+ return { ok: false, error: "SMTP settings are incomplete. Please configure host, user, and password." };
94
+ }
95
+ const transporter = createTransporter(settings);
96
+ await transporter.verify();
97
+ return { ok: true };
98
+ } catch (err) {
99
+ const message = err instanceof Error ? err.message : String(err);
100
+ return { ok: false, error: `Connection failed: ${message}` };
101
+ }
102
+ }
103
+ async function sendTestEmail(ctx, recipient) {
104
+ try {
105
+ const settings = await getSettings(ctx);
106
+ if (!settings.defaultFrom) {
107
+ return { ok: false, error: "Default From address is not configured." };
108
+ }
109
+ const transporter = createTransporter(settings);
110
+ await transporter.sendMail({
111
+ from: settings.fromName ? `"${settings.fromName}" <${settings.defaultFrom}>` : settings.defaultFrom,
112
+ to: recipient,
113
+ subject: "Test email from EmDash + AWS SES",
114
+ text: "This is a test email sent via Amazon SES from your EmDash CMS.",
115
+ html: "<p>This is a test email sent via <strong>Amazon SES</strong> from your EmDash CMS.</p>",
116
+ replyTo: settings.replyTo || void 0
117
+ });
118
+ return { ok: true };
119
+ } catch (err) {
120
+ const message = err instanceof Error ? err.message : String(err);
121
+ return { ok: false, error: `Send failed: ${message}` };
122
+ }
123
+ }
124
+ async function renderSettingsPage(ctx) {
125
+ const settings = await getSettings(ctx);
126
+ return {
127
+ blocks: [
128
+ { type: "header", text: "AWS SES Settings" },
129
+ { type: "section", text: "Configure your Amazon SES SMTP transport. All settings are stored in the plugin KV store." },
130
+ { type: "divider" },
131
+ {
132
+ type: "form",
133
+ block_id: "settings",
134
+ fields: [
135
+ {
136
+ type: "text_input",
137
+ action_id: "smtpHost",
138
+ label: "SMTP Host",
139
+ initial_value: settings.smtpHost,
140
+ placeholder: "email-smtp.us-east-1.amazonaws.com"
141
+ },
142
+ {
143
+ type: "number_input",
144
+ action_id: "smtpPort",
145
+ label: "SMTP Port",
146
+ initial_value: settings.smtpPort,
147
+ min: 1,
148
+ max: 65535
149
+ },
150
+ {
151
+ type: "text_input",
152
+ action_id: "smtpUser",
153
+ label: "SMTP Username",
154
+ initial_value: settings.smtpUser,
155
+ placeholder: "AWS SES SMTP username (IAM Access Key ID)"
156
+ },
157
+ {
158
+ type: "secret_input",
159
+ action_id: "smtpPassword",
160
+ label: "SMTP Password",
161
+ initial_value: ""
162
+ },
163
+ {
164
+ type: "text_input",
165
+ action_id: "region",
166
+ label: "AWS Region",
167
+ initial_value: settings.region,
168
+ placeholder: "us-east-1"
169
+ },
170
+ {
171
+ type: "text_input",
172
+ action_id: "defaultFrom",
173
+ label: "Default From Address",
174
+ initial_value: settings.defaultFrom,
175
+ placeholder: "noreply@example.com"
176
+ },
177
+ {
178
+ type: "text_input",
179
+ action_id: "fromName",
180
+ label: "From Name",
181
+ initial_value: settings.fromName,
182
+ placeholder: "My Site"
183
+ },
184
+ {
185
+ type: "text_input",
186
+ action_id: "replyTo",
187
+ label: "Reply-To Address",
188
+ initial_value: settings.replyTo,
189
+ placeholder: "support@example.com"
190
+ }
191
+ ],
192
+ submit: { label: "Save Settings", action_id: "save_settings" }
193
+ },
194
+ { type: "divider" },
195
+ { type: "header", text: "Email Provider Activation" },
196
+ { type: "section", text: "To activate AWS SES as your active email provider, go to Settings > Email in the EmDash admin and select emdash-aws-ses from the list. This routes all EmDash email (magic links, invites, notifications) through AWS SES." }
197
+ ]
198
+ };
199
+ }
200
+ function getDefaultEndpoint(region) {
201
+ return `email-smtp.${region}.amazonaws.com`;
202
+ }
203
+ async function saveSettings(ctx, values) {
204
+ const currentSettings = await getSettings(ctx);
205
+ if (typeof values.region === "string" && values.region.length > 0 && values.region !== currentSettings.region) {
206
+ const oldEndpoint = getDefaultEndpoint(currentSettings.region);
207
+ if (!currentSettings.smtpHost || currentSettings.smtpHost === oldEndpoint) {
208
+ values.smtpHost = getDefaultEndpoint(values.region);
209
+ }
210
+ }
211
+ if (typeof values.smtpHost === "string" && values.smtpHost.length > 0) {
212
+ await ctx.kv.set("settings:smtpHost", values.smtpHost);
213
+ }
214
+ if (typeof values.smtpPort === "number" && values.smtpPort >= 1 && values.smtpPort <= 65535) {
215
+ await ctx.kv.set("settings:smtpPort", values.smtpPort);
216
+ }
217
+ if (typeof values.smtpUser === "string" && values.smtpUser.length > 0) {
218
+ await ctx.kv.set("settings:smtpUser", values.smtpUser);
219
+ }
220
+ if (typeof values.smtpPassword === "string" && values.smtpPassword.length > 0) {
221
+ await ctx.kv.set("settings:smtpPassword", values.smtpPassword);
222
+ }
223
+ if (typeof values.region === "string" && values.region.length > 0) {
224
+ await ctx.kv.set("settings:region", values.region);
225
+ }
226
+ if (typeof values.defaultFrom === "string") {
227
+ await ctx.kv.set("settings:defaultFrom", values.defaultFrom);
228
+ }
229
+ if (typeof values.fromName === "string") {
230
+ await ctx.kv.set("settings:fromName", values.fromName);
231
+ }
232
+ if (typeof values.replyTo === "string") {
233
+ await ctx.kv.set("settings:replyTo", values.replyTo);
234
+ }
235
+ }
236
+ async function renderTestPage(ctx, validation, sendResult) {
237
+ const settings = await getSettings(ctx);
238
+ const blocks = [
239
+ { type: "header", text: "AWS SES Test & Status" },
240
+ { type: "section", text: `Current endpoint: ${settings.smtpHost}:${settings.smtpPort} (${settings.region})` },
241
+ { type: "divider" }
242
+ ];
243
+ if (validation) {
244
+ if (validation.ok) {
245
+ blocks.push({
246
+ type: "banner",
247
+ title: "Connection verified",
248
+ description: "SMTP credentials are valid.",
249
+ variant: "default"
250
+ });
251
+ } else {
252
+ blocks.push({
253
+ type: "banner",
254
+ title: "Connection failed",
255
+ description: validation.error,
256
+ variant: "error"
257
+ });
258
+ }
259
+ }
260
+ blocks.push(
261
+ { type: "section", text: "Test your SMTP connection and verify your credentials are correct." },
262
+ {
263
+ type: "actions",
264
+ elements: [
265
+ {
266
+ type: "button",
267
+ label: "Test Connection",
268
+ action_id: "validate"
269
+ }
270
+ ]
271
+ },
272
+ { type: "divider" },
273
+ { type: "header", text: "Send Test Email" },
274
+ { type: "section", text: "Send a test email to verify the full transport pipeline. Enter a recipient address below." }
275
+ );
276
+ if (sendResult) {
277
+ if (sendResult.ok) {
278
+ blocks.push({
279
+ type: "banner",
280
+ title: "Email sent",
281
+ description: `Test email sent successfully to ${sendResult.recipient}.`,
282
+ variant: "default"
283
+ });
284
+ } else {
285
+ blocks.push({
286
+ type: "banner",
287
+ title: "Email send failed",
288
+ description: sendResult.error,
289
+ variant: "error"
290
+ });
291
+ }
292
+ }
293
+ blocks.push({
294
+ type: "form",
295
+ block_id: "test_form",
296
+ fields: [
297
+ {
298
+ type: "text_input",
299
+ action_id: "recipient",
300
+ label: "Recipient Email",
301
+ placeholder: "you@example.com",
302
+ initial_value: sendResult?.ok ? "" : void 0
303
+ }
304
+ ],
305
+ submit: { label: "Send Test", action_id: "send_test" }
306
+ });
307
+ return { blocks };
308
+ }
309
+ var sandbox_entry_default = {
310
+ hooks: {
311
+ "plugin:install": async (_event, ctx) => {
312
+ await ctx.kv.set("settings:smtpHost", "email-smtp.us-east-1.amazonaws.com");
313
+ await ctx.kv.set("settings:smtpPort", 587);
314
+ await ctx.kv.set("settings:region", "us-east-1");
315
+ ctx.log.info("AWS SES plugin defaults seeded");
316
+ },
317
+ "email:deliver": {
318
+ exclusive: true,
319
+ handler: async ({ message, source }, ctx) => {
320
+ const settings = await getSettings(ctx);
321
+ const transporter = createTransporter(settings);
322
+ await transporter.sendMail({
323
+ from: settings.fromName ? `"${settings.fromName}" <${settings.defaultFrom}>` : settings.defaultFrom,
324
+ to: message.to,
325
+ subject: message.subject,
326
+ text: message.text,
327
+ html: message.html,
328
+ replyTo: settings.replyTo || void 0
329
+ });
330
+ ctx.log.info(`Email sent via AWS SES to ${message.to}`, { source });
331
+ }
332
+ }
333
+ },
334
+ routes: {
335
+ admin: {
336
+ handler: async (routeCtx, ctx) => {
337
+ const interaction = routeCtx.input;
338
+ if (interaction.type === "page_load" && (!interaction.page || interaction.page === "/")) {
339
+ return renderSettingsPage(ctx);
340
+ }
341
+ if (interaction.type === "page_load" && interaction.page === "/settings") {
342
+ return renderSettingsPage(ctx);
343
+ }
344
+ if (interaction.type === "form_submit" && interaction.action_id === "save_settings") {
345
+ await saveSettings(ctx, interaction.values ?? {});
346
+ const settingsPage = await renderSettingsPage(ctx);
347
+ const settingsBlocks = settingsPage.blocks;
348
+ return {
349
+ blocks: [
350
+ settingsBlocks[0],
351
+ {
352
+ type: "banner",
353
+ title: "Settings saved",
354
+ description: "Your AWS SES configuration has been updated successfully.",
355
+ variant: "default"
356
+ },
357
+ ...settingsBlocks.slice(1)
358
+ ],
359
+ toast: { message: "Settings saved successfully", type: "success" }
360
+ };
361
+ }
362
+ if (interaction.type === "page_load" && interaction.page === "/test") {
363
+ return renderTestPage(ctx);
364
+ }
365
+ if (interaction.type === "block_action" && interaction.action_id === "validate") {
366
+ const result = await validateSMTP(ctx);
367
+ return {
368
+ ...await renderTestPage(ctx, result),
369
+ toast: result.ok ? { message: "Connection verified successfully", type: "success" } : { message: result.error ?? "Validation failed", type: "error" }
370
+ };
371
+ }
372
+ if (interaction.type === "form_submit" && interaction.action_id === "send_test") {
373
+ const recipient = interaction.values?.recipient?.trim();
374
+ if (!recipient) {
375
+ return {
376
+ ...await renderTestPage(ctx),
377
+ toast: { message: "Please enter a recipient email", type: "error" }
378
+ };
379
+ }
380
+ const result = await sendTestEmail(ctx, recipient);
381
+ return {
382
+ ...await renderTestPage(ctx, null, { ...result, recipient }),
383
+ toast: result.ok ? { message: `Test email sent to ${recipient}`, type: "success" } : { message: result.error ?? "Send failed", type: "error" }
384
+ };
385
+ }
386
+ return { blocks: [] };
387
+ }
388
+ }
389
+ }
390
+ };
@@ -0,0 +1,127 @@
1
+ import type { SandboxedRouteContext, PluginContext, EmailDeliverEvent, LifecycleEvent } from "emdash/plugin";
2
+ declare const _default: {
3
+ hooks: {
4
+ "plugin:install": (_event: LifecycleEvent, ctx: PluginContext) => Promise<void>;
5
+ "email:deliver": {
6
+ exclusive: true;
7
+ handler: ({ message, source }: EmailDeliverEvent, ctx: PluginContext) => Promise<void>;
8
+ };
9
+ };
10
+ routes: {
11
+ admin: {
12
+ handler: (routeCtx: SandboxedRouteContext, ctx: PluginContext) => Promise<{
13
+ blocks: ({
14
+ type: string;
15
+ text: string;
16
+ block_id?: undefined;
17
+ fields?: undefined;
18
+ submit?: undefined;
19
+ } | {
20
+ text?: undefined;
21
+ type: string;
22
+ block_id?: undefined;
23
+ fields?: undefined;
24
+ submit?: undefined;
25
+ } | {
26
+ text?: undefined;
27
+ type: string;
28
+ block_id: string;
29
+ fields: ({
30
+ type: string;
31
+ action_id: string;
32
+ label: string;
33
+ initial_value: string;
34
+ placeholder: string;
35
+ min?: undefined;
36
+ max?: undefined;
37
+ } | {
38
+ placeholder?: undefined;
39
+ type: string;
40
+ action_id: string;
41
+ label: string;
42
+ initial_value: number;
43
+ min: number;
44
+ max: number;
45
+ } | {
46
+ placeholder?: undefined;
47
+ min?: undefined;
48
+ max?: undefined;
49
+ type: string;
50
+ action_id: string;
51
+ label: string;
52
+ initial_value: string;
53
+ })[];
54
+ submit: {
55
+ label: string;
56
+ action_id: string;
57
+ };
58
+ })[];
59
+ } | {
60
+ blocks: Record<string, unknown>[];
61
+ } | {
62
+ blocks: ({
63
+ type: string;
64
+ text: string;
65
+ block_id?: undefined;
66
+ fields?: undefined;
67
+ submit?: undefined;
68
+ } | {
69
+ text?: undefined;
70
+ type: string;
71
+ block_id?: undefined;
72
+ fields?: undefined;
73
+ submit?: undefined;
74
+ } | {
75
+ text?: undefined;
76
+ type: string;
77
+ block_id: string;
78
+ fields: ({
79
+ type: string;
80
+ action_id: string;
81
+ label: string;
82
+ initial_value: string;
83
+ placeholder: string;
84
+ min?: undefined;
85
+ max?: undefined;
86
+ } | {
87
+ placeholder?: undefined;
88
+ type: string;
89
+ action_id: string;
90
+ label: string;
91
+ initial_value: number;
92
+ min: number;
93
+ max: number;
94
+ } | {
95
+ placeholder?: undefined;
96
+ min?: undefined;
97
+ max?: undefined;
98
+ type: string;
99
+ action_id: string;
100
+ label: string;
101
+ initial_value: string;
102
+ })[];
103
+ submit: {
104
+ label: string;
105
+ action_id: string;
106
+ };
107
+ } | {
108
+ type: string;
109
+ title: string;
110
+ description: string;
111
+ variant: string;
112
+ })[];
113
+ toast: {
114
+ message: string;
115
+ type: string;
116
+ };
117
+ } | {
118
+ blocks: Record<string, unknown>[];
119
+ toast: {
120
+ message: string;
121
+ type: string;
122
+ };
123
+ }>;
124
+ };
125
+ };
126
+ };
127
+ export default _default;
@@ -1,20 +1,6 @@
1
- import type { SandboxedPlugin, SandboxedRouteContext, PluginContext, EmailDeliverEvent, LifecycleEvent } from "emdash/plugin";
1
+ // src/sandbox-entry.ts
2
2
  import nodemailer from "nodemailer";
3
- import type { Transporter } from "nodemailer";
4
- import type { BlockInteraction } from "@emdash-cms/blocks";
5
-
6
- interface SesSettings {
7
- smtpHost: string;
8
- smtpPort: number;
9
- smtpUser: string;
10
- smtpPassword: string;
11
- region: string;
12
- defaultFrom: string;
13
- fromName: string;
14
- replyTo: string;
15
- }
16
-
17
- async function getSettings(ctx: PluginContext): Promise<SesSettings> {
3
+ async function getSettings(ctx) {
18
4
  const defaults = {
19
5
  smtpHost: "email-smtp.us-east-1.amazonaws.com",
20
6
  smtpPort: 587,
@@ -23,20 +9,18 @@ async function getSettings(ctx: PluginContext): Promise<SesSettings> {
23
9
  fromName: "",
24
10
  replyTo: "",
25
11
  smtpUser: "",
26
- smtpPassword: "",
12
+ smtpPassword: ""
27
13
  };
28
-
29
14
  const [smtpHost, smtpPortRaw, smtpUser, smtpPassword, region, defaultFrom, fromName, replyTo] = await Promise.all([
30
- ctx.kv.get<string>("settings:smtpHost"),
31
- ctx.kv.get<number>("settings:smtpPort"),
32
- ctx.kv.get<string>("settings:smtpUser"),
33
- ctx.kv.get<string>("settings:smtpPassword"),
34
- ctx.kv.get<string>("settings:region"),
35
- ctx.kv.get<string>("settings:defaultFrom"),
36
- ctx.kv.get<string>("settings:fromName"),
37
- ctx.kv.get<string>("settings:replyTo"),
15
+ ctx.kv.get("settings:smtpHost"),
16
+ ctx.kv.get("settings:smtpPort"),
17
+ ctx.kv.get("settings:smtpUser"),
18
+ ctx.kv.get("settings:smtpPassword"),
19
+ ctx.kv.get("settings:region"),
20
+ ctx.kv.get("settings:defaultFrom"),
21
+ ctx.kv.get("settings:fromName"),
22
+ ctx.kv.get("settings:replyTo")
38
23
  ]);
39
-
40
24
  return {
41
25
  smtpHost: smtpHost ?? defaults.smtpHost,
42
26
  smtpPort: Number(smtpPortRaw ?? defaults.smtpPort),
@@ -45,33 +29,30 @@ async function getSettings(ctx: PluginContext): Promise<SesSettings> {
45
29
  region: region ?? defaults.region,
46
30
  defaultFrom: defaultFrom ?? defaults.defaultFrom,
47
31
  fromName: fromName ?? defaults.fromName,
48
- replyTo: replyTo ?? defaults.replyTo,
32
+ replyTo: replyTo ?? defaults.replyTo
49
33
  };
50
34
  }
51
-
52
- function createTransporter(settings: SesSettings): Transporter {
35
+ function createTransporter(settings) {
53
36
  const secure = settings.smtpPort === 465;
54
37
  const requireTLS = settings.smtpPort === 587 || settings.smtpPort === 25 || settings.smtpPort === 2587;
55
-
56
38
  return nodemailer.createTransport({
57
39
  host: settings.smtpHost,
58
40
  port: settings.smtpPort,
59
41
  secure,
60
42
  auth: {
61
43
  user: settings.smtpUser,
62
- pass: settings.smtpPassword,
44
+ pass: settings.smtpPassword
63
45
  },
64
46
  tls: {
65
- rejectUnauthorized: true,
47
+ rejectUnauthorized: true
66
48
  },
67
49
  requireTLS,
68
- connectionTimeout: 10000,
69
- greetingTimeout: 10000,
70
- socketTimeout: 10000,
50
+ connectionTimeout: 1e4,
51
+ greetingTimeout: 1e4,
52
+ socketTimeout: 1e4
71
53
  });
72
54
  }
73
-
74
- async function validateSMTP(ctx: PluginContext): Promise<{ ok: boolean; error?: string }> {
55
+ async function validateSMTP(ctx) {
75
56
  try {
76
57
  const settings = await getSettings(ctx);
77
58
  if (!settings.smtpHost || !settings.smtpUser || !settings.smtpPassword) {
@@ -85,8 +66,7 @@ async function validateSMTP(ctx: PluginContext): Promise<{ ok: boolean; error?:
85
66
  return { ok: false, error: `Connection failed: ${message}` };
86
67
  }
87
68
  }
88
-
89
- async function sendTestEmail(ctx: PluginContext, recipient: string): Promise<{ ok: boolean; error?: string }> {
69
+ async function sendTestEmail(ctx, recipient) {
90
70
  try {
91
71
  const settings = await getSettings(ctx);
92
72
  if (!settings.defaultFrom) {
@@ -99,7 +79,7 @@ async function sendTestEmail(ctx: PluginContext, recipient: string): Promise<{ o
99
79
  subject: "Test email from EmDash + AWS SES",
100
80
  text: "This is a test email sent via Amazon SES from your EmDash CMS.",
101
81
  html: "<p>This is a test email sent via <strong>Amazon SES</strong> from your EmDash CMS.</p>",
102
- replyTo: settings.replyTo || undefined,
82
+ replyTo: settings.replyTo || void 0
103
83
  });
104
84
  return { ok: true };
105
85
  } catch (err) {
@@ -107,10 +87,8 @@ async function sendTestEmail(ctx: PluginContext, recipient: string): Promise<{ o
107
87
  return { ok: false, error: `Send failed: ${message}` };
108
88
  }
109
89
  }
110
-
111
- async function renderSettingsPage(ctx: PluginContext) {
90
+ async function renderSettingsPage(ctx) {
112
91
  const settings = await getSettings(ctx);
113
-
114
92
  return {
115
93
  blocks: [
116
94
  { type: "header", text: "AWS SES Settings" },
@@ -125,7 +103,7 @@ async function renderSettingsPage(ctx: PluginContext) {
125
103
  action_id: "smtpHost",
126
104
  label: "SMTP Host",
127
105
  initial_value: settings.smtpHost,
128
- placeholder: "email-smtp.us-east-1.amazonaws.com",
106
+ placeholder: "email-smtp.us-east-1.amazonaws.com"
129
107
  },
130
108
  {
131
109
  type: "number_input",
@@ -133,73 +111,69 @@ async function renderSettingsPage(ctx: PluginContext) {
133
111
  label: "SMTP Port",
134
112
  initial_value: settings.smtpPort,
135
113
  min: 1,
136
- max: 65535,
114
+ max: 65535
137
115
  },
138
116
  {
139
117
  type: "text_input",
140
118
  action_id: "smtpUser",
141
119
  label: "SMTP Username",
142
120
  initial_value: settings.smtpUser,
143
- placeholder: "AWS SES SMTP username (IAM Access Key ID)",
121
+ placeholder: "AWS SES SMTP username (IAM Access Key ID)"
144
122
  },
145
123
  {
146
124
  type: "secret_input",
147
125
  action_id: "smtpPassword",
148
126
  label: "SMTP Password",
149
- initial_value: "",
127
+ initial_value: ""
150
128
  },
151
129
  {
152
130
  type: "text_input",
153
131
  action_id: "region",
154
132
  label: "AWS Region",
155
133
  initial_value: settings.region,
156
- placeholder: "us-east-1",
134
+ placeholder: "us-east-1"
157
135
  },
158
136
  {
159
137
  type: "text_input",
160
138
  action_id: "defaultFrom",
161
139
  label: "Default From Address",
162
140
  initial_value: settings.defaultFrom,
163
- placeholder: "noreply@example.com",
141
+ placeholder: "noreply@example.com"
164
142
  },
165
143
  {
166
144
  type: "text_input",
167
145
  action_id: "fromName",
168
146
  label: "From Name",
169
147
  initial_value: settings.fromName,
170
- placeholder: "My Site",
148
+ placeholder: "My Site"
171
149
  },
172
150
  {
173
151
  type: "text_input",
174
152
  action_id: "replyTo",
175
153
  label: "Reply-To Address",
176
154
  initial_value: settings.replyTo,
177
- placeholder: "support@example.com",
178
- },
155
+ placeholder: "support@example.com"
156
+ }
179
157
  ],
180
- submit: { label: "Save Settings", action_id: "save_settings" },
158
+ submit: { label: "Save Settings", action_id: "save_settings" }
181
159
  },
182
160
  { type: "divider" },
183
161
  { type: "header", text: "Email Provider Activation" },
184
- { type: "section", text: "To activate AWS SES as your active email provider, go to Settings > Email in the EmDash admin and select emdash-aws-ses from the list. This routes all EmDash email (magic links, invites, notifications) through AWS SES." },
185
- ],
162
+ { type: "section", text: "To activate AWS SES as your active email provider, go to Settings > Email in the EmDash admin and select emdash-aws-ses from the list. This routes all EmDash email (magic links, invites, notifications) through AWS SES." }
163
+ ]
186
164
  };
187
165
  }
188
-
189
- function getDefaultEndpoint(region: string): string {
166
+ function getDefaultEndpoint(region) {
190
167
  return `email-smtp.${region}.amazonaws.com`;
191
168
  }
192
-
193
- async function saveSettings(ctx: PluginContext, values: Record<string, unknown>) {
169
+ async function saveSettings(ctx, values) {
194
170
  const currentSettings = await getSettings(ctx);
195
-
196
171
  if (typeof values.region === "string" && values.region.length > 0 && values.region !== currentSettings.region) {
197
172
  const oldEndpoint = getDefaultEndpoint(currentSettings.region);
198
173
  if (!currentSettings.smtpHost || currentSettings.smtpHost === oldEndpoint) {
199
174
  values.smtpHost = getDefaultEndpoint(values.region);
200
175
  }
201
176
  }
202
-
203
177
  if (typeof values.smtpHost === "string" && values.smtpHost.length > 0) {
204
178
  await ctx.kv.set("settings:smtpHost", values.smtpHost);
205
179
  }
@@ -225,38 +199,30 @@ async function saveSettings(ctx: PluginContext, values: Record<string, unknown>)
225
199
  await ctx.kv.set("settings:replyTo", values.replyTo);
226
200
  }
227
201
  }
228
-
229
- async function renderTestPage(
230
- ctx: PluginContext,
231
- validation?: { ok: boolean; error?: string } | null,
232
- sendResult?: { ok: boolean; error?: string; recipient?: string } | null,
233
- ) {
202
+ async function renderTestPage(ctx, validation, sendResult) {
234
203
  const settings = await getSettings(ctx);
235
-
236
- const blocks: Array<Record<string, unknown>> = [
204
+ const blocks = [
237
205
  { type: "header", text: "AWS SES Test & Status" },
238
206
  { type: "section", text: `Current endpoint: ${settings.smtpHost}:${settings.smtpPort} (${settings.region})` },
239
- { type: "divider" },
207
+ { type: "divider" }
240
208
  ];
241
-
242
209
  if (validation) {
243
210
  if (validation.ok) {
244
211
  blocks.push({
245
212
  type: "banner",
246
213
  title: "Connection verified",
247
214
  description: "SMTP credentials are valid.",
248
- variant: "default",
215
+ variant: "default"
249
216
  });
250
217
  } else {
251
218
  blocks.push({
252
219
  type: "banner",
253
220
  title: "Connection failed",
254
221
  description: validation.error,
255
- variant: "error",
222
+ variant: "error"
256
223
  });
257
224
  }
258
225
  }
259
-
260
226
  blocks.push(
261
227
  { type: "section", text: "Test your SMTP connection and verify your credentials are correct." },
262
228
  {
@@ -265,33 +231,31 @@ async function renderTestPage(
265
231
  {
266
232
  type: "button",
267
233
  label: "Test Connection",
268
- action_id: "validate",
269
- },
270
- ],
234
+ action_id: "validate"
235
+ }
236
+ ]
271
237
  },
272
238
  { type: "divider" },
273
239
  { type: "header", text: "Send Test Email" },
274
- { type: "section", text: "Send a test email to verify the full transport pipeline. Enter a recipient address below." },
240
+ { type: "section", text: "Send a test email to verify the full transport pipeline. Enter a recipient address below." }
275
241
  );
276
-
277
242
  if (sendResult) {
278
243
  if (sendResult.ok) {
279
244
  blocks.push({
280
245
  type: "banner",
281
246
  title: "Email sent",
282
247
  description: `Test email sent successfully to ${sendResult.recipient}.`,
283
- variant: "default",
248
+ variant: "default"
284
249
  });
285
250
  } else {
286
251
  blocks.push({
287
252
  type: "banner",
288
253
  title: "Email send failed",
289
254
  description: sendResult.error,
290
- variant: "error",
255
+ variant: "error"
291
256
  });
292
257
  }
293
258
  }
294
-
295
259
  blocks.push({
296
260
  type: "form",
297
261
  block_id: "test_form",
@@ -301,57 +265,48 @@ async function renderTestPage(
301
265
  action_id: "recipient",
302
266
  label: "Recipient Email",
303
267
  placeholder: "you@example.com",
304
- initial_value: sendResult?.ok ? "" : undefined,
305
- },
268
+ initial_value: sendResult?.ok ? "" : void 0
269
+ }
306
270
  ],
307
- submit: { label: "Send Test", action_id: "send_test" },
271
+ submit: { label: "Send Test", action_id: "send_test" }
308
272
  });
309
-
310
273
  return { blocks };
311
274
  }
312
-
313
- export default {
275
+ var sandbox_entry_default = {
314
276
  hooks: {
315
- "plugin:install": async (_event: LifecycleEvent, ctx: PluginContext) => {
277
+ "plugin:install": async (_event, ctx) => {
316
278
  await ctx.kv.set("settings:smtpHost", "email-smtp.us-east-1.amazonaws.com");
317
279
  await ctx.kv.set("settings:smtpPort", 587);
318
280
  await ctx.kv.set("settings:region", "us-east-1");
319
281
  ctx.log.info("AWS SES plugin defaults seeded");
320
282
  },
321
-
322
283
  "email:deliver": {
323
284
  exclusive: true,
324
- handler: async ({ message, source }: EmailDeliverEvent, ctx: PluginContext) => {
285
+ handler: async ({ message, source }, ctx) => {
325
286
  const settings = await getSettings(ctx);
326
287
  const transporter = createTransporter(settings);
327
-
328
288
  await transporter.sendMail({
329
289
  from: settings.fromName ? `"${settings.fromName}" <${settings.defaultFrom}>` : settings.defaultFrom,
330
290
  to: message.to,
331
291
  subject: message.subject,
332
292
  text: message.text,
333
293
  html: message.html,
334
- replyTo: settings.replyTo || undefined,
294
+ replyTo: settings.replyTo || void 0
335
295
  });
336
-
337
296
  ctx.log.info(`Email sent via AWS SES to ${message.to}`, { source });
338
- },
339
- },
297
+ }
298
+ }
340
299
  },
341
-
342
300
  routes: {
343
301
  admin: {
344
- handler: async (routeCtx: SandboxedRouteContext, ctx: PluginContext) => {
345
- const interaction = routeCtx.input as BlockInteraction;
346
-
302
+ handler: async (routeCtx, ctx) => {
303
+ const interaction = routeCtx.input;
347
304
  if (interaction.type === "page_load" && (!interaction.page || interaction.page === "/")) {
348
305
  return renderSettingsPage(ctx);
349
306
  }
350
-
351
307
  if (interaction.type === "page_load" && interaction.page === "/settings") {
352
308
  return renderSettingsPage(ctx);
353
309
  }
354
-
355
310
  if (interaction.type === "form_submit" && interaction.action_id === "save_settings") {
356
311
  await saveSettings(ctx, interaction.values ?? {});
357
312
  const settingsPage = await renderSettingsPage(ctx);
@@ -363,47 +318,42 @@ export default {
363
318
  type: "banner",
364
319
  title: "Settings saved",
365
320
  description: "Your AWS SES configuration has been updated successfully.",
366
- variant: "default",
321
+ variant: "default"
367
322
  },
368
- ...settingsBlocks.slice(1),
323
+ ...settingsBlocks.slice(1)
369
324
  ],
370
- toast: { message: "Settings saved successfully", type: "success" },
325
+ toast: { message: "Settings saved successfully", type: "success" }
371
326
  };
372
327
  }
373
-
374
328
  if (interaction.type === "page_load" && interaction.page === "/test") {
375
329
  return renderTestPage(ctx);
376
330
  }
377
-
378
331
  if (interaction.type === "block_action" && interaction.action_id === "validate") {
379
332
  const result = await validateSMTP(ctx);
380
333
  return {
381
- ...(await renderTestPage(ctx, result)),
382
- toast: result.ok
383
- ? { message: "Connection verified successfully", type: "success" }
384
- : { message: result.error ?? "Validation failed", type: "error" },
334
+ ...await renderTestPage(ctx, result),
335
+ toast: result.ok ? { message: "Connection verified successfully", type: "success" } : { message: result.error ?? "Validation failed", type: "error" }
385
336
  };
386
337
  }
387
-
388
338
  if (interaction.type === "form_submit" && interaction.action_id === "send_test") {
389
- const recipient = (interaction.values?.recipient as string)?.trim();
339
+ const recipient = interaction.values?.recipient?.trim();
390
340
  if (!recipient) {
391
341
  return {
392
- ...(await renderTestPage(ctx)),
393
- toast: { message: "Please enter a recipient email", type: "error" },
342
+ ...await renderTestPage(ctx),
343
+ toast: { message: "Please enter a recipient email", type: "error" }
394
344
  };
395
345
  }
396
346
  const result = await sendTestEmail(ctx, recipient);
397
347
  return {
398
- ...(await renderTestPage(ctx, null, { ...result, recipient })),
399
- toast: result.ok
400
- ? { message: `Test email sent to ${recipient}`, type: "success" }
401
- : { message: result.error ?? "Send failed", type: "error" },
348
+ ...await renderTestPage(ctx, null, { ...result, recipient }),
349
+ toast: result.ok ? { message: `Test email sent to ${recipient}`, type: "success" } : { message: result.error ?? "Send failed", type: "error" }
402
350
  };
403
351
  }
404
-
405
352
  return { blocks: [] };
406
- },
407
- },
408
- },
409
- } satisfies SandboxedPlugin;
353
+ }
354
+ }
355
+ }
356
+ };
357
+ export {
358
+ sandbox_entry_default as default
359
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emdash-aws-ses",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Amazon SES SMTP email transport plugin for EmDash CMS",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -19,6 +19,9 @@
19
19
  },
20
20
  "homepage": "https://github.com/ab6162/emdash-aws-ses#readme",
21
21
  "type": "module",
22
+ "files": [
23
+ "dist"
24
+ ],
22
25
  "main": "./dist/index.cjs",
23
26
  "module": "./dist/index.js",
24
27
  "types": "./dist/index.d.ts",
@@ -35,10 +38,10 @@
35
38
  }
36
39
  },
37
40
  "scripts": {
38
- "build": "tsup src/index.ts src/sandbox-entry.ts --format cjs,esm --clean"
41
+ "build": "tsup src/index.ts src/sandbox-entry.ts --format cjs,esm --clean && tsc --emitDeclarationOnly"
39
42
  },
40
43
  "peerDependencies": {
41
- "emdash": "^0.29.0"
44
+ "emdash": "^0.37.0"
42
45
  },
43
46
  "dependencies": {
44
47
  "nodemailer": "^9.0.3"
package/.gitattributes DELETED
@@ -1,2 +0,0 @@
1
- # Auto detect text files and perform LF normalization
2
- * text=auto
package/tsconfig.json DELETED
@@ -1,14 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "strict": true,
7
- "noImplicitAny": false, // <--- Añade esto para silenciar los 'any' implícitos
8
- "esModuleInterop": true,
9
- "skipLibCheck": true,
10
- "declaration": true,
11
- "outDir": "./dist"
12
- },
13
- "include": ["src"]
14
- }