@step-func-emailer/handlers 0.2.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.
Files changed (136) hide show
  1. package/dist/handlers/__tests__/bounce-handler.test.d.ts +2 -0
  2. package/dist/handlers/__tests__/bounce-handler.test.d.ts.map +1 -0
  3. package/dist/handlers/__tests__/bounce-handler.test.js +126 -0
  4. package/dist/handlers/__tests__/bounce-handler.test.js.map +1 -0
  5. package/dist/handlers/__tests__/check-condition.test.d.ts +2 -0
  6. package/dist/handlers/__tests__/check-condition.test.d.ts.map +1 -0
  7. package/dist/handlers/__tests__/check-condition.test.js +140 -0
  8. package/dist/handlers/__tests__/check-condition.test.js.map +1 -0
  9. package/dist/handlers/__tests__/engagement-handler.test.d.ts +2 -0
  10. package/dist/handlers/__tests__/engagement-handler.test.d.ts.map +1 -0
  11. package/dist/handlers/__tests__/engagement-handler.test.js +139 -0
  12. package/dist/handlers/__tests__/engagement-handler.test.js.map +1 -0
  13. package/dist/handlers/__tests__/send-email.test.d.ts +2 -0
  14. package/dist/handlers/__tests__/send-email.test.d.ts.map +1 -0
  15. package/dist/handlers/__tests__/send-email.test.js +205 -0
  16. package/dist/handlers/__tests__/send-email.test.js.map +1 -0
  17. package/dist/handlers/__tests__/unsubscribe.test.d.ts +2 -0
  18. package/dist/handlers/__tests__/unsubscribe.test.d.ts.map +1 -0
  19. package/dist/handlers/__tests__/unsubscribe.test.js +79 -0
  20. package/dist/handlers/__tests__/unsubscribe.test.js.map +1 -0
  21. package/dist/handlers/bounce-handler.d.ts +3 -0
  22. package/dist/handlers/bounce-handler.d.ts.map +1 -0
  23. package/dist/handlers/bounce-handler.js +55 -0
  24. package/dist/handlers/bounce-handler.js.map +1 -0
  25. package/dist/handlers/check-condition.d.ts +3 -0
  26. package/dist/handlers/check-condition.d.ts.map +1 -0
  27. package/dist/handlers/check-condition.js +74 -0
  28. package/dist/handlers/check-condition.js.map +1 -0
  29. package/dist/handlers/engagement-handler.d.ts +3 -0
  30. package/dist/handlers/engagement-handler.d.ts.map +1 -0
  31. package/dist/handlers/engagement-handler.js +98 -0
  32. package/dist/handlers/engagement-handler.js.map +1 -0
  33. package/dist/handlers/send-email.d.ts +5 -0
  34. package/dist/handlers/send-email.d.ts.map +1 -0
  35. package/dist/handlers/send-email.js +137 -0
  36. package/dist/handlers/send-email.js.map +1 -0
  37. package/dist/handlers/unsubscribe.d.ts +5 -0
  38. package/dist/handlers/unsubscribe.d.ts.map +1 -0
  39. package/dist/handlers/unsubscribe.js +53 -0
  40. package/dist/handlers/unsubscribe.js.map +1 -0
  41. package/dist/index.d.ts +8 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +50 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/lib/__tests__/display-names.test.d.ts +2 -0
  46. package/dist/lib/__tests__/display-names.test.d.ts.map +1 -0
  47. package/dist/lib/__tests__/display-names.test.js +49 -0
  48. package/dist/lib/__tests__/display-names.test.js.map +1 -0
  49. package/dist/lib/__tests__/dynamo-client.test.d.ts +2 -0
  50. package/dist/lib/__tests__/dynamo-client.test.d.ts.map +1 -0
  51. package/dist/lib/__tests__/dynamo-client.test.js +229 -0
  52. package/dist/lib/__tests__/dynamo-client.test.js.map +1 -0
  53. package/dist/lib/__tests__/execution-stopper.test.d.ts +2 -0
  54. package/dist/lib/__tests__/execution-stopper.test.d.ts.map +1 -0
  55. package/dist/lib/__tests__/execution-stopper.test.js +56 -0
  56. package/dist/lib/__tests__/execution-stopper.test.js.map +1 -0
  57. package/dist/lib/__tests__/ses-sender.test.d.ts +2 -0
  58. package/dist/lib/__tests__/ses-sender.test.d.ts.map +1 -0
  59. package/dist/lib/__tests__/ses-sender.test.js +66 -0
  60. package/dist/lib/__tests__/ses-sender.test.js.map +1 -0
  61. package/dist/lib/__tests__/ssm-config.test.d.ts +2 -0
  62. package/dist/lib/__tests__/ssm-config.test.d.ts.map +1 -0
  63. package/dist/lib/__tests__/ssm-config.test.js +73 -0
  64. package/dist/lib/__tests__/ssm-config.test.js.map +1 -0
  65. package/dist/lib/__tests__/template-renderer.test.d.ts +2 -0
  66. package/dist/lib/__tests__/template-renderer.test.d.ts.map +1 -0
  67. package/dist/lib/__tests__/template-renderer.test.js +79 -0
  68. package/dist/lib/__tests__/template-renderer.test.js.map +1 -0
  69. package/dist/lib/__tests__/unsubscribe-token.test.d.ts +2 -0
  70. package/dist/lib/__tests__/unsubscribe-token.test.d.ts.map +1 -0
  71. package/dist/lib/__tests__/unsubscribe-token.test.js +74 -0
  72. package/dist/lib/__tests__/unsubscribe-token.test.js.map +1 -0
  73. package/dist/lib/display-names.d.ts +5 -0
  74. package/dist/lib/display-names.d.ts.map +1 -0
  75. package/dist/lib/display-names.js +46 -0
  76. package/dist/lib/display-names.js.map +1 -0
  77. package/dist/lib/dynamo-client.d.ts +13 -0
  78. package/dist/lib/dynamo-client.d.ts.map +1 -0
  79. package/dist/lib/dynamo-client.js +211 -0
  80. package/dist/lib/dynamo-client.js.map +1 -0
  81. package/dist/lib/execution-stopper.d.ts +2 -0
  82. package/dist/lib/execution-stopper.d.ts.map +1 -0
  83. package/dist/lib/execution-stopper.js +38 -0
  84. package/dist/lib/execution-stopper.js.map +1 -0
  85. package/dist/lib/logger.d.ts +8 -0
  86. package/dist/lib/logger.d.ts.map +1 -0
  87. package/dist/lib/logger.js +13 -0
  88. package/dist/lib/logger.js.map +1 -0
  89. package/dist/lib/ses-sender.d.ts +13 -0
  90. package/dist/lib/ses-sender.d.ts.map +1 -0
  91. package/dist/lib/ses-sender.js +86 -0
  92. package/dist/lib/ses-sender.js.map +1 -0
  93. package/dist/lib/ses-suppression.d.ts +2 -0
  94. package/dist/lib/ses-suppression.d.ts.map +1 -0
  95. package/dist/lib/ses-suppression.js +24 -0
  96. package/dist/lib/ses-suppression.js.map +1 -0
  97. package/dist/lib/ssm-config.d.ts +14 -0
  98. package/dist/lib/ssm-config.d.ts.map +1 -0
  99. package/dist/lib/ssm-config.js +54 -0
  100. package/dist/lib/ssm-config.js.map +1 -0
  101. package/dist/lib/template-renderer.d.ts +9 -0
  102. package/dist/lib/template-renderer.d.ts.map +1 -0
  103. package/dist/lib/template-renderer.js +36 -0
  104. package/dist/lib/template-renderer.js.map +1 -0
  105. package/dist/lib/unsubscribe-token.d.ts +13 -0
  106. package/dist/lib/unsubscribe-token.d.ts.map +1 -0
  107. package/dist/lib/unsubscribe-token.js +37 -0
  108. package/dist/lib/unsubscribe-token.js.map +1 -0
  109. package/package.json +41 -0
  110. package/src/handlers/__tests__/bounce-handler.test.ts +173 -0
  111. package/src/handlers/__tests__/check-condition.test.ts +172 -0
  112. package/src/handlers/__tests__/engagement-handler.test.ts +161 -0
  113. package/src/handlers/__tests__/send-email.test.ts +279 -0
  114. package/src/handlers/__tests__/unsubscribe.test.ts +111 -0
  115. package/src/handlers/bounce-handler.ts +91 -0
  116. package/src/handlers/check-condition.ts +77 -0
  117. package/src/handlers/engagement-handler.ts +169 -0
  118. package/src/handlers/send-email.ts +184 -0
  119. package/src/handlers/unsubscribe.ts +70 -0
  120. package/src/index.ts +10 -0
  121. package/src/lib/__tests__/display-names.test.ts +52 -0
  122. package/src/lib/__tests__/dynamo-client.test.ts +346 -0
  123. package/src/lib/__tests__/execution-stopper.test.ts +68 -0
  124. package/src/lib/__tests__/ses-sender.test.ts +85 -0
  125. package/src/lib/__tests__/ssm-config.test.ts +85 -0
  126. package/src/lib/__tests__/template-renderer.test.ts +96 -0
  127. package/src/lib/__tests__/unsubscribe-token.test.ts +79 -0
  128. package/src/lib/display-names.ts +54 -0
  129. package/src/lib/dynamo-client.ts +301 -0
  130. package/src/lib/execution-stopper.ts +40 -0
  131. package/src/lib/logger.ts +10 -0
  132. package/src/lib/ses-sender.ts +102 -0
  133. package/src/lib/ses-suppression.ts +30 -0
  134. package/src/lib/ssm-config.ts +81 -0
  135. package/src/lib/template-renderer.ts +52 -0
  136. package/src/lib/unsubscribe-token.ts +53 -0
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSubscriberProfile = getSubscriberProfile;
4
+ exports.extractAttributes = extractAttributes;
5
+ exports.upsertSubscriberProfile = upsertSubscriberProfile;
6
+ exports.getExecution = getExecution;
7
+ exports.putExecution = putExecution;
8
+ exports.deleteExecution = deleteExecution;
9
+ exports.getAllExecutions = getAllExecutions;
10
+ exports.writeSendLog = writeSendLog;
11
+ exports.hasBeenSent = hasBeenSent;
12
+ exports.writeSuppression = writeSuppression;
13
+ exports.setProfileFlag = setProfileFlag;
14
+ const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
15
+ const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
16
+ const shared_1 = require("@step-func-emailer/shared");
17
+ const logger_js_1 = require("./logger.js");
18
+ const logger = (0, logger_js_1.createLogger)("dynamo-client");
19
+ const dynamo = new client_dynamodb_1.DynamoDBClient({});
20
+ // ── Subscriber profile ──────────────────────────────────────────────────────
21
+ async function getSubscriberProfile(tableName, email) {
22
+ logger.debug("Getting subscriber profile", { email });
23
+ const result = await dynamo.send(new client_dynamodb_1.GetItemCommand({
24
+ TableName: tableName,
25
+ Key: (0, util_dynamodb_1.marshall)({ PK: (0, shared_1.subscriberPK)(email), SK: shared_1.PROFILE_SK }),
26
+ }));
27
+ const profile = result.Item ? (0, util_dynamodb_1.unmarshall)(result.Item) : null;
28
+ logger.debug("Subscriber profile result", {
29
+ email,
30
+ found: !!profile,
31
+ unsubscribed: profile?.unsubscribed,
32
+ suppressed: profile?.suppressed,
33
+ });
34
+ return profile;
35
+ }
36
+ const SYSTEM_KEYS = new Set([
37
+ "PK",
38
+ "SK",
39
+ "email",
40
+ "firstName",
41
+ "unsubscribed",
42
+ "suppressed",
43
+ "createdAt",
44
+ "updatedAt",
45
+ ]);
46
+ function extractAttributes(profile) {
47
+ const attrs = {};
48
+ for (const [key, value] of Object.entries(profile)) {
49
+ if (!SYSTEM_KEYS.has(key)) {
50
+ attrs[key] = value;
51
+ }
52
+ }
53
+ return attrs;
54
+ }
55
+ async function upsertSubscriberProfile(tableName, subscriber) {
56
+ const now = new Date().toISOString();
57
+ const pk = (0, shared_1.subscriberPK)(subscriber.email);
58
+ const rawAttrs = subscriber.attributes ?? {};
59
+ // Filter out system keys to avoid duplicate paths in the UpdateExpression
60
+ const attrs = {};
61
+ for (const [key, value] of Object.entries(rawAttrs)) {
62
+ if (!SYSTEM_KEYS.has(key)) {
63
+ attrs[key] = value;
64
+ }
65
+ }
66
+ logger.info("Upserting subscriber profile", {
67
+ email: subscriber.email,
68
+ firstName: subscriber.firstName,
69
+ attributeCount: Object.keys(attrs).length,
70
+ });
71
+ // Build SET expressions — never overwrite unsubscribed or suppressed
72
+ const expressionParts = ["email = :email", "firstName = :firstName", "updatedAt = :updatedAt"];
73
+ const expressionValues = {
74
+ ":email": subscriber.email,
75
+ ":firstName": subscriber.firstName,
76
+ ":updatedAt": now,
77
+ ":createdAt": now,
78
+ ":defaultFalse": false,
79
+ };
80
+ const expressionNames = {};
81
+ // Write each attribute as a top-level column
82
+ for (const [key, value] of Object.entries(attrs)) {
83
+ expressionParts.push(`#attr_${key} = :attr_${key}`);
84
+ expressionNames[`#attr_${key}`] = key;
85
+ expressionValues[`:attr_${key}`] = value;
86
+ }
87
+ await dynamo.send(new client_dynamodb_1.UpdateItemCommand({
88
+ TableName: tableName,
89
+ Key: (0, util_dynamodb_1.marshall)({ PK: pk, SK: shared_1.PROFILE_SK }),
90
+ UpdateExpression: `SET ${expressionParts.join(", ")}, createdAt = if_not_exists(createdAt, :createdAt), unsubscribed = if_not_exists(unsubscribed, :defaultFalse), suppressed = if_not_exists(suppressed, :defaultFalse)`,
91
+ ExpressionAttributeValues: (0, util_dynamodb_1.marshall)(expressionValues),
92
+ ...(Object.keys(expressionNames).length > 0
93
+ ? { ExpressionAttributeNames: expressionNames }
94
+ : {}),
95
+ }));
96
+ }
97
+ // ── Active executions ───────────────────────────────────────────────────────
98
+ async function getExecution(tableName, email, sequenceId) {
99
+ logger.debug("Getting execution", { email, sequenceId });
100
+ const result = await dynamo.send(new client_dynamodb_1.GetItemCommand({
101
+ TableName: tableName,
102
+ Key: (0, util_dynamodb_1.marshall)({
103
+ PK: (0, shared_1.subscriberPK)(email),
104
+ SK: (0, shared_1.executionSK)(sequenceId),
105
+ }),
106
+ }));
107
+ return result.Item ? (0, util_dynamodb_1.unmarshall)(result.Item) : null;
108
+ }
109
+ async function putExecution(tableName, email, sequenceId, executionArn) {
110
+ logger.info("Storing execution", { email, sequenceId, executionArn });
111
+ await dynamo.send(new client_dynamodb_1.PutItemCommand({
112
+ TableName: tableName,
113
+ Item: (0, util_dynamodb_1.marshall)({
114
+ PK: (0, shared_1.subscriberPK)(email),
115
+ SK: (0, shared_1.executionSK)(sequenceId),
116
+ executionArn,
117
+ sequenceId,
118
+ startedAt: new Date().toISOString(),
119
+ }),
120
+ }));
121
+ }
122
+ async function deleteExecution(tableName, email, sequenceId) {
123
+ logger.info("Deleting execution", { email, sequenceId });
124
+ await dynamo.send(new client_dynamodb_1.DeleteItemCommand({
125
+ TableName: tableName,
126
+ Key: (0, util_dynamodb_1.marshall)({
127
+ PK: (0, shared_1.subscriberPK)(email),
128
+ SK: (0, shared_1.executionSK)(sequenceId),
129
+ }),
130
+ }));
131
+ }
132
+ async function getAllExecutions(tableName, email) {
133
+ logger.debug("Querying all executions", { email });
134
+ const result = await dynamo.send(new client_dynamodb_1.QueryCommand({
135
+ TableName: tableName,
136
+ KeyConditionExpression: "PK = :pk AND begins_with(SK, :prefix)",
137
+ ExpressionAttributeValues: (0, util_dynamodb_1.marshall)({
138
+ ":pk": (0, shared_1.subscriberPK)(email),
139
+ ":prefix": shared_1.EXEC_SK_PREFIX,
140
+ }),
141
+ }));
142
+ const executions = (result.Items ?? []).map((item) => (0, util_dynamodb_1.unmarshall)(item));
143
+ logger.debug("Found executions", { email, count: executions.length });
144
+ return executions;
145
+ }
146
+ // ── Send log ────────────────────────────────────────────────────────────────
147
+ async function writeSendLog(tableName, email, log) {
148
+ const now = new Date();
149
+ const ttl = Math.floor(now.getTime() / 1000) + shared_1.SEND_LOG_TTL_DAYS * 86400;
150
+ logger.info("Writing send log", {
151
+ email,
152
+ templateKey: log.templateKey,
153
+ sequenceId: log.sequenceId,
154
+ sesMessageId: log.sesMessageId,
155
+ });
156
+ await dynamo.send(new client_dynamodb_1.PutItemCommand({
157
+ TableName: tableName,
158
+ Item: (0, util_dynamodb_1.marshall)({
159
+ PK: (0, shared_1.subscriberPK)(email),
160
+ SK: (0, shared_1.sentSK)(now.toISOString()),
161
+ ...log,
162
+ ttl,
163
+ }),
164
+ }));
165
+ }
166
+ async function hasBeenSent(tableName, email, templateKey) {
167
+ logger.debug("Checking if template has been sent", { email, templateKey });
168
+ const result = await dynamo.send(new client_dynamodb_1.QueryCommand({
169
+ TableName: tableName,
170
+ KeyConditionExpression: "PK = :pk AND begins_with(SK, :prefix)",
171
+ FilterExpression: "templateKey = :templateKey",
172
+ ExpressionAttributeValues: (0, util_dynamodb_1.marshall)({
173
+ ":pk": (0, shared_1.subscriberPK)(email),
174
+ ":prefix": shared_1.SENT_SK_PREFIX,
175
+ ":templateKey": templateKey,
176
+ }),
177
+ Limit: 1,
178
+ }));
179
+ const sent = (result.Count ?? 0) > 0;
180
+ logger.debug("Has been sent result", { email, templateKey, sent });
181
+ return sent;
182
+ }
183
+ // ── Suppression ─────────────────────────────────────────────────────────────
184
+ async function writeSuppression(tableName, email, reason, bounceType, sesNotificationId) {
185
+ logger.warn("Writing suppression record", { email, reason, bounceType, sesNotificationId });
186
+ await dynamo.send(new client_dynamodb_1.PutItemCommand({
187
+ TableName: tableName,
188
+ Item: (0, util_dynamodb_1.marshall)({
189
+ PK: (0, shared_1.subscriberPK)(email),
190
+ SK: shared_1.SUPPRESSION_SK,
191
+ reason,
192
+ ...(bounceType ? { bounceType } : {}),
193
+ sesNotificationId,
194
+ recordedAt: new Date().toISOString(),
195
+ }),
196
+ }));
197
+ }
198
+ async function setProfileFlag(tableName, email, flag) {
199
+ logger.warn("Setting profile flag", { email, flag });
200
+ await dynamo.send(new client_dynamodb_1.UpdateItemCommand({
201
+ TableName: tableName,
202
+ Key: (0, util_dynamodb_1.marshall)({ PK: (0, shared_1.subscriberPK)(email), SK: shared_1.PROFILE_SK }),
203
+ UpdateExpression: `SET #flag = :val, updatedAt = :now`,
204
+ ExpressionAttributeNames: { "#flag": flag },
205
+ ExpressionAttributeValues: (0, util_dynamodb_1.marshall)({
206
+ ":val": true,
207
+ ":now": new Date().toISOString(),
208
+ }),
209
+ }));
210
+ }
211
+ //# sourceMappingURL=dynamo-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamo-client.js","sourceRoot":"","sources":["../../src/lib/dynamo-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function stopAllExecutions(tableName: string, email: string): Promise<void>;
2
+ //# sourceMappingURL=execution-stopper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution-stopper.d.ts","sourceRoot":"","sources":["../../src/lib/execution-stopper.ts"],"names":[],"mappings":"AAOA,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCvF"}
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stopAllExecutions = stopAllExecutions;
4
+ const client_sfn_1 = require("@aws-sdk/client-sfn");
5
+ const dynamo_client_js_1 = require("./dynamo-client.js");
6
+ const logger_js_1 = require("./logger.js");
7
+ const logger = (0, logger_js_1.createLogger)("execution-stopper");
8
+ const sfn = new client_sfn_1.SFNClient({});
9
+ async function stopAllExecutions(tableName, email) {
10
+ const executions = await (0, dynamo_client_js_1.getAllExecutions)(tableName, email);
11
+ logger.info("Stopping all executions for subscriber", {
12
+ email,
13
+ executionCount: executions.length,
14
+ });
15
+ await Promise.all(executions.map(async (exec) => {
16
+ try {
17
+ await sfn.send(new client_sfn_1.StopExecutionCommand({
18
+ executionArn: exec.executionArn,
19
+ cause: "Subscriber unsubscribed or suppressed",
20
+ }));
21
+ logger.info("Stopped execution", {
22
+ email,
23
+ sequenceId: exec.sequenceId,
24
+ executionArn: exec.executionArn,
25
+ });
26
+ }
27
+ catch (err) {
28
+ logger.warn("Failed to stop execution (may already be stopped)", {
29
+ email,
30
+ sequenceId: exec.sequenceId,
31
+ executionArn: exec.executionArn,
32
+ error: err instanceof Error ? err.message : String(err),
33
+ });
34
+ }
35
+ await (0, dynamo_client_js_1.deleteExecution)(tableName, email, exec.sequenceId);
36
+ }));
37
+ }
38
+ //# sourceMappingURL=execution-stopper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution-stopper.js","sourceRoot":"","sources":["../../src/lib/execution-stopper.ts"],"names":[],"mappings":";;AAOA,8CAgCC;AAvCD,oDAAsE;AACtE,yDAAuE;AACvE,2CAA2C;AAE3C,MAAM,MAAM,GAAG,IAAA,wBAAY,EAAC,mBAAmB,CAAC,CAAC;AACjD,MAAM,GAAG,GAAG,IAAI,sBAAS,CAAC,EAAE,CAAC,CAAC;AAEvB,KAAK,UAAU,iBAAiB,CAAC,SAAiB,EAAE,KAAa;IACtE,MAAM,UAAU,GAAG,MAAM,IAAA,mCAAgB,EAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAC5D,MAAM,CAAC,IAAI,CAAC,wCAAwC,EAAE;QACpD,KAAK;QACL,cAAc,EAAE,UAAU,CAAC,MAAM;KAClC,CAAC,CAAC;IAEH,MAAM,OAAO,CAAC,GAAG,CACf,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QAC5B,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,IAAI,CACZ,IAAI,iCAAoB,CAAC;gBACvB,YAAY,EAAE,IAAI,CAAC,YAAY;gBAC/B,KAAK,EAAE,uCAAuC;aAC/C,CAAC,CACH,CAAC;YACF,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE;gBAC/B,KAAK;gBACL,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,YAAY,EAAE,IAAI,CAAC,YAAY;aAChC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CAAC,mDAAmD,EAAE;gBAC/D,KAAK;gBACL,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,YAAY,EAAE,IAAI,CAAC,YAAY;gBAC/B,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAC;QACL,CAAC;QACD,MAAM,IAAA,kCAAe,EAAC,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAC3D,CAAC,CAAC,CACH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { Logger } from "@aws-lambda-powertools/logger";
2
+ /**
3
+ * Creates a Logger instance scoped to a service name.
4
+ * LOG_LEVEL is controlled via environment variable (default: INFO).
5
+ * Set LOG_LEVEL=DEBUG for verbose tracing.
6
+ */
7
+ export declare function createLogger(serviceName: string): Logger;
8
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAEvD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAExD"}
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createLogger = createLogger;
4
+ const logger_1 = require("@aws-lambda-powertools/logger");
5
+ /**
6
+ * Creates a Logger instance scoped to a service name.
7
+ * LOG_LEVEL is controlled via environment variable (default: INFO).
8
+ * Set LOG_LEVEL=DEBUG for verbose tracing.
9
+ */
10
+ function createLogger(serviceName) {
11
+ return new logger_1.Logger({ serviceName });
12
+ }
13
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":";;AAOA,oCAEC;AATD,0DAAuD;AAEvD;;;;GAIG;AACH,SAAgB,YAAY,CAAC,WAAmB;IAC9C,OAAO,IAAI,eAAM,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,13 @@
1
+ export interface SendEmailParams {
2
+ from: string;
3
+ to: string;
4
+ subject: string;
5
+ htmlBody: string;
6
+ configurationSetName: string;
7
+ unsubscribeUrl: string;
8
+ replyToAddress?: string;
9
+ templateKey: string;
10
+ sequenceId: string;
11
+ }
12
+ export declare function sendEmail(params: SendEmailParams): Promise<string>;
13
+ //# sourceMappingURL=ses-sender.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ses-sender.d.ts","sourceRoot":"","sources":["../../src/lib/ses-sender.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AA4BD,wBAAsB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAyDxE"}
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendEmail = sendEmail;
4
+ const client_sesv2_1 = require("@aws-sdk/client-sesv2");
5
+ const logger_js_1 = require("./logger.js");
6
+ const logger = (0, logger_js_1.createLogger)("ses-sender");
7
+ const ses = new client_sesv2_1.SESv2Client({});
8
+ /**
9
+ * Strip HTML to plain text for the text/plain MIME part.
10
+ * Handles common email HTML patterns without requiring an external dependency.
11
+ */
12
+ function htmlToPlainText(html) {
13
+ return html
14
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "")
15
+ .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
16
+ .replace(/<br\s*\/?>/gi, "\n")
17
+ .replace(/<\/p>/gi, "\n\n")
18
+ .replace(/<\/div>/gi, "\n")
19
+ .replace(/<\/tr>/gi, "\n")
20
+ .replace(/<\/li>/gi, "\n")
21
+ .replace(/<li[^>]*>/gi, " - ")
22
+ .replace(/<a[^>]+href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi, "$2 ($1)")
23
+ .replace(/<[^>]+>/g, "")
24
+ .replace(/&nbsp;/gi, " ")
25
+ .replace(/&amp;/gi, "&")
26
+ .replace(/&lt;/gi, "<")
27
+ .replace(/&gt;/gi, ">")
28
+ .replace(/&quot;/gi, '"')
29
+ .replace(/&#39;/gi, "'")
30
+ .replace(/\n{3,}/g, "\n\n")
31
+ .trim();
32
+ }
33
+ async function sendEmail(params) {
34
+ logger.info("Sending email via SES", {
35
+ to: params.to,
36
+ subject: params.subject,
37
+ templateKey: params.templateKey,
38
+ sequenceId: params.sequenceId,
39
+ });
40
+ const textBody = htmlToPlainText(params.htmlBody);
41
+ const result = await ses.send(new client_sesv2_1.SendEmailCommand({
42
+ FromEmailAddress: params.from,
43
+ Destination: { ToAddresses: [params.to] },
44
+ ...(params.replyToAddress ? { ReplyToAddresses: [params.replyToAddress] } : {}),
45
+ Content: {
46
+ Simple: {
47
+ Subject: { Data: params.subject, Charset: "UTF-8" },
48
+ Body: {
49
+ Html: { Data: params.htmlBody, Charset: "UTF-8" },
50
+ Text: { Data: textBody, Charset: "UTF-8" },
51
+ },
52
+ Headers: [
53
+ {
54
+ Name: "List-Unsubscribe",
55
+ Value: `<${params.unsubscribeUrl}>`,
56
+ },
57
+ {
58
+ Name: "List-Unsubscribe-Post",
59
+ Value: "List-Unsubscribe=One-Click",
60
+ },
61
+ {
62
+ Name: "X-Template-Key",
63
+ Value: params.templateKey,
64
+ },
65
+ {
66
+ Name: "X-Sequence-Id",
67
+ Value: params.sequenceId,
68
+ },
69
+ ],
70
+ },
71
+ },
72
+ ConfigurationSetName: params.configurationSetName,
73
+ EmailTags: [
74
+ { Name: "templateKey", Value: params.templateKey.replace(/\//g, "--") },
75
+ { Name: "sequenceId", Value: params.sequenceId },
76
+ ],
77
+ }));
78
+ const messageId = result.MessageId ?? "unknown";
79
+ logger.info("Email sent successfully", {
80
+ messageId,
81
+ to: params.to,
82
+ templateKey: params.templateKey,
83
+ });
84
+ return messageId;
85
+ }
86
+ //# sourceMappingURL=ses-sender.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ses-sender.js","sourceRoot":"","sources":["../../src/lib/ses-sender.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function addToSuppressionList(email: string, reason: "BOUNCE" | "COMPLAINT"): Promise<void>;
2
+ //# sourceMappingURL=ses-suppression.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ses-suppression.d.ts","sourceRoot":"","sources":["../../src/lib/ses-suppression.ts"],"names":[],"mappings":"AAUA,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,QAAQ,GAAG,WAAW,GAC7B,OAAO,CAAC,IAAI,CAAC,CAgBf"}
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.addToSuppressionList = addToSuppressionList;
4
+ const client_sesv2_1 = require("@aws-sdk/client-sesv2");
5
+ const logger_js_1 = require("./logger.js");
6
+ const logger = (0, logger_js_1.createLogger)("ses-suppression");
7
+ const ses = new client_sesv2_1.SESv2Client({});
8
+ async function addToSuppressionList(email, reason) {
9
+ try {
10
+ await ses.send(new client_sesv2_1.PutSuppressedDestinationCommand({
11
+ EmailAddress: email,
12
+ Reason: reason,
13
+ }));
14
+ logger.info("Added to SES suppression list", { email, reason });
15
+ }
16
+ catch (error) {
17
+ logger.warn("Failed to add to SES suppression list", {
18
+ email,
19
+ reason,
20
+ error: error instanceof Error ? error.message : String(error),
21
+ });
22
+ }
23
+ }
24
+ //# sourceMappingURL=ses-suppression.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ses-suppression.js","sourceRoot":"","sources":["../../src/lib/ses-suppression.ts"],"names":[],"mappings":";;AAUA,oDAmBC;AA7BD,wDAI+B;AAC/B,2CAA2C;AAE3C,MAAM,MAAM,GAAG,IAAA,wBAAY,EAAC,iBAAiB,CAAC,CAAC;AAC/C,MAAM,GAAG,GAAG,IAAI,0BAAW,CAAC,EAAE,CAAC,CAAC;AAEzB,KAAK,UAAU,oBAAoB,CACxC,KAAa,EACb,MAA8B;IAE9B,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,IAAI,CACZ,IAAI,8CAA+B,CAAC;YAClC,YAAY,EAAE,KAAK;YACnB,MAAM,EAAE,MAA+B;SACxC,CAAC,CACH,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,+BAA+B,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IAClE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC,uCAAuC,EAAE;YACnD,KAAK;YACL,MAAM;YACN,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAC9D,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
@@ -0,0 +1,14 @@
1
+ export declare function getParameter(name: string): Promise<string>;
2
+ export interface ResolvedConfig {
3
+ tableName: string;
4
+ eventsTableName: string;
5
+ templateBucket: string;
6
+ defaultFromEmail: string;
7
+ defaultFromName: string;
8
+ replyToEmail: string;
9
+ sesConfigSet: string;
10
+ unsubscribeBaseUrl: string;
11
+ unsubscribeSecret: string;
12
+ }
13
+ export declare function resolveConfig(): Promise<ResolvedConfig>;
14
+ //# sourceMappingURL=ssm-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssm-config.d.ts","sourceRoot":"","sources":["../../src/lib/ssm-config.ts"],"names":[],"mappings":"AAQA,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBhE;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAID,wBAAsB,aAAa,IAAI,OAAO,CAAC,cAAc,CAAC,CAsC7D"}
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getParameter = getParameter;
4
+ exports.resolveConfig = resolveConfig;
5
+ const client_ssm_1 = require("@aws-sdk/client-ssm");
6
+ const logger_js_1 = require("./logger.js");
7
+ const logger = (0, logger_js_1.createLogger)("ssm-config");
8
+ const ssm = new client_ssm_1.SSMClient({});
9
+ const cache = new Map();
10
+ const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
11
+ async function getParameter(name) {
12
+ const cached = cache.get(name);
13
+ if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
14
+ logger.debug("SSM cache hit", { parameter: name });
15
+ return cached.value;
16
+ }
17
+ logger.debug("Fetching SSM parameter", { parameter: name });
18
+ const result = await ssm.send(new client_ssm_1.GetParameterCommand({ Name: name, WithDecryption: true }));
19
+ const value = result.Parameter?.Value;
20
+ if (!value) {
21
+ logger.error("SSM parameter not found", { parameter: name });
22
+ throw new Error(`SSM parameter not found: ${name}`);
23
+ }
24
+ cache.set(name, { value, fetchedAt: Date.now() });
25
+ return value;
26
+ }
27
+ const SSM_PREFIX = process.env.SSM_PREFIX ?? "/step-func-emailer";
28
+ async function resolveConfig() {
29
+ logger.debug("Resolving config", { prefix: SSM_PREFIX });
30
+ const [tableName, eventsTableName, templateBucket, defaultFromEmail, defaultFromName, replyToEmail, sesConfigSet, unsubscribeBaseUrl, unsubscribeSecret,] = await Promise.all([
31
+ getParameter(`${SSM_PREFIX}/table-name`),
32
+ getParameter(`${SSM_PREFIX}/events-table-name`),
33
+ getParameter(`${SSM_PREFIX}/template-bucket`),
34
+ getParameter(`${SSM_PREFIX}/default-from-email`),
35
+ getParameter(`${SSM_PREFIX}/default-from-name`),
36
+ getParameter(`${SSM_PREFIX}/reply-to-email`).catch(() => ""),
37
+ getParameter(`${SSM_PREFIX}/ses-config-set`),
38
+ getParameter(`${SSM_PREFIX}/unsubscribe-base-url`),
39
+ getParameter(`${SSM_PREFIX}/unsubscribe-secret`),
40
+ ]);
41
+ logger.debug("Config resolved", { tableName, templateBucket });
42
+ return {
43
+ tableName,
44
+ eventsTableName,
45
+ templateBucket,
46
+ defaultFromEmail,
47
+ defaultFromName,
48
+ replyToEmail,
49
+ sesConfigSet,
50
+ unsubscribeBaseUrl,
51
+ unsubscribeSecret,
52
+ };
53
+ }
54
+ //# sourceMappingURL=ssm-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssm-config.js","sourceRoot":"","sources":["../../src/lib/ssm-config.ts"],"names":[],"mappings":";;AAQA,oCAkBC;AAgBD,sCAsCC;AAhFD,oDAAqE;AACrE,2CAA2C;AAE3C,MAAM,MAAM,GAAG,IAAA,wBAAY,EAAC,YAAY,CAAC,CAAC;AAC1C,MAAM,GAAG,GAAG,IAAI,sBAAS,CAAC,EAAE,CAAC,CAAC;AAC9B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAgD,CAAC;AACtE,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,YAAY;AAEzC,KAAK,UAAU,YAAY,CAAC,IAAY;IAC7C,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,GAAG,YAAY,EAAE,CAAC;QAC3D,MAAM,CAAC,KAAK,CAAC,eAAe,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,OAAO,MAAM,CAAC,KAAK,CAAC;IACtB,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,wBAAwB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,gCAAmB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAE7F,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC;IACtC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,CAAC,KAAK,CAAC,yBAAyB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,MAAM,IAAI,KAAK,CAAC,4BAA4B,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAClD,OAAO,KAAK,CAAC;AACf,CAAC;AAcD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,oBAAoB,CAAC;AAE3D,KAAK,UAAU,aAAa;IACjC,MAAM,CAAC,KAAK,CAAC,kBAAkB,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;IAEzD,MAAM,CACJ,SAAS,EACT,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EAClB,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACpB,YAAY,CAAC,GAAG,UAAU,aAAa,CAAC;QACxC,YAAY,CAAC,GAAG,UAAU,oBAAoB,CAAC;QAC/C,YAAY,CAAC,GAAG,UAAU,kBAAkB,CAAC;QAC7C,YAAY,CAAC,GAAG,UAAU,qBAAqB,CAAC;QAChD,YAAY,CAAC,GAAG,UAAU,oBAAoB,CAAC;QAC/C,YAAY,CAAC,GAAG,UAAU,iBAAiB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;QAC5D,YAAY,CAAC,GAAG,UAAU,iBAAiB,CAAC;QAC5C,YAAY,CAAC,GAAG,UAAU,uBAAuB,CAAC;QAClD,YAAY,CAAC,GAAG,UAAU,qBAAqB,CAAC;KACjD,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CAAC;IAE/D,OAAO;QACL,SAAS;QACT,eAAe;QACf,cAAc;QACd,gBAAgB;QAChB,eAAe;QACf,YAAY;QACZ,YAAY;QACZ,kBAAkB;QAClB,iBAAiB;KAClB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ export interface RenderContext {
2
+ email: string;
3
+ firstName: string;
4
+ unsubscribeUrl: string;
5
+ currentYear: number;
6
+ [key: string]: unknown;
7
+ }
8
+ export declare function renderTemplate(bucket: string, templateKey: string, context: RenderContext): Promise<string>;
9
+ //# sourceMappingURL=template-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-renderer.d.ts","sourceRoot":"","sources":["../../src/lib/template-renderer.ts"],"names":[],"mappings":"AAiCA,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,MAAM,CAAC,CAMjB"}
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderTemplate = renderTemplate;
4
+ const client_s3_1 = require("@aws-sdk/client-s3");
5
+ const liquidjs_1 = require("liquidjs");
6
+ const shared_1 = require("@step-func-emailer/shared");
7
+ const logger_js_1 = require("./logger.js");
8
+ const logger = (0, logger_js_1.createLogger)("template-renderer");
9
+ const s3 = new client_s3_1.S3Client({});
10
+ const liquid = new liquidjs_1.Liquid();
11
+ // Module-level cache survives across warm invocations
12
+ const templateCache = new Map();
13
+ async function fetchTemplate(bucket, templateKey) {
14
+ const cached = templateCache.get(templateKey);
15
+ if (cached && Date.now() - cached.fetchedAt < shared_1.TEMPLATE_CACHE_TTL_MS) {
16
+ logger.debug("Template cache hit", { templateKey });
17
+ return cached.html;
18
+ }
19
+ logger.debug("Fetching template from S3", { bucket, key: `${templateKey}.html` });
20
+ const result = await s3.send(new client_s3_1.GetObjectCommand({
21
+ Bucket: bucket,
22
+ Key: `${templateKey}.html`,
23
+ }));
24
+ const html = (await result.Body?.transformToString()) ?? "";
25
+ templateCache.set(templateKey, { html, fetchedAt: Date.now() });
26
+ logger.debug("Template fetched and cached", { templateKey, length: html.length });
27
+ return html;
28
+ }
29
+ async function renderTemplate(bucket, templateKey, context) {
30
+ logger.info("Rendering template", { templateKey, email: context.email });
31
+ const html = await fetchTemplate(bucket, templateKey);
32
+ const rendered = await liquid.parseAndRender(html, context);
33
+ logger.debug("Template rendered", { templateKey, outputLength: rendered.length });
34
+ return rendered;
35
+ }
36
+ //# sourceMappingURL=template-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-renderer.js","sourceRoot":"","sources":["../../src/lib/template-renderer.ts"],"names":[],"mappings":";;AAyCA,wCAUC;AAnDD,kDAAgE;AAChE,uCAAkC;AAClC,sDAAkE;AAClE,2CAA2C;AAE3C,MAAM,MAAM,GAAG,IAAA,wBAAY,EAAC,mBAAmB,CAAC,CAAC;AACjD,MAAM,EAAE,GAAG,IAAI,oBAAQ,CAAC,EAAE,CAAC,CAAC;AAC5B,MAAM,MAAM,GAAG,IAAI,iBAAM,EAAE,CAAC;AAE5B,sDAAsD;AACtD,MAAM,aAAa,GAAG,IAAI,GAAG,EAA+C,CAAC;AAE7E,KAAK,UAAU,aAAa,CAAC,MAAc,EAAE,WAAmB;IAC9D,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC9C,IAAI,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,GAAG,8BAAqB,EAAE,CAAC;QACpE,MAAM,CAAC,KAAK,CAAC,oBAAoB,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC;QACpD,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,2BAA2B,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,WAAW,OAAO,EAAE,CAAC,CAAC;IAClF,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI,CAC1B,IAAI,4BAAgB,CAAC;QACnB,MAAM,EAAE,MAAM;QACd,GAAG,EAAE,GAAG,WAAW,OAAO;KAC3B,CAAC,CACH,CAAC;IAEF,MAAM,IAAI,GAAG,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,iBAAiB,EAAE,CAAC,IAAI,EAAE,CAAC;IAC5D,aAAa,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAChE,MAAM,CAAC,KAAK,CAAC,6BAA6B,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IAClF,OAAO,IAAI,CAAC;AACd,CAAC;AAUM,KAAK,UAAU,cAAc,CAClC,MAAc,EACd,WAAmB,EACnB,OAAsB;IAEtB,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IACzE,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC5D,MAAM,CAAC,KAAK,CAAC,mBAAmB,EAAE,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAClF,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,13 @@
1
+ export declare function generateToken(email: string, secret: string): string;
2
+ export interface ValidatedToken {
3
+ valid: true;
4
+ email: string;
5
+ sendTimestamp: string;
6
+ expiryTimestamp: string;
7
+ }
8
+ export interface InvalidToken {
9
+ valid: false;
10
+ reason: string;
11
+ }
12
+ export declare function validateToken(token: string, secret: string): ValidatedToken | InvalidToken;
13
+ //# sourceMappingURL=unsubscribe-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unsubscribe-token.d.ts","sourceRoot":"","sources":["../../src/lib/unsubscribe-token.ts"],"names":[],"mappings":"AAIA,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CASnE;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,IAAI,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,GAAG,YAAY,CAyB1F"}