@vllnt/convex-email 0.1.0-canary.63aca6b
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/LICENSE +21 -0
- package/README.md +171 -0
- package/dist/client/index.d.ts +174 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +151 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/types.d.ts +84 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +3 -0
- package/dist/client/types.js.map +1 -0
- package/dist/component/_generated/api.d.ts +40 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +110 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +7 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/crons.d.ts +16 -0
- package/dist/component/crons.d.ts.map +1 -0
- package/dist/component/crons.js +19 -0
- package/dist/component/crons.js.map +1 -0
- package/dist/component/mutations.d.ts +95 -0
- package/dist/component/mutations.d.ts.map +1 -0
- package/dist/component/mutations.js +243 -0
- package/dist/component/mutations.js.map +1 -0
- package/dist/component/queries.d.ts +59 -0
- package/dist/component/queries.d.ts.map +1 -0
- package/dist/component/queries.js +61 -0
- package/dist/component/queries.js.map +1 -0
- package/dist/component/schema.d.ts +54 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +40 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/validators.d.ts +54 -0
- package/dist/component/validators.d.ts.map +1 -0
- package/dist/component/validators.js +40 -0
- package/dist/component/validators.js.map +1 -0
- package/dist/jmap/index.d.ts +22 -0
- package/dist/jmap/index.d.ts.map +1 -0
- package/dist/jmap/index.js +21 -0
- package/dist/jmap/index.js.map +1 -0
- package/dist/jmap/send.d.ts +125 -0
- package/dist/jmap/send.d.ts.map +1 -0
- package/dist/jmap/send.js +418 -0
- package/dist/jmap/send.js.map +1 -0
- package/dist/jmap/types.d.ts +107 -0
- package/dist/jmap/types.d.ts.map +1 -0
- package/dist/jmap/types.js +16 -0
- package/dist/jmap/types.js.map +1 -0
- package/dist/shared.d.ts +32 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +33 -0
- package/dist/shared.js.map +1 -0
- package/dist/smtp/index.d.ts +22 -0
- package/dist/smtp/index.d.ts.map +1 -0
- package/dist/smtp/index.js +21 -0
- package/dist/smtp/index.js.map +1 -0
- package/dist/smtp/send.d.ts +51 -0
- package/dist/smtp/send.d.ts.map +1 -0
- package/dist/smtp/send.js +124 -0
- package/dist/smtp/send.js.map +1 -0
- package/dist/smtp/transport.d.ts +43 -0
- package/dist/smtp/transport.d.ts.map +1 -0
- package/dist/smtp/transport.js +55 -0
- package/dist/smtp/transport.js.map +1 -0
- package/dist/smtp/types.d.ts +122 -0
- package/dist/smtp/types.d.ts.map +1 -0
- package/dist/smtp/types.js +9 -0
- package/dist/smtp/types.js.map +1 -0
- package/package.json +118 -0
- package/src/client/index.ts +312 -0
- package/src/client/types.ts +90 -0
- package/src/component/_generated/api.ts +56 -0
- package/src/component/_generated/component.ts +134 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +156 -0
- package/src/component/convex.config.ts +9 -0
- package/src/component/crons.ts +23 -0
- package/src/component/mutations.ts +262 -0
- package/src/component/queries.ts +70 -0
- package/src/component/schema.ts +40 -0
- package/src/component/validators.ts +47 -0
- package/src/jmap/index.ts +39 -0
- package/src/jmap/send.test.ts +565 -0
- package/src/jmap/send.ts +502 -0
- package/src/jmap/types.ts +117 -0
- package/src/shared.ts +41 -0
- package/src/smtp/index.ts +30 -0
- package/src/smtp/send.test.ts +240 -0
- package/src/smtp/send.ts +154 -0
- package/src/smtp/transport.ts +58 -0
- package/src/smtp/types.ts +124 -0
- package/src/test.ts +12 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { ConvexError, v } from "convex/values";
|
|
2
|
+
import { api } from "./_generated/api";
|
|
3
|
+
import { mutation } from "./_generated/server";
|
|
4
|
+
import { jsonValue } from "./validators";
|
|
5
|
+
/**
|
|
6
|
+
* Enqueue an outbound message and return its id immediately — the durable queue
|
|
7
|
+
* entry. The message is inserted in `queued` state with `attempts: 0`,
|
|
8
|
+
* `createdAt`/`updatedAt` stamped from the server clock (`Date.now()` inside the
|
|
9
|
+
* handler — never caller-supplied). The host owns the meaning of `to`/`from`, the
|
|
10
|
+
* opaque `payload`, and the `transport` adapter name — the component records the
|
|
11
|
+
* intent and never calls any provider. A transport sender later claims the
|
|
12
|
+
* message ({@link markSending}) and reports the outcome ({@link markSent} /
|
|
13
|
+
* {@link markFailed}).
|
|
14
|
+
*
|
|
15
|
+
* `messageId` is host-supplied (the host names its own message). The id must be
|
|
16
|
+
* unique — re-using an existing id throws
|
|
17
|
+
* `ConvexError({ code: "DUPLICATE_MESSAGE" })`. When `idempotencyKey` is supplied
|
|
18
|
+
* and a message already carries it, the existing message's id is returned and no
|
|
19
|
+
* new row is inserted — a retried enqueue (double-submit, at-least-once delivery)
|
|
20
|
+
* can never produce a duplicate send.
|
|
21
|
+
*/
|
|
22
|
+
export const enqueue = mutation({
|
|
23
|
+
args: {
|
|
24
|
+
messageId: v.string(),
|
|
25
|
+
to: v.string(),
|
|
26
|
+
from: v.string(),
|
|
27
|
+
transport: v.string(),
|
|
28
|
+
payload: v.optional(jsonValue),
|
|
29
|
+
subjectRef: v.optional(v.string()),
|
|
30
|
+
idempotencyKey: v.optional(v.string()),
|
|
31
|
+
maxAttempts: v.number(),
|
|
32
|
+
},
|
|
33
|
+
returns: v.object({ messageId: v.string(), deduplicated: v.boolean() }),
|
|
34
|
+
handler: async (ctx, args) => {
|
|
35
|
+
if (args.idempotencyKey !== undefined) {
|
|
36
|
+
const dupe = await ctx.db
|
|
37
|
+
.query("messages")
|
|
38
|
+
.withIndex("by_idempotency_key", (q) => q.eq("idempotencyKey", args.idempotencyKey))
|
|
39
|
+
.unique();
|
|
40
|
+
if (dupe !== null) {
|
|
41
|
+
return { messageId: dupe.messageId, deduplicated: true };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const existing = await ctx.db
|
|
45
|
+
.query("messages")
|
|
46
|
+
.withIndex("by_message_id", (q) => q.eq("messageId", args.messageId))
|
|
47
|
+
.unique();
|
|
48
|
+
if (existing !== null) {
|
|
49
|
+
throw new ConvexError({
|
|
50
|
+
code: "DUPLICATE_MESSAGE",
|
|
51
|
+
message: `message "${args.messageId}" already exists`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
await ctx.db.insert("messages", {
|
|
56
|
+
messageId: args.messageId,
|
|
57
|
+
to: args.to,
|
|
58
|
+
from: args.from,
|
|
59
|
+
transport: args.transport,
|
|
60
|
+
status: "queued",
|
|
61
|
+
payload: args.payload,
|
|
62
|
+
subjectRef: args.subjectRef,
|
|
63
|
+
idempotencyKey: args.idempotencyKey,
|
|
64
|
+
attempts: 0,
|
|
65
|
+
maxAttempts: args.maxAttempts,
|
|
66
|
+
createdAt: now,
|
|
67
|
+
updatedAt: now,
|
|
68
|
+
});
|
|
69
|
+
return { messageId: args.messageId, deduplicated: false };
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
/**
|
|
73
|
+
* Claim a `queued` message for a send attempt: move it to `sending` and increment
|
|
74
|
+
* `attempts`. A transport sender calls this before dispatching, so concurrent
|
|
75
|
+
* flushers don't double-send the same row. Re-claiming an already-`sending`
|
|
76
|
+
* message is rejected (another sender owns it). A terminal message is rejected.
|
|
77
|
+
*
|
|
78
|
+
* @throws `ConvexError({ code: "NOT_FOUND" })` when no message has `messageId`.
|
|
79
|
+
* @throws `ConvexError({ code: "TERMINAL_STATE" })` when the message is terminal.
|
|
80
|
+
* @throws `ConvexError({ code: "INVALID_TRANSITION" })` when the message is
|
|
81
|
+
* already `sending` (claimed by another sender).
|
|
82
|
+
*/
|
|
83
|
+
export const markSending = mutation({
|
|
84
|
+
args: { messageId: v.string() },
|
|
85
|
+
returns: v.object({ attempts: v.number() }),
|
|
86
|
+
handler: async (ctx, args) => {
|
|
87
|
+
const msg = await ctx.db
|
|
88
|
+
.query("messages")
|
|
89
|
+
.withIndex("by_message_id", (q) => q.eq("messageId", args.messageId))
|
|
90
|
+
.unique();
|
|
91
|
+
if (msg === null) {
|
|
92
|
+
throw new ConvexError({
|
|
93
|
+
code: "NOT_FOUND",
|
|
94
|
+
message: `message "${args.messageId}" not found`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (msg.status === "sent" || msg.status === "failed") {
|
|
98
|
+
throw new ConvexError({
|
|
99
|
+
code: "TERMINAL_STATE",
|
|
100
|
+
message: `message "${args.messageId}" is already ${msg.status} and cannot transition`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (msg.status === "sending") {
|
|
104
|
+
throw new ConvexError({
|
|
105
|
+
code: "INVALID_TRANSITION",
|
|
106
|
+
message: `message "${args.messageId}" is already sending`,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
const attempts = msg.attempts + 1;
|
|
110
|
+
await ctx.db.patch(msg._id, {
|
|
111
|
+
status: "sending",
|
|
112
|
+
attempts,
|
|
113
|
+
updatedAt: Date.now(),
|
|
114
|
+
});
|
|
115
|
+
return { attempts };
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
/**
|
|
119
|
+
* Record a successful send — the message moves to terminal `sent`, recording the
|
|
120
|
+
* transport's own `providerId` (its message handle, opaque to the component) and
|
|
121
|
+
* clearing any prior `error`. Idempotent against a replayed callback: re-marking
|
|
122
|
+
* an already-`sent` message is a no-op (the recorded `providerId` is preserved).
|
|
123
|
+
* Any other terminal state (`failed`) is rejected — a `failed` message is final.
|
|
124
|
+
*
|
|
125
|
+
* @throws `ConvexError({ code: "NOT_FOUND" })` when no message has `messageId`.
|
|
126
|
+
* @throws `ConvexError({ code: "TERMINAL_STATE" })` when the message is already
|
|
127
|
+
* `failed` (a final state may not be transitioned out of).
|
|
128
|
+
*/
|
|
129
|
+
export const markSent = mutation({
|
|
130
|
+
args: { messageId: v.string(), providerId: v.optional(v.string()) },
|
|
131
|
+
returns: v.null(),
|
|
132
|
+
handler: async (ctx, args) => {
|
|
133
|
+
const msg = await ctx.db
|
|
134
|
+
.query("messages")
|
|
135
|
+
.withIndex("by_message_id", (q) => q.eq("messageId", args.messageId))
|
|
136
|
+
.unique();
|
|
137
|
+
if (msg === null) {
|
|
138
|
+
throw new ConvexError({
|
|
139
|
+
code: "NOT_FOUND",
|
|
140
|
+
message: `message "${args.messageId}" not found`,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (msg.status === "sent") {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
if (msg.status === "failed") {
|
|
147
|
+
throw new ConvexError({
|
|
148
|
+
code: "TERMINAL_STATE",
|
|
149
|
+
message: `message "${args.messageId}" is already failed and cannot transition`,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
await ctx.db.patch(msg._id, {
|
|
153
|
+
status: "sent",
|
|
154
|
+
providerId: args.providerId,
|
|
155
|
+
error: undefined,
|
|
156
|
+
updatedAt: Date.now(),
|
|
157
|
+
});
|
|
158
|
+
return null;
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
/**
|
|
162
|
+
* Record a failed send attempt, recording the host-supplied `error`. The outcome
|
|
163
|
+
* depends on the retry budget: if `attempts < maxAttempts` the message returns to
|
|
164
|
+
* `queued` for another attempt (`retried: true`); once attempts are exhausted it
|
|
165
|
+
* lands in terminal `failed` (`retried: false`). The host's backoff scheduler
|
|
166
|
+
* re-claims a re-queued message when ready. An already-`sent` message is rejected
|
|
167
|
+
* (it succeeded); an already-`failed` message is rejected (it is terminal).
|
|
168
|
+
*
|
|
169
|
+
* @throws `ConvexError({ code: "NOT_FOUND" })` when no message has `messageId`.
|
|
170
|
+
* @throws `ConvexError({ code: "TERMINAL_STATE" })` when the message is already
|
|
171
|
+
* `sent` or `failed`.
|
|
172
|
+
*/
|
|
173
|
+
export const markFailed = mutation({
|
|
174
|
+
args: { messageId: v.string(), error: v.optional(v.string()) },
|
|
175
|
+
returns: v.object({ status: v.union(v.literal("queued"), v.literal("failed")), retried: v.boolean() }),
|
|
176
|
+
handler: async (ctx, args) => {
|
|
177
|
+
const msg = await ctx.db
|
|
178
|
+
.query("messages")
|
|
179
|
+
.withIndex("by_message_id", (q) => q.eq("messageId", args.messageId))
|
|
180
|
+
.unique();
|
|
181
|
+
if (msg === null) {
|
|
182
|
+
throw new ConvexError({
|
|
183
|
+
code: "NOT_FOUND",
|
|
184
|
+
message: `message "${args.messageId}" not found`,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
if (msg.status === "sent" || msg.status === "failed") {
|
|
188
|
+
throw new ConvexError({
|
|
189
|
+
code: "TERMINAL_STATE",
|
|
190
|
+
message: `message "${args.messageId}" is already ${msg.status} and cannot transition`,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
const retried = msg.attempts < msg.maxAttempts;
|
|
194
|
+
const status = retried ? "queued" : "failed";
|
|
195
|
+
await ctx.db.patch(msg._id, {
|
|
196
|
+
status,
|
|
197
|
+
error: args.error,
|
|
198
|
+
updatedAt: Date.now(),
|
|
199
|
+
});
|
|
200
|
+
return { status, retried };
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
/**
|
|
204
|
+
* Delete up to `batch` terminal messages whose `updatedAt < before`, oldest first
|
|
205
|
+
* — `sent` then `failed`, each via the `by_status_updated` index. `before`
|
|
206
|
+
* defaults to the server clock (`Date.now()`) when omitted, so the built-in cron
|
|
207
|
+
* sweeps exactly the messages terminal-and-stale as of the run. If a full batch
|
|
208
|
+
* was removed there may be more, so the sweep self-reschedules through
|
|
209
|
+
* `ctx.scheduler` until a short batch signals the tail is clean. Idempotent: only
|
|
210
|
+
* ever removes already-terminal, past-retention rows. Queued and sending messages
|
|
211
|
+
* are never pruned.
|
|
212
|
+
*/
|
|
213
|
+
export const prune = mutation({
|
|
214
|
+
args: { before: v.optional(v.number()), batch: v.number() },
|
|
215
|
+
returns: v.number(),
|
|
216
|
+
handler: async (ctx, args) => {
|
|
217
|
+
const before = args.before ?? Date.now();
|
|
218
|
+
// Both terminal states, queried explicitly (no query-in-loop): sent first,
|
|
219
|
+
// then failed for whatever batch budget remains.
|
|
220
|
+
const sent = await ctx.db
|
|
221
|
+
.query("messages")
|
|
222
|
+
.withIndex("by_status_updated", (q) => q.eq("status", "sent").lt("updatedAt", before))
|
|
223
|
+
.take(args.batch);
|
|
224
|
+
const failed = sent.length < args.batch
|
|
225
|
+
? await ctx.db
|
|
226
|
+
.query("messages")
|
|
227
|
+
.withIndex("by_status_updated", (q) => q.eq("status", "failed").lt("updatedAt", before))
|
|
228
|
+
.take(args.batch - sent.length)
|
|
229
|
+
: [];
|
|
230
|
+
for (const row of [...sent, ...failed]) {
|
|
231
|
+
await ctx.db.delete(row._id);
|
|
232
|
+
}
|
|
233
|
+
const removed = sent.length + failed.length;
|
|
234
|
+
if (removed === args.batch) {
|
|
235
|
+
await ctx.scheduler.runAfter(0, api.mutations.prune, {
|
|
236
|
+
before,
|
|
237
|
+
batch: args.batch,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
return removed;
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
//# sourceMappingURL=mutations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutations.js","sourceRoot":"","sources":["../../src/component/mutations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** The current envelope for one message, or `null` if no such id is held. */
|
|
2
|
+
export declare const get: import("convex/server").RegisteredQuery<"public", {
|
|
3
|
+
messageId: string;
|
|
4
|
+
}, Promise<{
|
|
5
|
+
messageId: string;
|
|
6
|
+
to: string;
|
|
7
|
+
from: string;
|
|
8
|
+
transport: string;
|
|
9
|
+
status: "queued" | "sending" | "sent" | "failed";
|
|
10
|
+
payload: any;
|
|
11
|
+
subjectRef: string | undefined;
|
|
12
|
+
idempotencyKey: string | undefined;
|
|
13
|
+
providerId: string | undefined;
|
|
14
|
+
attempts: number;
|
|
15
|
+
maxAttempts: number;
|
|
16
|
+
error: string | undefined;
|
|
17
|
+
createdAt: number;
|
|
18
|
+
updatedAt: number;
|
|
19
|
+
} | null>>;
|
|
20
|
+
/**
|
|
21
|
+
* Page messages in one `status`, oldest first via the `by_status` index. Takes
|
|
22
|
+
* the standard Convex `paginationOpts` and returns the standard paginated
|
|
23
|
+
* envelope (`page`, `isDone`, `continueCursor`) so the host can poll the
|
|
24
|
+
* `queued` backlog to flush, watch `sending`, or audit a terminal `sent`/`failed`
|
|
25
|
+
* history reactively.
|
|
26
|
+
*/
|
|
27
|
+
export declare const listByStatus: import("convex/server").RegisteredQuery<"public", {
|
|
28
|
+
status: "queued" | "sending" | "sent" | "failed";
|
|
29
|
+
paginationOpts: {
|
|
30
|
+
id?: number;
|
|
31
|
+
endCursor?: string | null;
|
|
32
|
+
maximumRowsRead?: number;
|
|
33
|
+
maximumBytesRead?: number;
|
|
34
|
+
numItems: number;
|
|
35
|
+
cursor: string | null;
|
|
36
|
+
};
|
|
37
|
+
}, Promise<{
|
|
38
|
+
page: {
|
|
39
|
+
messageId: string;
|
|
40
|
+
to: string;
|
|
41
|
+
from: string;
|
|
42
|
+
transport: string;
|
|
43
|
+
status: "queued" | "sending" | "sent" | "failed";
|
|
44
|
+
payload: any;
|
|
45
|
+
subjectRef: string | undefined;
|
|
46
|
+
idempotencyKey: string | undefined;
|
|
47
|
+
providerId: string | undefined;
|
|
48
|
+
attempts: number;
|
|
49
|
+
maxAttempts: number;
|
|
50
|
+
error: string | undefined;
|
|
51
|
+
createdAt: number;
|
|
52
|
+
updatedAt: number;
|
|
53
|
+
}[];
|
|
54
|
+
isDone: boolean;
|
|
55
|
+
continueCursor: import("convex/server").Cursor;
|
|
56
|
+
splitCursor?: import("convex/server").Cursor | null;
|
|
57
|
+
pageStatus?: "SplitRecommended" | "SplitRequired" | null;
|
|
58
|
+
}>>;
|
|
59
|
+
//# sourceMappingURL=queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/component/queries.ts"],"names":[],"mappings":"AA0BA,6EAA6E;AAC7E,eAAO,MAAM,GAAG;;;;;;;;;;;;;;;;;UAUd,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuBvB,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { paginationOptsValidator } from "convex/server";
|
|
3
|
+
import { query } from "./_generated/server";
|
|
4
|
+
import { messageStatus, messageView } from "./validators";
|
|
5
|
+
/** Project a stored message row to its public view (drops internal fields). */
|
|
6
|
+
function view(msg) {
|
|
7
|
+
return {
|
|
8
|
+
messageId: msg.messageId,
|
|
9
|
+
to: msg.to,
|
|
10
|
+
from: msg.from,
|
|
11
|
+
transport: msg.transport,
|
|
12
|
+
status: msg.status,
|
|
13
|
+
payload: msg.payload,
|
|
14
|
+
subjectRef: msg.subjectRef,
|
|
15
|
+
idempotencyKey: msg.idempotencyKey,
|
|
16
|
+
providerId: msg.providerId,
|
|
17
|
+
attempts: msg.attempts,
|
|
18
|
+
maxAttempts: msg.maxAttempts,
|
|
19
|
+
error: msg.error,
|
|
20
|
+
createdAt: msg.createdAt,
|
|
21
|
+
updatedAt: msg.updatedAt,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** The current envelope for one message, or `null` if no such id is held. */
|
|
25
|
+
export const get = query({
|
|
26
|
+
args: { messageId: v.string() },
|
|
27
|
+
returns: v.union(v.null(), messageView),
|
|
28
|
+
handler: async (ctx, args) => {
|
|
29
|
+
const msg = await ctx.db
|
|
30
|
+
.query("messages")
|
|
31
|
+
.withIndex("by_message_id", (q) => q.eq("messageId", args.messageId))
|
|
32
|
+
.unique();
|
|
33
|
+
return msg === null ? null : view(msg);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Page messages in one `status`, oldest first via the `by_status` index. Takes
|
|
38
|
+
* the standard Convex `paginationOpts` and returns the standard paginated
|
|
39
|
+
* envelope (`page`, `isDone`, `continueCursor`) so the host can poll the
|
|
40
|
+
* `queued` backlog to flush, watch `sending`, or audit a terminal `sent`/`failed`
|
|
41
|
+
* history reactively.
|
|
42
|
+
*/
|
|
43
|
+
export const listByStatus = query({
|
|
44
|
+
args: { status: messageStatus, paginationOpts: paginationOptsValidator },
|
|
45
|
+
returns: v.object({
|
|
46
|
+
page: v.array(messageView),
|
|
47
|
+
isDone: v.boolean(),
|
|
48
|
+
continueCursor: v.string(),
|
|
49
|
+
splitCursor: v.optional(v.union(v.string(), v.null())),
|
|
50
|
+
pageStatus: v.optional(v.union(v.literal("SplitRecommended"), v.literal("SplitRequired"), v.null())),
|
|
51
|
+
}),
|
|
52
|
+
handler: async (ctx, args) => {
|
|
53
|
+
const result = await ctx.db
|
|
54
|
+
.query("messages")
|
|
55
|
+
.withIndex("by_status", (q) => q.eq("status", args.status))
|
|
56
|
+
.order("asc")
|
|
57
|
+
.paginate(args.paginationOpts);
|
|
58
|
+
return { ...result, page: result.page.map(view) };
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
//# sourceMappingURL=queries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/component/queries.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandboxed table — the outbound-email queue's own concern. A `messageId` is a
|
|
3
|
+
* host-opaque string that uniquely names one queued message; `to`/`from` are the
|
|
4
|
+
* opaque envelope addresses; `transport` names the host-configured adapter (never
|
|
5
|
+
* a baked-in vendor); `status` tracks the lifecycle. `payload` carries the opaque
|
|
6
|
+
* rendered message host data (never inspected). `subjectRef` is an opaque host
|
|
7
|
+
* ref (e.g. the addressee subject) for subject-centric listing; `idempotencyKey`
|
|
8
|
+
* deduplicates double-enqueue; `attempts`/`maxAttempts` drive retry-vs-terminal.
|
|
9
|
+
*
|
|
10
|
+
* Indexes: `by_message_id` (lookup), `by_idempotency_key` (dedup), `by_status`
|
|
11
|
+
* (poll a status queue oldest-first), `by_subject` (subject-centric listing), and
|
|
12
|
+
* `by_status_updated` (retention sweep — prune terminal rows oldest-first).
|
|
13
|
+
*/
|
|
14
|
+
declare const _default: import("convex/server").SchemaDefinition<{
|
|
15
|
+
messages: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
16
|
+
payload?: any;
|
|
17
|
+
subjectRef?: string | undefined;
|
|
18
|
+
idempotencyKey?: string | undefined;
|
|
19
|
+
providerId?: string | undefined;
|
|
20
|
+
error?: string | undefined;
|
|
21
|
+
messageId: string;
|
|
22
|
+
to: string;
|
|
23
|
+
from: string;
|
|
24
|
+
transport: string;
|
|
25
|
+
maxAttempts: number;
|
|
26
|
+
status: "queued" | "sending" | "sent" | "failed";
|
|
27
|
+
attempts: number;
|
|
28
|
+
createdAt: number;
|
|
29
|
+
updatedAt: number;
|
|
30
|
+
}, {
|
|
31
|
+
messageId: import("convex/values").VString<string, "required">;
|
|
32
|
+
to: import("convex/values").VString<string, "required">;
|
|
33
|
+
from: import("convex/values").VString<string, "required">;
|
|
34
|
+
transport: import("convex/values").VString<string, "required">;
|
|
35
|
+
status: import("convex/values").VUnion<"queued" | "sending" | "sent" | "failed", [import("convex/values").VLiteral<"queued", "required">, import("convex/values").VLiteral<"sending", "required">, import("convex/values").VLiteral<"sent", "required">, import("convex/values").VLiteral<"failed", "required">], "required", never>;
|
|
36
|
+
payload: import("convex/values").VAny<any, "optional", string>;
|
|
37
|
+
subjectRef: import("convex/values").VString<string | undefined, "optional">;
|
|
38
|
+
idempotencyKey: import("convex/values").VString<string | undefined, "optional">;
|
|
39
|
+
providerId: import("convex/values").VString<string | undefined, "optional">;
|
|
40
|
+
attempts: import("convex/values").VFloat64<number, "required">;
|
|
41
|
+
maxAttempts: import("convex/values").VFloat64<number, "required">;
|
|
42
|
+
error: import("convex/values").VString<string | undefined, "optional">;
|
|
43
|
+
createdAt: import("convex/values").VFloat64<number, "required">;
|
|
44
|
+
updatedAt: import("convex/values").VFloat64<number, "required">;
|
|
45
|
+
}, "required", "messageId" | "to" | "from" | "transport" | "payload" | "subjectRef" | "idempotencyKey" | "maxAttempts" | "providerId" | "error" | "status" | "attempts" | "createdAt" | "updatedAt" | `payload.${string}`>, {
|
|
46
|
+
by_message_id: ["messageId", "_creationTime"];
|
|
47
|
+
by_idempotency_key: ["idempotencyKey", "_creationTime"];
|
|
48
|
+
by_status: ["status", "createdAt", "_creationTime"];
|
|
49
|
+
by_subject: ["subjectRef", "createdAt", "_creationTime"];
|
|
50
|
+
by_status_updated: ["status", "updatedAt", "_creationTime"];
|
|
51
|
+
}, {}, {}>;
|
|
52
|
+
}, true>;
|
|
53
|
+
export default _default;
|
|
54
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/component/schema.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACH,wBAsBG"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { defineSchema, defineTable } from "convex/server";
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
import { jsonValue, messageStatus } from "./validators";
|
|
4
|
+
/**
|
|
5
|
+
* Sandboxed table — the outbound-email queue's own concern. A `messageId` is a
|
|
6
|
+
* host-opaque string that uniquely names one queued message; `to`/`from` are the
|
|
7
|
+
* opaque envelope addresses; `transport` names the host-configured adapter (never
|
|
8
|
+
* a baked-in vendor); `status` tracks the lifecycle. `payload` carries the opaque
|
|
9
|
+
* rendered message host data (never inspected). `subjectRef` is an opaque host
|
|
10
|
+
* ref (e.g. the addressee subject) for subject-centric listing; `idempotencyKey`
|
|
11
|
+
* deduplicates double-enqueue; `attempts`/`maxAttempts` drive retry-vs-terminal.
|
|
12
|
+
*
|
|
13
|
+
* Indexes: `by_message_id` (lookup), `by_idempotency_key` (dedup), `by_status`
|
|
14
|
+
* (poll a status queue oldest-first), `by_subject` (subject-centric listing), and
|
|
15
|
+
* `by_status_updated` (retention sweep — prune terminal rows oldest-first).
|
|
16
|
+
*/
|
|
17
|
+
export default defineSchema({
|
|
18
|
+
messages: defineTable({
|
|
19
|
+
messageId: v.string(),
|
|
20
|
+
to: v.string(),
|
|
21
|
+
from: v.string(),
|
|
22
|
+
transport: v.string(),
|
|
23
|
+
status: messageStatus,
|
|
24
|
+
payload: v.optional(jsonValue),
|
|
25
|
+
subjectRef: v.optional(v.string()),
|
|
26
|
+
idempotencyKey: v.optional(v.string()),
|
|
27
|
+
providerId: v.optional(v.string()),
|
|
28
|
+
attempts: v.number(),
|
|
29
|
+
maxAttempts: v.number(),
|
|
30
|
+
error: v.optional(v.string()),
|
|
31
|
+
createdAt: v.number(),
|
|
32
|
+
updatedAt: v.number(),
|
|
33
|
+
})
|
|
34
|
+
.index("by_message_id", ["messageId"])
|
|
35
|
+
.index("by_idempotency_key", ["idempotencyKey"])
|
|
36
|
+
.index("by_status", ["status", "createdAt"])
|
|
37
|
+
.index("by_subject", ["subjectRef", "createdAt"])
|
|
38
|
+
.index("by_status_updated", ["status", "updatedAt"]),
|
|
39
|
+
});
|
|
40
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/component/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,CAAC,EAAE,MAAM,eAAe,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAExD;;;;;;;;;;;;GAYG;AACH,eAAe,YAAY,CAAC;IAC1B,QAAQ,EAAE,WAAW,CAAC;QACpB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;QACrB,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;QACd,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;QACrB,MAAM,EAAE,aAAa;QACrB,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC9B,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QAClC,cAAc,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QACtC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QAClC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;QACpB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;QACvB,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QAC7B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;QACrB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;KACtB,CAAC;SACC,KAAK,CAAC,eAAe,EAAE,CAAC,WAAW,CAAC,CAAC;SACrC,KAAK,CAAC,oBAAoB,EAAE,CAAC,gBAAgB,CAAC,CAAC;SAC/C,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;SAC3C,KAAK,CAAC,YAAY,EAAE,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;SAChD,KAAK,CAAC,mBAAmB,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;CACvD,CAAC,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opaque host-owned data stored on a message — its `payload` (rendered body,
|
|
3
|
+
* template name + data, headers, attachment refs — whatever the host's transport
|
|
4
|
+
* needs to send). The component never inspects it; it is last-resort arbitrary
|
|
5
|
+
* data, aliased here rather than left bare in function signatures. The host
|
|
6
|
+
* narrows it at the {@link Email} client boundary via an optional
|
|
7
|
+
* `payloadValidator` parser.
|
|
8
|
+
*
|
|
9
|
+
* This is the single documented `v.any()` escape hatch in the component; the lint
|
|
10
|
+
* rule `convex-rules/no-bare-v-any` is satisfied by routing every arbitrary host
|
|
11
|
+
* payload through this alias instead of a bare `v.any()`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const jsonValue: import("convex/values").VAny<any, "required", string>;
|
|
14
|
+
/** The four lifecycle states a message moves through. */
|
|
15
|
+
export declare const messageStatus: import("convex/values").VUnion<"queued" | "sending" | "sent" | "failed", [import("convex/values").VLiteral<"queued", "required">, import("convex/values").VLiteral<"sending", "required">, import("convex/values").VLiteral<"sent", "required">, import("convex/values").VLiteral<"failed", "required">], "required", never>;
|
|
16
|
+
/**
|
|
17
|
+
* Public projection of a message returned by {@link get}. `payload` is opaque
|
|
18
|
+
* host data; `transport` names the host-configured adapter the message is routed
|
|
19
|
+
* through (never a baked-in vendor); `providerId` is the transport's own message
|
|
20
|
+
* handle recorded on a successful send; `error` is the host-supplied failure
|
|
21
|
+
* reason recorded on a failed attempt.
|
|
22
|
+
*/
|
|
23
|
+
export declare const messageView: import("convex/values").VObject<{
|
|
24
|
+
payload?: any;
|
|
25
|
+
subjectRef?: string | undefined;
|
|
26
|
+
idempotencyKey?: string | undefined;
|
|
27
|
+
providerId?: string | undefined;
|
|
28
|
+
error?: string | undefined;
|
|
29
|
+
messageId: string;
|
|
30
|
+
to: string;
|
|
31
|
+
from: string;
|
|
32
|
+
transport: string;
|
|
33
|
+
maxAttempts: number;
|
|
34
|
+
status: "queued" | "sending" | "sent" | "failed";
|
|
35
|
+
attempts: number;
|
|
36
|
+
createdAt: number;
|
|
37
|
+
updatedAt: number;
|
|
38
|
+
}, {
|
|
39
|
+
messageId: import("convex/values").VString<string, "required">;
|
|
40
|
+
to: import("convex/values").VString<string, "required">;
|
|
41
|
+
from: import("convex/values").VString<string, "required">;
|
|
42
|
+
transport: import("convex/values").VString<string, "required">;
|
|
43
|
+
status: import("convex/values").VUnion<"queued" | "sending" | "sent" | "failed", [import("convex/values").VLiteral<"queued", "required">, import("convex/values").VLiteral<"sending", "required">, import("convex/values").VLiteral<"sent", "required">, import("convex/values").VLiteral<"failed", "required">], "required", never>;
|
|
44
|
+
payload: import("convex/values").VAny<any, "optional", string>;
|
|
45
|
+
subjectRef: import("convex/values").VString<string | undefined, "optional">;
|
|
46
|
+
idempotencyKey: import("convex/values").VString<string | undefined, "optional">;
|
|
47
|
+
providerId: import("convex/values").VString<string | undefined, "optional">;
|
|
48
|
+
attempts: import("convex/values").VFloat64<number, "required">;
|
|
49
|
+
maxAttempts: import("convex/values").VFloat64<number, "required">;
|
|
50
|
+
error: import("convex/values").VString<string | undefined, "optional">;
|
|
51
|
+
createdAt: import("convex/values").VFloat64<number, "required">;
|
|
52
|
+
updatedAt: import("convex/values").VFloat64<number, "required">;
|
|
53
|
+
}, "required", "messageId" | "to" | "from" | "transport" | "payload" | "subjectRef" | "idempotencyKey" | "maxAttempts" | "providerId" | "error" | "status" | "attempts" | "createdAt" | "updatedAt" | `payload.${string}`>;
|
|
54
|
+
//# sourceMappingURL=validators.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../../src/component/validators.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,SAAS,uDAAU,CAAC;AAEjC,yDAAyD;AACzD,eAAO,MAAM,aAAa,8TAKzB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0NAetB,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
/**
|
|
3
|
+
* Opaque host-owned data stored on a message — its `payload` (rendered body,
|
|
4
|
+
* template name + data, headers, attachment refs — whatever the host's transport
|
|
5
|
+
* needs to send). The component never inspects it; it is last-resort arbitrary
|
|
6
|
+
* data, aliased here rather than left bare in function signatures. The host
|
|
7
|
+
* narrows it at the {@link Email} client boundary via an optional
|
|
8
|
+
* `payloadValidator` parser.
|
|
9
|
+
*
|
|
10
|
+
* This is the single documented `v.any()` escape hatch in the component; the lint
|
|
11
|
+
* rule `convex-rules/no-bare-v-any` is satisfied by routing every arbitrary host
|
|
12
|
+
* payload through this alias instead of a bare `v.any()`.
|
|
13
|
+
*/
|
|
14
|
+
export const jsonValue = v.any();
|
|
15
|
+
/** The four lifecycle states a message moves through. */
|
|
16
|
+
export const messageStatus = v.union(v.literal("queued"), v.literal("sending"), v.literal("sent"), v.literal("failed"));
|
|
17
|
+
/**
|
|
18
|
+
* Public projection of a message returned by {@link get}. `payload` is opaque
|
|
19
|
+
* host data; `transport` names the host-configured adapter the message is routed
|
|
20
|
+
* through (never a baked-in vendor); `providerId` is the transport's own message
|
|
21
|
+
* handle recorded on a successful send; `error` is the host-supplied failure
|
|
22
|
+
* reason recorded on a failed attempt.
|
|
23
|
+
*/
|
|
24
|
+
export const messageView = v.object({
|
|
25
|
+
messageId: v.string(),
|
|
26
|
+
to: v.string(),
|
|
27
|
+
from: v.string(),
|
|
28
|
+
transport: v.string(),
|
|
29
|
+
status: messageStatus,
|
|
30
|
+
payload: v.optional(jsonValue),
|
|
31
|
+
subjectRef: v.optional(v.string()),
|
|
32
|
+
idempotencyKey: v.optional(v.string()),
|
|
33
|
+
providerId: v.optional(v.string()),
|
|
34
|
+
attempts: v.number(),
|
|
35
|
+
maxAttempts: v.number(),
|
|
36
|
+
error: v.optional(v.string()),
|
|
37
|
+
createdAt: v.number(),
|
|
38
|
+
updatedAt: v.number(),
|
|
39
|
+
});
|
|
40
|
+
//# sourceMappingURL=validators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validators.js","sourceRoot":"","sources":["../../src/component/validators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,eAAe,CAAC;AAElC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;AAEjC,yDAAyD;AACzD,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAClC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,EACnB,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,EACpB,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EACjB,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CACpB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,MAAM,EAAE,aAAa;IACrB,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC;IAC9B,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAClC,cAAc,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACtC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAClC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC7B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;CACtB,CAAC,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The optional generic-JMAP transport adapter — a `./jmap` entry the host imports
|
|
3
|
+
* into its OWN Convex action to actually send queued messages over a JMAP server's
|
|
4
|
+
* HTTP API (Stalwart, Fastmail, Cyrus, any JMAP server). It is NOT part of the
|
|
5
|
+
* sandboxed component: the component records intent + status and never sends.
|
|
6
|
+
*
|
|
7
|
+
* Unlike `./smtp`, this layer is **pure** and dependency-free: it drives an
|
|
8
|
+
* injected `fetch` (the host's runtime `fetch`), so it needs no `nodemailer`, no
|
|
9
|
+
* `"use node"` action (JMAP is plain HTTP — `fetch` runs in a normal Convex
|
|
10
|
+
* action), and is 100%-covered with a fake `fetch` (no excluded wrapper).
|
|
11
|
+
*
|
|
12
|
+
* Tree-shake boundary: a backend-only consumer importing `@vllnt/convex-email`
|
|
13
|
+
* (the `.` entry) pulls ZERO JMAP code. Importing this `./jmap` entry is the
|
|
14
|
+
* explicit opt-in.
|
|
15
|
+
*
|
|
16
|
+
* JMAP is a protocol (RFC 8620/8621), not a vendor — Stalwart is one configured
|
|
17
|
+
* server (`{ endpoint, token }`), never baked in, so this is `./jmap`, never
|
|
18
|
+
* `./stalwart`.
|
|
19
|
+
*/
|
|
20
|
+
export { validateJmapConfig, buildEmailCreate, buildSubmitRequest, parseSubmitResponse, sendViaJmap, discoverJmapSession, createJmapSender, } from "./send.js";
|
|
21
|
+
export type { JmapConfig, JmapMessage, JmapSendResult, JmapSender, JmapFetch, JmapRequestInit, JmapResponse, JmapDiscoverOptions, } from "./types.js";
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/jmap/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,WAAW,EACX,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,UAAU,EACV,WAAW,EACX,cAAc,EACd,UAAU,EACV,SAAS,EACT,eAAe,EACf,YAAY,EACZ,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The optional generic-JMAP transport adapter — a `./jmap` entry the host imports
|
|
3
|
+
* into its OWN Convex action to actually send queued messages over a JMAP server's
|
|
4
|
+
* HTTP API (Stalwart, Fastmail, Cyrus, any JMAP server). It is NOT part of the
|
|
5
|
+
* sandboxed component: the component records intent + status and never sends.
|
|
6
|
+
*
|
|
7
|
+
* Unlike `./smtp`, this layer is **pure** and dependency-free: it drives an
|
|
8
|
+
* injected `fetch` (the host's runtime `fetch`), so it needs no `nodemailer`, no
|
|
9
|
+
* `"use node"` action (JMAP is plain HTTP — `fetch` runs in a normal Convex
|
|
10
|
+
* action), and is 100%-covered with a fake `fetch` (no excluded wrapper).
|
|
11
|
+
*
|
|
12
|
+
* Tree-shake boundary: a backend-only consumer importing `@vllnt/convex-email`
|
|
13
|
+
* (the `.` entry) pulls ZERO JMAP code. Importing this `./jmap` entry is the
|
|
14
|
+
* explicit opt-in.
|
|
15
|
+
*
|
|
16
|
+
* JMAP is a protocol (RFC 8620/8621), not a vendor — Stalwart is one configured
|
|
17
|
+
* server (`{ endpoint, token }`), never baked in, so this is `./jmap`, never
|
|
18
|
+
* `./stalwart`.
|
|
19
|
+
*/
|
|
20
|
+
export { validateJmapConfig, buildEmailCreate, buildSubmitRequest, parseSubmitResponse, sendViaJmap, discoverJmapSession, createJmapSender, } from "./send.js";
|
|
21
|
+
//# sourceMappingURL=index.js.map
|