@freecorps/ember-email 0.5.1 → 0.5.3
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/README.md +5 -0
- package/dist/component.d.ts +34 -0
- package/dist/component.js +32 -5
- package/dist/schema.d.ts +5 -0
- package/dist/schema.js +2 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -57,6 +57,11 @@ export const signup = mutation({
|
|
|
57
57
|
- **Inbox queries** for dashboards and mobile apps: `threads`, `thread`, `unread` (aggregate), `search`
|
|
58
58
|
(`searchIndex`), `reply`, `note`, `markRead`, `markUnread`, `setStatus`, `assign`, `compose` (a new conversation
|
|
59
59
|
from the mailbox, answers join it by token) and `recent` (latest threads with their messages, for offline replicas).
|
|
60
|
+
`reply` and `compose` take `attachments: [{ filename, contentType, file }]`, files already stored in the shard
|
|
61
|
+
(uploaded with `ctx.storage.generateUploadUrl`). They go with the email and stay on the thread's outbound message
|
|
62
|
+
(with their size). Mind the provider's message size: for a large file, put a download link in the text
|
|
63
|
+
(`ctx.storage.getUrl(file, { download, expiresIn })`) and pass it with `link: true`. The email then has no MIME part
|
|
64
|
+
for it, and the message still holds the file (the thread lists it, and it is not collected).
|
|
60
65
|
|
|
61
66
|
## Domains: `@freecorps/ember-email/cloudflare`
|
|
62
67
|
|
package/dist/component.d.ts
CHANGED
|
@@ -252,6 +252,7 @@ export declare const emailFunctions: {
|
|
|
252
252
|
contentId?: string;
|
|
253
253
|
inline?: boolean;
|
|
254
254
|
size?: number;
|
|
255
|
+
link?: boolean;
|
|
255
256
|
}[];
|
|
256
257
|
replyTo?: {
|
|
257
258
|
email: string;
|
|
@@ -316,6 +317,7 @@ export declare const emailFunctions: {
|
|
|
316
317
|
contentId?: string;
|
|
317
318
|
inline?: boolean;
|
|
318
319
|
size?: number;
|
|
320
|
+
link?: boolean;
|
|
319
321
|
}[];
|
|
320
322
|
replyTo?: {
|
|
321
323
|
email: string;
|
|
@@ -419,6 +421,7 @@ export declare const emailFunctions: {
|
|
|
419
421
|
contentId?: string;
|
|
420
422
|
inline?: boolean;
|
|
421
423
|
size?: number;
|
|
424
|
+
link?: boolean;
|
|
422
425
|
}[];
|
|
423
426
|
replyTo?: {
|
|
424
427
|
email: string;
|
|
@@ -674,6 +677,7 @@ export declare const emailFunctions: {
|
|
|
674
677
|
contentId?: string;
|
|
675
678
|
inline?: boolean;
|
|
676
679
|
size?: number;
|
|
680
|
+
link?: boolean;
|
|
677
681
|
}[];
|
|
678
682
|
references: string[];
|
|
679
683
|
to: {
|
|
@@ -731,6 +735,12 @@ export declare const emailFunctions: {
|
|
|
731
735
|
text: string;
|
|
732
736
|
threadId: GenericId<"threads">;
|
|
733
737
|
html?: string;
|
|
738
|
+
attachments?: {
|
|
739
|
+
filename: string;
|
|
740
|
+
contentType: string;
|
|
741
|
+
file: import("@freecorps/ember-values").FileId;
|
|
742
|
+
link?: boolean;
|
|
743
|
+
}[];
|
|
734
744
|
cc?: string[];
|
|
735
745
|
sendAt?: number;
|
|
736
746
|
author?: string;
|
|
@@ -759,6 +769,12 @@ export declare const emailFunctions: {
|
|
|
759
769
|
subject: string;
|
|
760
770
|
mailbox: string;
|
|
761
771
|
html?: string;
|
|
772
|
+
attachments?: {
|
|
773
|
+
filename: string;
|
|
774
|
+
contentType: string;
|
|
775
|
+
file: import("@freecorps/ember-values").FileId;
|
|
776
|
+
link?: boolean;
|
|
777
|
+
}[];
|
|
762
778
|
cc?: string[];
|
|
763
779
|
author?: string;
|
|
764
780
|
}, {
|
|
@@ -811,6 +827,7 @@ export declare const emailFunctions: {
|
|
|
811
827
|
contentId?: string;
|
|
812
828
|
inline?: boolean;
|
|
813
829
|
size?: number;
|
|
830
|
+
link?: boolean;
|
|
814
831
|
}[];
|
|
815
832
|
references: string[];
|
|
816
833
|
to: {
|
|
@@ -1028,6 +1045,7 @@ export declare const email: import("@freecorps/ember-server").ComponentDefinitio
|
|
|
1028
1045
|
contentId?: string;
|
|
1029
1046
|
inline?: boolean;
|
|
1030
1047
|
size?: number;
|
|
1048
|
+
link?: boolean;
|
|
1031
1049
|
}[];
|
|
1032
1050
|
replyTo?: {
|
|
1033
1051
|
email: string;
|
|
@@ -1092,6 +1110,7 @@ export declare const email: import("@freecorps/ember-server").ComponentDefinitio
|
|
|
1092
1110
|
contentId?: string;
|
|
1093
1111
|
inline?: boolean;
|
|
1094
1112
|
size?: number;
|
|
1113
|
+
link?: boolean;
|
|
1095
1114
|
}[];
|
|
1096
1115
|
replyTo?: {
|
|
1097
1116
|
email: string;
|
|
@@ -1195,6 +1214,7 @@ export declare const email: import("@freecorps/ember-server").ComponentDefinitio
|
|
|
1195
1214
|
contentId?: string;
|
|
1196
1215
|
inline?: boolean;
|
|
1197
1216
|
size?: number;
|
|
1217
|
+
link?: boolean;
|
|
1198
1218
|
}[];
|
|
1199
1219
|
replyTo?: {
|
|
1200
1220
|
email: string;
|
|
@@ -1450,6 +1470,7 @@ export declare const email: import("@freecorps/ember-server").ComponentDefinitio
|
|
|
1450
1470
|
contentId?: string;
|
|
1451
1471
|
inline?: boolean;
|
|
1452
1472
|
size?: number;
|
|
1473
|
+
link?: boolean;
|
|
1453
1474
|
}[];
|
|
1454
1475
|
references: string[];
|
|
1455
1476
|
to: {
|
|
@@ -1507,6 +1528,12 @@ export declare const email: import("@freecorps/ember-server").ComponentDefinitio
|
|
|
1507
1528
|
text: string;
|
|
1508
1529
|
threadId: GenericId<"threads">;
|
|
1509
1530
|
html?: string;
|
|
1531
|
+
attachments?: {
|
|
1532
|
+
filename: string;
|
|
1533
|
+
contentType: string;
|
|
1534
|
+
file: import("@freecorps/ember-values").FileId;
|
|
1535
|
+
link?: boolean;
|
|
1536
|
+
}[];
|
|
1510
1537
|
cc?: string[];
|
|
1511
1538
|
sendAt?: number;
|
|
1512
1539
|
author?: string;
|
|
@@ -1535,6 +1562,12 @@ export declare const email: import("@freecorps/ember-server").ComponentDefinitio
|
|
|
1535
1562
|
subject: string;
|
|
1536
1563
|
mailbox: string;
|
|
1537
1564
|
html?: string;
|
|
1565
|
+
attachments?: {
|
|
1566
|
+
filename: string;
|
|
1567
|
+
contentType: string;
|
|
1568
|
+
file: import("@freecorps/ember-values").FileId;
|
|
1569
|
+
link?: boolean;
|
|
1570
|
+
}[];
|
|
1538
1571
|
cc?: string[];
|
|
1539
1572
|
author?: string;
|
|
1540
1573
|
}, {
|
|
@@ -1587,6 +1620,7 @@ export declare const email: import("@freecorps/ember-server").ComponentDefinitio
|
|
|
1587
1620
|
contentId?: string;
|
|
1588
1621
|
inline?: boolean;
|
|
1589
1622
|
size?: number;
|
|
1623
|
+
link?: boolean;
|
|
1590
1624
|
}[];
|
|
1591
1625
|
references: string[];
|
|
1592
1626
|
to: {
|
package/dist/component.js
CHANGED
|
@@ -74,6 +74,20 @@ const sendArgs = {
|
|
|
74
74
|
threadId: v.optional(v.string()),
|
|
75
75
|
};
|
|
76
76
|
const HEADER_NAME = /^[A-Za-z0-9-]{1,100}$/;
|
|
77
|
+
/**
|
|
78
|
+
* Files already stored in this shard, for an inbox reply or a composed email. `link: true`: too large to attach; the
|
|
79
|
+
* caller puts a download link in the text, the email carries no MIME part, and the message still holds the file.
|
|
80
|
+
*/
|
|
81
|
+
const inboxAttachmentsV = v.optional(v.array(v.object({ filename: v.string(), contentType: v.string(), file: v.file(), link: v.optional(v.boolean()) })));
|
|
82
|
+
/** The MIME attachments of the email: every file not sent as a link. */
|
|
83
|
+
const mimeParts = (list) => (list ?? []).filter((a) => !a.link).map(({ filename, contentType, file }) => ({ filename, contentType, file }));
|
|
84
|
+
/** The stored message's attachment rows (size from the file's metadata when known). */
|
|
85
|
+
function attachmentRows(ctx, list) {
|
|
86
|
+
return (list ?? []).map((a) => {
|
|
87
|
+
const meta = ctx.storage?.getMetadata?.(a.file);
|
|
88
|
+
return { filename: a.filename, contentType: a.contentType, file: a.file, ...(meta?.size != null ? { size: meta.size } : {}), ...(a.link ? { link: true } : {}) };
|
|
89
|
+
});
|
|
90
|
+
}
|
|
77
91
|
const RESERVED_HEADERS = new Set(["from", "to", "cc", "bcc", "subject", "reply-to", "content-type", "mime-version", "content-transfer-encoding"]);
|
|
78
92
|
function doSend(ctx, a) {
|
|
79
93
|
const project = a.project ?? "default";
|
|
@@ -533,8 +547,8 @@ function appendOutbound(ctx, thread, mb, a) {
|
|
|
533
547
|
text: a.text,
|
|
534
548
|
...(a.html ? { html: a.html } : {}),
|
|
535
549
|
snippet,
|
|
536
|
-
searchText: `${thread.subject}\n${a.author ?? ""}\n${a.text}`.slice(0, 100_000),
|
|
537
|
-
attachments: [],
|
|
550
|
+
searchText: `${thread.subject}\n${a.author ?? ""}\n${a.text}${(a.attachments ?? []).map((x) => `\n${x.filename}`).join("")}`.slice(0, 100_000),
|
|
551
|
+
attachments: a.attachments ?? [],
|
|
538
552
|
autoSubmitted: false,
|
|
539
553
|
...(a.emailId ? { emailId: a.emailId } : {}),
|
|
540
554
|
...(a.author ? { author: a.author } : {}),
|
|
@@ -880,7 +894,15 @@ export const emailFunctions = {
|
|
|
880
894
|
}),
|
|
881
895
|
/** Replies from the inbox: sent from the mailbox address, Reply-To carries the thread token, threading headers set. */
|
|
882
896
|
reply: mutation({
|
|
883
|
-
args: {
|
|
897
|
+
args: {
|
|
898
|
+
threadId: v.id("threads"),
|
|
899
|
+
text: v.string(),
|
|
900
|
+
html: v.optional(v.string()),
|
|
901
|
+
author: v.optional(v.string()),
|
|
902
|
+
cc: v.optional(v.array(v.string())),
|
|
903
|
+
sendAt: v.optional(v.number()),
|
|
904
|
+
attachments: inboxAttachmentsV,
|
|
905
|
+
},
|
|
884
906
|
handler: (ctx, a) => {
|
|
885
907
|
const thread = ctx.db.get(a.threadId);
|
|
886
908
|
if (!thread)
|
|
@@ -917,8 +939,10 @@ export const emailFunctions = {
|
|
|
917
939
|
headers,
|
|
918
940
|
threadId: thread._id,
|
|
919
941
|
...(a.sendAt ? { sendAt: a.sendAt } : {}),
|
|
942
|
+
...(mimeParts(a.attachments).length ? { attachments: mimeParts(a.attachments) } : {}),
|
|
920
943
|
});
|
|
921
|
-
const
|
|
944
|
+
const files = attachmentRows(ctx, a.attachments);
|
|
945
|
+
const messageId = appendOutbound(ctx, thread, mb, { text: a.text, ...(a.html ? { html: a.html } : {}), emailId: sent.id, ...(a.author ? { author: a.author } : {}), to, ...(a.cc?.length ? { cc: parseAddressList(a.cc) } : {}), attachments: files });
|
|
922
946
|
return { emailId: sent.id, messageId, status: sent.status };
|
|
923
947
|
},
|
|
924
948
|
}),
|
|
@@ -956,6 +980,7 @@ export const emailFunctions = {
|
|
|
956
980
|
text: v.string(),
|
|
957
981
|
html: v.optional(v.string()),
|
|
958
982
|
author: v.optional(v.string()),
|
|
983
|
+
attachments: inboxAttachmentsV,
|
|
959
984
|
},
|
|
960
985
|
handler: (ctx, a) => {
|
|
961
986
|
const mb = ctx.db.query("mailboxes").withIndex("by_name", (q) => q.eq("name", a.mailbox)).first();
|
|
@@ -1002,8 +1027,10 @@ export const emailFunctions = {
|
|
|
1002
1027
|
text: a.text,
|
|
1003
1028
|
html: a.html ?? emailLayout(textToHtml(a.text)),
|
|
1004
1029
|
threadId,
|
|
1030
|
+
...(mimeParts(a.attachments).length ? { attachments: mimeParts(a.attachments) } : {}),
|
|
1005
1031
|
});
|
|
1006
|
-
const
|
|
1032
|
+
const files = attachmentRows(ctx, a.attachments);
|
|
1033
|
+
const messageId = appendOutbound(ctx, thread, mb, { text: a.text, ...(a.html ? { html: a.html } : {}), emailId: sent.id, ...(a.author ? { author: a.author } : {}), to, ...(a.cc?.length ? { cc: parseAddressList(a.cc) } : {}), attachments: files });
|
|
1007
1034
|
return { threadId, emailId: sent.id, messageId, status: sent.status, number };
|
|
1008
1035
|
},
|
|
1009
1036
|
}),
|
package/dist/schema.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare const attachmentV: import("@freecorps/ember-values").Validator<{
|
|
|
12
12
|
contentId?: string;
|
|
13
13
|
inline?: boolean;
|
|
14
14
|
size?: number;
|
|
15
|
+
link?: boolean;
|
|
15
16
|
}, "required", import("@freecorps/ember-values").ObjectFieldPaths<{
|
|
16
17
|
filename: import("@freecorps/ember-values").Validator<string, "required", never>;
|
|
17
18
|
contentType: import("@freecorps/ember-values").Validator<string, "required", never>;
|
|
@@ -19,6 +20,8 @@ export declare const attachmentV: import("@freecorps/ember-values").Validator<{
|
|
|
19
20
|
size: import("@freecorps/ember-values").Validator<number, "optional", never>;
|
|
20
21
|
contentId: import("@freecorps/ember-values").Validator<string, "optional", never>;
|
|
21
22
|
inline: import("@freecorps/ember-values").Validator<boolean, "optional", never>;
|
|
23
|
+
/** Sent as a link in the body (too large to attach), not as a MIME part: the message still holds the file. */
|
|
24
|
+
link: import("@freecorps/ember-values").Validator<boolean, "optional", never>;
|
|
22
25
|
}>>;
|
|
23
26
|
export declare const errorV: import("@freecorps/ember-values").Validator<{
|
|
24
27
|
code: string;
|
|
@@ -68,6 +71,7 @@ export declare const emailSchema: import("@freecorps/ember-server").SchemaDefini
|
|
|
68
71
|
contentId?: string;
|
|
69
72
|
inline?: boolean;
|
|
70
73
|
size?: number;
|
|
74
|
+
link?: boolean;
|
|
71
75
|
}[], "optional", never>;
|
|
72
76
|
template: import("@freecorps/ember-values").Validator<{
|
|
73
77
|
name: string;
|
|
@@ -239,6 +243,7 @@ export declare const emailSchema: import("@freecorps/ember-server").SchemaDefini
|
|
|
239
243
|
contentId?: string;
|
|
240
244
|
inline?: boolean;
|
|
241
245
|
size?: number;
|
|
246
|
+
link?: boolean;
|
|
242
247
|
}[], "required", never>;
|
|
243
248
|
droppedAttachments: import("@freecorps/ember-values").Validator<number, "optional", never>;
|
|
244
249
|
auth: import("@freecorps/ember-values").Validator<{
|
package/dist/schema.js
CHANGED
|
@@ -8,6 +8,8 @@ export const attachmentV = v.object({
|
|
|
8
8
|
size: v.optional(v.number()),
|
|
9
9
|
contentId: v.optional(v.string()),
|
|
10
10
|
inline: v.optional(v.boolean()),
|
|
11
|
+
/** Sent as a link in the body (too large to attach), not as a MIME part: the message still holds the file. */
|
|
12
|
+
link: v.optional(v.boolean()),
|
|
11
13
|
});
|
|
12
14
|
export const errorV = v.object({ code: v.string(), message: v.string() });
|
|
13
15
|
export const emailSchema = defineSchema({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freecorps/ember-email",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Email component for Ember on Cloudflare: send (atomic from mutations, scheduled, idempotent) through Email Service, templates, suppressions and per-project rate limits, delivery events from the Queue, inbound mail (Email Routing) parsed into mailboxes and threads. `@freecorps/ember-email/cloudflare` onboards a domain (sending, routing rules, event subscription) and removes it.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ember",
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"access": "public"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@freecorps/ember-rate-limit": "0.5.
|
|
56
|
-
"@freecorps/ember-server": "0.5.
|
|
57
|
-
"@freecorps/ember-values": "0.5.
|
|
55
|
+
"@freecorps/ember-rate-limit": "0.5.3",
|
|
56
|
+
"@freecorps/ember-server": "0.5.3",
|
|
57
|
+
"@freecorps/ember-values": "0.5.3",
|
|
58
58
|
"postal-mime": "^4.1.4"
|
|
59
59
|
}
|
|
60
60
|
}
|