@stacksjs/email 0.70.229 → 0.70.230
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.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mime-preview.d.ts +2 -0
- package/dist/mime-preview.js +70 -0
- package/dist/sdk/index.js +37 -47
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from './webhook-events';
|
|
|
10
10
|
export * from './webhook-handlers';
|
|
11
11
|
export * from './webhook-signatures';
|
|
12
12
|
export * from './mailable';
|
|
13
|
+
export * from './mime-preview';
|
|
13
14
|
export * from './sdk/index';
|
|
14
15
|
export * from './template';
|
|
15
16
|
export * from './types';
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
function splitHeaders(value) {
|
|
2
|
+
const normalized = value.replace(/\r\n/g, `
|
|
3
|
+
`), separator = normalized.indexOf(`
|
|
4
|
+
|
|
5
|
+
`);
|
|
6
|
+
if (separator < 0)
|
|
7
|
+
return { headers: {}, body: normalized };
|
|
8
|
+
const headerLines = normalized.slice(0, separator).replace(/\n[ \t]+/g, " ").split(`
|
|
9
|
+
`), headers = {};
|
|
10
|
+
for (const line of headerLines) {
|
|
11
|
+
const match = line.match(/^([^:]+):\s*(.*)$/);
|
|
12
|
+
if (match)
|
|
13
|
+
headers[match[1].toLowerCase()] = match[2] || "";
|
|
14
|
+
}
|
|
15
|
+
return { headers, body: normalized.slice(separator + 2) };
|
|
16
|
+
}
|
|
17
|
+
function decodeQuotedPrintable(value) {
|
|
18
|
+
const input = value.replace(/=\n/g, ""), bytes = [], encoder = new TextEncoder;
|
|
19
|
+
for (let index = 0;index < input.length; index++) {
|
|
20
|
+
const encoded = input.slice(index, index + 3);
|
|
21
|
+
if (/^=[0-9a-f]{2}$/i.test(encoded)) {
|
|
22
|
+
bytes.push(Number.parseInt(encoded.slice(1), 16));
|
|
23
|
+
index += 2;
|
|
24
|
+
} else
|
|
25
|
+
bytes.push(...encoder.encode(input[index]));
|
|
26
|
+
}
|
|
27
|
+
return new TextDecoder().decode(Uint8Array.from(bytes));
|
|
28
|
+
}
|
|
29
|
+
function decodeBody(body, encoding) {
|
|
30
|
+
if (encoding === "base64")
|
|
31
|
+
try {
|
|
32
|
+
return Buffer.from(body.replace(/\s+/g, ""), "base64").toString("utf8");
|
|
33
|
+
} catch {
|
|
34
|
+
return body;
|
|
35
|
+
}
|
|
36
|
+
if (encoding === "quoted-printable")
|
|
37
|
+
return decodeQuotedPrintable(body);
|
|
38
|
+
return body;
|
|
39
|
+
}
|
|
40
|
+
function mimeParts(value) {
|
|
41
|
+
const { headers, body } = splitHeaders(value), contentType = (headers["content-type"] || "text/plain").toLowerCase(), boundary = headers["content-type"]?.match(/boundary\s*=\s*(?:"([^"]+)"|([^;\s]+))/i)?.slice(1).find(Boolean);
|
|
42
|
+
if (contentType.startsWith("multipart/") && boundary)
|
|
43
|
+
return body.split(`--${boundary}`).slice(1).filter((part) => !part.trimStart().startsWith("--")).flatMap(mimeParts);
|
|
44
|
+
return [{
|
|
45
|
+
contentType,
|
|
46
|
+
encoding: (headers["content-transfer-encoding"] || "").toLowerCase(),
|
|
47
|
+
body
|
|
48
|
+
}];
|
|
49
|
+
}
|
|
50
|
+
function stripLegacyMimePreamble(value) {
|
|
51
|
+
let result = value.trim();
|
|
52
|
+
result = result.replace(/^[-=_]{2,}[^\s]*\s+/, "");
|
|
53
|
+
for (let pass = 0;pass < 6; pass++) {
|
|
54
|
+
const next = result.replace(/^content-transfer-encoding:\s*[^\s]+\s*/i, "").replace(/^mime-version:\s*[^\s]+\s*/i, "").replace(/^content-description:\s*[^\s]+\s*/i, "").replace(/^content-type:\s*[^\s;]+(?:;\s*(?:charset|boundary|name)\s*=\s*(?:"[^"]*"|[^\s]+))*\s*/i, "");
|
|
55
|
+
if (next === result)
|
|
56
|
+
break;
|
|
57
|
+
result = next;
|
|
58
|
+
}
|
|
59
|
+
return result.replace(/^[-=_]{4,}[^\s]*\s*/, "");
|
|
60
|
+
}
|
|
61
|
+
function plainText(value) {
|
|
62
|
+
return decodeQuotedPrintable(stripLegacyMimePreamble(value)).replace(/<style\b[\s\S]*?<\/style>/gi, " ").replace(/<script\b[\s\S]*?<\/script>/gi, " ").replace(/<[^>]+>/g, " ").replace(/ /gi, " ").replace(/&/gi, "&").replace(/</gi, "<").replace(/>/gi, ">").replace(/"/gi, '"').replace(/'/gi, "'").replace(/\s+/g, " ").trim();
|
|
63
|
+
}
|
|
64
|
+
export function extractEmailPreview(rawEmail, maxLength = 200) {
|
|
65
|
+
const parts = mimeParts(rawEmail), selected = parts.find((part) => part.contentType.startsWith("text/plain")) || parts.find((part) => part.contentType.startsWith("text/html"));
|
|
66
|
+
return plainText(selected ? decodeBody(selected.body, selected.encoding) : rawEmail).slice(0, maxLength);
|
|
67
|
+
}
|
|
68
|
+
export function normalizeEmailPreview(preview, maxLength = 200) {
|
|
69
|
+
return plainText(preview).slice(0, maxLength);
|
|
70
|
+
}
|
package/dist/sdk/index.js
CHANGED
|
@@ -144,39 +144,34 @@ export class EmailSDK {
|
|
|
144
144
|
return results.slice(offset, offset + limit);
|
|
145
145
|
}
|
|
146
146
|
async delete(mailbox, messageId) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (emailIndex === -1)
|
|
150
|
-
return !1;
|
|
151
|
-
const email = inbox[emailIndex];
|
|
152
|
-
if (!email)
|
|
153
|
-
return !1;
|
|
154
|
-
const basePath = email.path, keysToDelete = [
|
|
155
|
-
`${basePath}/metadata.json`,
|
|
156
|
-
`${basePath}/raw.eml`,
|
|
157
|
-
`${basePath}/body.html`,
|
|
158
|
-
`${basePath}/body.txt`,
|
|
159
|
-
`${basePath}/preview.txt`
|
|
160
|
-
];
|
|
161
|
-
for (const key of keysToDelete)
|
|
162
|
-
try {
|
|
163
|
-
await s3.deleteObject(this.bucket, key);
|
|
164
|
-
} catch (error) {
|
|
165
|
-
if (!getErrorMessage(error)?.includes("NoSuchKey") && !getErrorMessage(error)?.includes("404"))
|
|
166
|
-
console.debug(`[email-sdk] Failed to delete ${key}: ${getErrorMessage(error)}`);
|
|
167
|
-
}
|
|
168
|
-
inbox.splice(emailIndex, 1);
|
|
169
|
-
await s3.putObject({
|
|
170
|
-
bucket: this.bucket,
|
|
171
|
-
key: `mailboxes/${domain}/${localPart}/inbox.json`,
|
|
172
|
-
body: JSON.stringify(inbox, null, 2),
|
|
173
|
-
contentType: "application/json"
|
|
174
|
-
});
|
|
175
|
-
return !0;
|
|
176
|
-
} catch (error) {
|
|
177
|
-
console.debug(`[email-sdk] Failed to delete email ${messageId}: ${getErrorMessage(error)}`);
|
|
147
|
+
const { S3Client } = await import("@stacksjs/ts-cloud"), s3 = new S3Client(this.region), [localPart, domain] = mailbox.includes("@") ? mailbox.split("@") : [mailbox, this.domain], inbox = await this.getInbox(mailbox, { limit: 1000 }), emailIndex = inbox.findIndex((e) => e.messageId === messageId);
|
|
148
|
+
if (emailIndex === -1)
|
|
178
149
|
return !1;
|
|
179
|
-
|
|
150
|
+
const email = inbox[emailIndex];
|
|
151
|
+
if (!email)
|
|
152
|
+
return !1;
|
|
153
|
+
const basePath = email.path, keysToDelete = [
|
|
154
|
+
`${basePath}/metadata.json`,
|
|
155
|
+
`${basePath}/raw.eml`,
|
|
156
|
+
`${basePath}/body.html`,
|
|
157
|
+
`${basePath}/body.txt`,
|
|
158
|
+
`${basePath}/preview.txt`
|
|
159
|
+
];
|
|
160
|
+
for (const key of keysToDelete)
|
|
161
|
+
try {
|
|
162
|
+
await s3.deleteObject(this.bucket, key);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (!getErrorMessage(error)?.includes("NoSuchKey") && !getErrorMessage(error)?.includes("404"))
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
inbox.splice(emailIndex, 1);
|
|
168
|
+
await s3.putObject({
|
|
169
|
+
bucket: this.bucket,
|
|
170
|
+
key: `mailboxes/${domain}/${localPart}/inbox.json`,
|
|
171
|
+
body: JSON.stringify(inbox, null, 2),
|
|
172
|
+
contentType: "application/json"
|
|
173
|
+
});
|
|
174
|
+
return !0;
|
|
180
175
|
}
|
|
181
176
|
async markAsRead(mailbox, messageId) {
|
|
182
177
|
return this.updateEmailStatus(mailbox, messageId, { read: !0 });
|
|
@@ -185,22 +180,17 @@ export class EmailSDK {
|
|
|
185
180
|
return this.updateEmailStatus(mailbox, messageId, { read: !1 });
|
|
186
181
|
}
|
|
187
182
|
async updateEmailStatus(mailbox, messageId, updates) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (emailIndex === -1)
|
|
191
|
-
return !1;
|
|
192
|
-
Object.assign(inbox[emailIndex], updates);
|
|
193
|
-
await s3.putObject({
|
|
194
|
-
bucket: this.bucket,
|
|
195
|
-
key: `mailboxes/${domain}/${localPart}/inbox.json`,
|
|
196
|
-
body: JSON.stringify(inbox, null, 2),
|
|
197
|
-
contentType: "application/json"
|
|
198
|
-
});
|
|
199
|
-
return !0;
|
|
200
|
-
} catch (error) {
|
|
201
|
-
console.debug(`[email-sdk] Failed to update email status for ${messageId}: ${getErrorMessage(error)}`);
|
|
183
|
+
const { S3Client } = await import("@stacksjs/ts-cloud"), s3 = new S3Client(this.region), [localPart, domain] = mailbox.includes("@") ? mailbox.split("@") : [mailbox, this.domain], inbox = await this.getInbox(mailbox, { limit: 1000 }), emailIndex = inbox.findIndex((e) => e.messageId === messageId);
|
|
184
|
+
if (emailIndex === -1)
|
|
202
185
|
return !1;
|
|
203
|
-
|
|
186
|
+
Object.assign(inbox[emailIndex], updates);
|
|
187
|
+
await s3.putObject({
|
|
188
|
+
bucket: this.bucket,
|
|
189
|
+
key: `mailboxes/${domain}/${localPart}/inbox.json`,
|
|
190
|
+
body: JSON.stringify(inbox, null, 2),
|
|
191
|
+
contentType: "application/json"
|
|
192
|
+
});
|
|
193
|
+
return !0;
|
|
204
194
|
}
|
|
205
195
|
normalizeAddress(addr) {
|
|
206
196
|
if (typeof addr === "string") {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/email",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.230",
|
|
6
6
|
"description": "The Stacks Email integration. Painlessly create & manage your inboxes, templates, and send emails.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -58,10 +58,10 @@
|
|
|
58
58
|
"@stacksjs/ts-cloud": "^0.7.92"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@stacksjs/cli": "0.70.
|
|
62
|
-
"@stacksjs/config": "0.70.
|
|
61
|
+
"@stacksjs/cli": "0.70.230",
|
|
62
|
+
"@stacksjs/config": "0.70.230",
|
|
63
63
|
"better-dx": "^0.2.17",
|
|
64
|
-
"@stacksjs/error-handling": "0.70.
|
|
65
|
-
"@stacksjs/types": "0.70.
|
|
64
|
+
"@stacksjs/error-handling": "0.70.230",
|
|
65
|
+
"@stacksjs/types": "0.70.230"
|
|
66
66
|
}
|
|
67
67
|
}
|