fastmail-mcp-server 0.1.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.
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/package.json +46 -0
- package/src/index.ts +525 -0
- package/src/jmap/client.ts +149 -0
- package/src/jmap/methods.ts +500 -0
- package/src/jmap/types.ts +198 -0
- package/src/test.ts +69 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import { getClient } from "./client.js";
|
|
2
|
+
import type {
|
|
3
|
+
Email,
|
|
4
|
+
EmailAddress,
|
|
5
|
+
EmailCreate,
|
|
6
|
+
Identity,
|
|
7
|
+
Mailbox,
|
|
8
|
+
} from "./types.js";
|
|
9
|
+
|
|
10
|
+
// Standard properties to fetch for email listings
|
|
11
|
+
const EMAIL_LIST_PROPERTIES = [
|
|
12
|
+
"id",
|
|
13
|
+
"threadId",
|
|
14
|
+
"mailboxIds",
|
|
15
|
+
"keywords",
|
|
16
|
+
"from",
|
|
17
|
+
"to",
|
|
18
|
+
"subject",
|
|
19
|
+
"receivedAt",
|
|
20
|
+
"preview",
|
|
21
|
+
"hasAttachment",
|
|
22
|
+
"size",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
// Full properties for single email fetch
|
|
26
|
+
const EMAIL_FULL_PROPERTIES = [
|
|
27
|
+
...EMAIL_LIST_PROPERTIES,
|
|
28
|
+
"cc",
|
|
29
|
+
"bcc",
|
|
30
|
+
"replyTo",
|
|
31
|
+
"sentAt",
|
|
32
|
+
"messageId",
|
|
33
|
+
"inReplyTo",
|
|
34
|
+
"references",
|
|
35
|
+
"bodyValues",
|
|
36
|
+
"textBody",
|
|
37
|
+
"htmlBody",
|
|
38
|
+
"attachments",
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
// ============ Mailbox Methods ============
|
|
42
|
+
|
|
43
|
+
export async function listMailboxes(): Promise<Mailbox[]> {
|
|
44
|
+
const client = getClient();
|
|
45
|
+
const accountId = await client.getAccountId();
|
|
46
|
+
|
|
47
|
+
const result = await client.call<{ list: Mailbox[] }>("Mailbox/get", {
|
|
48
|
+
accountId,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return result.list;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function getMailboxByName(name: string): Promise<Mailbox | null> {
|
|
55
|
+
const mailboxes = await listMailboxes();
|
|
56
|
+
const lowerName = name.toLowerCase();
|
|
57
|
+
|
|
58
|
+
// Try exact match first, then role match, then case-insensitive
|
|
59
|
+
return (
|
|
60
|
+
mailboxes.find((m) => m.name === name) ||
|
|
61
|
+
mailboxes.find((m) => m.role === lowerName) ||
|
|
62
|
+
mailboxes.find((m) => m.name.toLowerCase() === lowerName) ||
|
|
63
|
+
null
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function getMailboxById(id: string): Promise<Mailbox | null> {
|
|
68
|
+
const mailboxes = await listMailboxes();
|
|
69
|
+
return mailboxes.find((m) => m.id === id) || null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ============ Email Methods ============
|
|
73
|
+
|
|
74
|
+
export async function listEmails(
|
|
75
|
+
mailboxName: string,
|
|
76
|
+
limit = 25,
|
|
77
|
+
): Promise<Email[]> {
|
|
78
|
+
const client = getClient();
|
|
79
|
+
const accountId = await client.getAccountId();
|
|
80
|
+
|
|
81
|
+
const mailbox = await getMailboxByName(mailboxName);
|
|
82
|
+
if (!mailbox) {
|
|
83
|
+
throw new Error(`Mailbox not found: ${mailboxName}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Query for email IDs
|
|
87
|
+
const queryResult = await client.call<{ ids: string[] }>("Email/query", {
|
|
88
|
+
accountId,
|
|
89
|
+
filter: { inMailbox: mailbox.id },
|
|
90
|
+
sort: [{ property: "receivedAt", isAscending: false }],
|
|
91
|
+
limit,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (queryResult.ids.length === 0) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Fetch email details
|
|
99
|
+
const getResult = await client.call<{ list: Email[] }>("Email/get", {
|
|
100
|
+
accountId,
|
|
101
|
+
ids: queryResult.ids,
|
|
102
|
+
properties: EMAIL_LIST_PROPERTIES,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return getResult.list;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function getEmail(emailId: string): Promise<Email | null> {
|
|
109
|
+
const client = getClient();
|
|
110
|
+
const accountId = await client.getAccountId();
|
|
111
|
+
|
|
112
|
+
const result = await client.call<{ list: Email[] }>("Email/get", {
|
|
113
|
+
accountId,
|
|
114
|
+
ids: [emailId],
|
|
115
|
+
properties: EMAIL_FULL_PROPERTIES,
|
|
116
|
+
fetchTextBodyValues: true,
|
|
117
|
+
fetchHTMLBodyValues: true,
|
|
118
|
+
maxBodyValueBytes: 1024 * 1024, // 1MB
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return result.list[0] || null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function searchEmails(
|
|
125
|
+
query: string,
|
|
126
|
+
limit = 25,
|
|
127
|
+
): Promise<Email[]> {
|
|
128
|
+
const client = getClient();
|
|
129
|
+
const accountId = await client.getAccountId();
|
|
130
|
+
|
|
131
|
+
// Query for email IDs with text filter
|
|
132
|
+
const queryResult = await client.call<{ ids: string[] }>("Email/query", {
|
|
133
|
+
accountId,
|
|
134
|
+
filter: { text: query },
|
|
135
|
+
sort: [{ property: "receivedAt", isAscending: false }],
|
|
136
|
+
limit,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (queryResult.ids.length === 0) {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Fetch email details
|
|
144
|
+
const getResult = await client.call<{ list: Email[] }>("Email/get", {
|
|
145
|
+
accountId,
|
|
146
|
+
ids: queryResult.ids,
|
|
147
|
+
properties: EMAIL_LIST_PROPERTIES,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return getResult.list;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ============ Email Modification Methods ============
|
|
154
|
+
|
|
155
|
+
export async function moveEmail(
|
|
156
|
+
emailId: string,
|
|
157
|
+
targetMailboxName: string,
|
|
158
|
+
): Promise<void> {
|
|
159
|
+
const client = getClient();
|
|
160
|
+
const accountId = await client.getAccountId();
|
|
161
|
+
|
|
162
|
+
const targetMailbox = await getMailboxByName(targetMailboxName);
|
|
163
|
+
if (!targetMailbox) {
|
|
164
|
+
throw new Error(`Target mailbox not found: ${targetMailboxName}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Get current email to find its mailboxes
|
|
168
|
+
const email = await getEmail(emailId);
|
|
169
|
+
if (!email) {
|
|
170
|
+
throw new Error(`Email not found: ${emailId}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Build new mailboxIds - remove all current, add target
|
|
174
|
+
const newMailboxIds: Record<string, boolean> = { [targetMailbox.id]: true };
|
|
175
|
+
|
|
176
|
+
await client.call("Email/set", {
|
|
177
|
+
accountId,
|
|
178
|
+
update: {
|
|
179
|
+
[emailId]: { mailboxIds: newMailboxIds },
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function setEmailKeywords(
|
|
185
|
+
emailId: string,
|
|
186
|
+
keywords: Record<string, boolean>,
|
|
187
|
+
): Promise<void> {
|
|
188
|
+
const client = getClient();
|
|
189
|
+
const accountId = await client.getAccountId();
|
|
190
|
+
|
|
191
|
+
await client.call("Email/set", {
|
|
192
|
+
accountId,
|
|
193
|
+
update: {
|
|
194
|
+
[emailId]: { keywords },
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export async function markAsRead(emailId: string, read = true): Promise<void> {
|
|
200
|
+
const email = await getEmail(emailId);
|
|
201
|
+
if (!email) {
|
|
202
|
+
throw new Error(`Email not found: ${emailId}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const keywords: Record<string, boolean> = { ...email.keywords };
|
|
206
|
+
if (read) {
|
|
207
|
+
keywords.$seen = true;
|
|
208
|
+
} else {
|
|
209
|
+
delete keywords.$seen;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await setEmailKeywords(emailId, keywords);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export async function markAsSpam(emailId: string): Promise<void> {
|
|
216
|
+
const client = getClient();
|
|
217
|
+
const accountId = await client.getAccountId();
|
|
218
|
+
|
|
219
|
+
const junkMailbox = await getMailboxByName("junk");
|
|
220
|
+
if (!junkMailbox) {
|
|
221
|
+
throw new Error("Junk mailbox not found");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Move to Junk and set $junk keyword (trains spam filter)
|
|
225
|
+
const email = await getEmail(emailId);
|
|
226
|
+
if (!email) {
|
|
227
|
+
throw new Error(`Email not found: ${emailId}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const keywords: Record<string, boolean> = { ...email.keywords, $junk: true };
|
|
231
|
+
delete keywords.$notjunk;
|
|
232
|
+
|
|
233
|
+
await client.call("Email/set", {
|
|
234
|
+
accountId,
|
|
235
|
+
update: {
|
|
236
|
+
[emailId]: {
|
|
237
|
+
mailboxIds: { [junkMailbox.id]: true },
|
|
238
|
+
keywords,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ============ Identity Methods ============
|
|
245
|
+
|
|
246
|
+
export async function getIdentities(): Promise<Identity[]> {
|
|
247
|
+
const client = getClient();
|
|
248
|
+
const accountId = await client.getAccountId();
|
|
249
|
+
|
|
250
|
+
const result = await client.call<{ list: Identity[] }>("Identity/get", {
|
|
251
|
+
accountId,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return result.list;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export async function getDefaultIdentity(): Promise<Identity> {
|
|
258
|
+
const identities = await getIdentities();
|
|
259
|
+
const identity = identities[0];
|
|
260
|
+
if (!identity) {
|
|
261
|
+
throw new Error("No email identity found");
|
|
262
|
+
}
|
|
263
|
+
return identity;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ============ Email Sending Methods ============
|
|
267
|
+
|
|
268
|
+
export interface SendEmailParams {
|
|
269
|
+
to: EmailAddress[];
|
|
270
|
+
subject: string;
|
|
271
|
+
textBody: string;
|
|
272
|
+
htmlBody?: string;
|
|
273
|
+
cc?: EmailAddress[];
|
|
274
|
+
bcc?: EmailAddress[];
|
|
275
|
+
inReplyTo?: string;
|
|
276
|
+
references?: string[];
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export async function sendEmail(params: SendEmailParams): Promise<string> {
|
|
280
|
+
const client = getClient();
|
|
281
|
+
const accountId = await client.getAccountId();
|
|
282
|
+
|
|
283
|
+
const identity = await getDefaultIdentity();
|
|
284
|
+
|
|
285
|
+
// Get Drafts and Sent mailbox IDs
|
|
286
|
+
const mailboxes = await listMailboxes();
|
|
287
|
+
const draftsMailbox = mailboxes.find((m) => m.role === "drafts");
|
|
288
|
+
const sentMailbox = mailboxes.find((m) => m.role === "sent");
|
|
289
|
+
|
|
290
|
+
if (!draftsMailbox || !sentMailbox) {
|
|
291
|
+
throw new Error("Could not find Drafts or Sent mailbox");
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Create email body
|
|
295
|
+
const emailCreate: EmailCreate = {
|
|
296
|
+
mailboxIds: { [draftsMailbox.id]: true },
|
|
297
|
+
keywords: { $draft: true },
|
|
298
|
+
from: [{ name: identity.name, email: identity.email }],
|
|
299
|
+
to: params.to,
|
|
300
|
+
cc: params.cc,
|
|
301
|
+
bcc: params.bcc,
|
|
302
|
+
subject: params.subject,
|
|
303
|
+
bodyValues: {
|
|
304
|
+
body: {
|
|
305
|
+
value: params.textBody,
|
|
306
|
+
isEncodingProblem: false,
|
|
307
|
+
isTruncated: false,
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
textBody: [{ partId: "body", type: "text/plain" } as const].map((p) => ({
|
|
311
|
+
...p,
|
|
312
|
+
blobId: null,
|
|
313
|
+
size: 0,
|
|
314
|
+
name: null,
|
|
315
|
+
charset: null,
|
|
316
|
+
disposition: null,
|
|
317
|
+
cid: null,
|
|
318
|
+
language: null,
|
|
319
|
+
location: null,
|
|
320
|
+
})),
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
if (params.inReplyTo) {
|
|
324
|
+
emailCreate.inReplyTo = [params.inReplyTo];
|
|
325
|
+
}
|
|
326
|
+
if (params.references) {
|
|
327
|
+
emailCreate.references = params.references;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Create email and submit in one request
|
|
331
|
+
const responses = await client.request([
|
|
332
|
+
[
|
|
333
|
+
"Email/set",
|
|
334
|
+
{
|
|
335
|
+
accountId,
|
|
336
|
+
create: { draft: emailCreate },
|
|
337
|
+
},
|
|
338
|
+
"0",
|
|
339
|
+
],
|
|
340
|
+
[
|
|
341
|
+
"EmailSubmission/set",
|
|
342
|
+
{
|
|
343
|
+
accountId,
|
|
344
|
+
create: {
|
|
345
|
+
submission: {
|
|
346
|
+
identityId: identity.id,
|
|
347
|
+
emailId: "#draft",
|
|
348
|
+
envelope: null,
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
onSuccessUpdateEmail: {
|
|
352
|
+
"#submission": {
|
|
353
|
+
mailboxIds: { [sentMailbox.id]: true },
|
|
354
|
+
"keywords/$draft": null,
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
"1",
|
|
359
|
+
],
|
|
360
|
+
]);
|
|
361
|
+
|
|
362
|
+
// Extract created email ID
|
|
363
|
+
const emailSetResponse = responses[0];
|
|
364
|
+
if (!emailSetResponse) {
|
|
365
|
+
throw new Error("No response from Email/set");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const created = (
|
|
369
|
+
emailSetResponse[1] as { created?: Record<string, { id: string }> }
|
|
370
|
+
).created;
|
|
371
|
+
const emailId = created?.draft?.id;
|
|
372
|
+
|
|
373
|
+
if (!emailId) {
|
|
374
|
+
throw new Error("Failed to create email");
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return emailId;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ============ Attachment Methods ============
|
|
381
|
+
|
|
382
|
+
export interface AttachmentInfo {
|
|
383
|
+
blobId: string;
|
|
384
|
+
name: string | null;
|
|
385
|
+
type: string;
|
|
386
|
+
size: number;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export async function getAttachments(
|
|
390
|
+
emailId: string,
|
|
391
|
+
): Promise<AttachmentInfo[]> {
|
|
392
|
+
const email = await getEmail(emailId);
|
|
393
|
+
if (!email) {
|
|
394
|
+
throw new Error(`Email not found: ${emailId}`);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (!email.attachments || email.attachments.length === 0) {
|
|
398
|
+
return [];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return email.attachments
|
|
402
|
+
.filter((a) => a.blobId)
|
|
403
|
+
.map((a) => ({
|
|
404
|
+
blobId: a.blobId as string,
|
|
405
|
+
name: a.name,
|
|
406
|
+
type: a.type,
|
|
407
|
+
size: a.size,
|
|
408
|
+
}));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export async function downloadAttachment(
|
|
412
|
+
emailId: string,
|
|
413
|
+
blobId: string,
|
|
414
|
+
): Promise<{
|
|
415
|
+
content: string;
|
|
416
|
+
type: string;
|
|
417
|
+
name: string | null;
|
|
418
|
+
isText: boolean;
|
|
419
|
+
}> {
|
|
420
|
+
const client = getClient();
|
|
421
|
+
const accountId = await client.getAccountId();
|
|
422
|
+
|
|
423
|
+
// Get attachment info for the name
|
|
424
|
+
const attachments = await getAttachments(emailId);
|
|
425
|
+
const attachment = attachments.find((a) => a.blobId === blobId);
|
|
426
|
+
if (!attachment) {
|
|
427
|
+
throw new Error(`Attachment not found: ${blobId}`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const { data, type } = await client.downloadBlob(blobId, accountId);
|
|
431
|
+
|
|
432
|
+
// Determine if it's text-based content
|
|
433
|
+
const isText =
|
|
434
|
+
type.startsWith("text/") ||
|
|
435
|
+
type.includes("json") ||
|
|
436
|
+
type.includes("xml") ||
|
|
437
|
+
type.includes("javascript") ||
|
|
438
|
+
type.includes("csv") ||
|
|
439
|
+
type === "application/pdf"; // We'll try to extract text from PDFs
|
|
440
|
+
|
|
441
|
+
if (isText && type !== "application/pdf") {
|
|
442
|
+
// Return as text
|
|
443
|
+
const decoder = new TextDecoder();
|
|
444
|
+
return {
|
|
445
|
+
content: decoder.decode(data),
|
|
446
|
+
type,
|
|
447
|
+
name: attachment.name,
|
|
448
|
+
isText: true,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// For binary files (including PDFs for now), return base64
|
|
453
|
+
const base64 = Buffer.from(data).toString("base64");
|
|
454
|
+
return {
|
|
455
|
+
content: base64,
|
|
456
|
+
type,
|
|
457
|
+
name: attachment.name,
|
|
458
|
+
isText: false,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Helper to build a reply
|
|
463
|
+
export async function buildReply(
|
|
464
|
+
originalEmailId: string,
|
|
465
|
+
replyBody: string,
|
|
466
|
+
): Promise<SendEmailParams> {
|
|
467
|
+
const original = await getEmail(originalEmailId);
|
|
468
|
+
if (!original) {
|
|
469
|
+
throw new Error(`Original email not found: ${originalEmailId}`);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Determine who to reply to
|
|
473
|
+
const replyTo = original.replyTo?.[0] || original.from?.[0];
|
|
474
|
+
if (!replyTo) {
|
|
475
|
+
throw new Error("Cannot determine reply address");
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Build subject (add Re: if not present)
|
|
479
|
+
let subject = original.subject || "";
|
|
480
|
+
if (!subject.toLowerCase().startsWith("re:")) {
|
|
481
|
+
subject = `Re: ${subject}`;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Build references chain
|
|
485
|
+
const references: string[] = [];
|
|
486
|
+
if (original.references) {
|
|
487
|
+
references.push(...original.references);
|
|
488
|
+
}
|
|
489
|
+
if (original.messageId?.[0]) {
|
|
490
|
+
references.push(original.messageId[0]);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
to: [replyTo],
|
|
495
|
+
subject,
|
|
496
|
+
textBody: replyBody,
|
|
497
|
+
inReplyTo: original.messageId?.[0],
|
|
498
|
+
references: references.length > 0 ? references : undefined,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// JMAP Core Types
|
|
2
|
+
// Based on RFC 8620 (JMAP Core) and RFC 8621 (JMAP Mail)
|
|
3
|
+
|
|
4
|
+
export interface JMAPSession {
|
|
5
|
+
capabilities: Record<string, unknown>;
|
|
6
|
+
accounts: Record<string, JMAPAccount>;
|
|
7
|
+
primaryAccounts: Record<string, string>;
|
|
8
|
+
username: string;
|
|
9
|
+
apiUrl: string;
|
|
10
|
+
downloadUrl: string;
|
|
11
|
+
uploadUrl: string;
|
|
12
|
+
eventSourceUrl: string;
|
|
13
|
+
state: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface JMAPAccount {
|
|
17
|
+
name: string;
|
|
18
|
+
isPersonal: boolean;
|
|
19
|
+
isReadOnly: boolean;
|
|
20
|
+
accountCapabilities: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface JMAPRequest {
|
|
24
|
+
using: string[];
|
|
25
|
+
methodCalls: JMAPMethodCall[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type JMAPMethodCall = [string, Record<string, unknown>, string];
|
|
29
|
+
|
|
30
|
+
export interface JMAPResponse {
|
|
31
|
+
methodResponses: JMAPMethodResponse[];
|
|
32
|
+
sessionState: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type JMAPMethodResponse = [string, Record<string, unknown>, string];
|
|
36
|
+
|
|
37
|
+
// Mailbox Types
|
|
38
|
+
export interface Mailbox {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
parentId: string | null;
|
|
42
|
+
role: MailboxRole | null;
|
|
43
|
+
sortOrder: number;
|
|
44
|
+
totalEmails: number;
|
|
45
|
+
unreadEmails: number;
|
|
46
|
+
totalThreads: number;
|
|
47
|
+
unreadThreads: number;
|
|
48
|
+
myRights: MailboxRights;
|
|
49
|
+
isSubscribed: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type MailboxRole =
|
|
53
|
+
| "all"
|
|
54
|
+
| "archive"
|
|
55
|
+
| "drafts"
|
|
56
|
+
| "flagged"
|
|
57
|
+
| "important"
|
|
58
|
+
| "inbox"
|
|
59
|
+
| "junk"
|
|
60
|
+
| "sent"
|
|
61
|
+
| "subscribed"
|
|
62
|
+
| "trash";
|
|
63
|
+
|
|
64
|
+
export interface MailboxRights {
|
|
65
|
+
mayReadItems: boolean;
|
|
66
|
+
mayAddItems: boolean;
|
|
67
|
+
mayRemoveItems: boolean;
|
|
68
|
+
maySetSeen: boolean;
|
|
69
|
+
maySetKeywords: boolean;
|
|
70
|
+
mayCreateChild: boolean;
|
|
71
|
+
mayRename: boolean;
|
|
72
|
+
mayDelete: boolean;
|
|
73
|
+
maySubmit: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Email Types
|
|
77
|
+
export interface Email {
|
|
78
|
+
id: string;
|
|
79
|
+
blobId: string;
|
|
80
|
+
threadId: string;
|
|
81
|
+
mailboxIds: Record<string, boolean>;
|
|
82
|
+
keywords: Record<string, boolean>;
|
|
83
|
+
size: number;
|
|
84
|
+
receivedAt: string;
|
|
85
|
+
messageId: string[] | null;
|
|
86
|
+
inReplyTo: string[] | null;
|
|
87
|
+
references: string[] | null;
|
|
88
|
+
sender: EmailAddress[] | null;
|
|
89
|
+
from: EmailAddress[] | null;
|
|
90
|
+
to: EmailAddress[] | null;
|
|
91
|
+
cc: EmailAddress[] | null;
|
|
92
|
+
bcc: EmailAddress[] | null;
|
|
93
|
+
replyTo: EmailAddress[] | null;
|
|
94
|
+
subject: string | null;
|
|
95
|
+
sentAt: string | null;
|
|
96
|
+
hasAttachment: boolean;
|
|
97
|
+
preview: string;
|
|
98
|
+
bodyStructure?: EmailBodyPart;
|
|
99
|
+
bodyValues?: Record<string, EmailBodyValue>;
|
|
100
|
+
textBody?: EmailBodyPart[];
|
|
101
|
+
htmlBody?: EmailBodyPart[];
|
|
102
|
+
attachments?: EmailBodyPart[];
|
|
103
|
+
headers?: EmailHeader[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface EmailAddress {
|
|
107
|
+
name: string | null;
|
|
108
|
+
email: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface EmailBodyPart {
|
|
112
|
+
partId: string | null;
|
|
113
|
+
blobId: string | null;
|
|
114
|
+
size: number;
|
|
115
|
+
headers?: EmailHeader[];
|
|
116
|
+
name: string | null;
|
|
117
|
+
type: string;
|
|
118
|
+
charset: string | null;
|
|
119
|
+
disposition: string | null;
|
|
120
|
+
cid: string | null;
|
|
121
|
+
language: string[] | null;
|
|
122
|
+
location: string | null;
|
|
123
|
+
subParts?: EmailBodyPart[];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface EmailBodyValue {
|
|
127
|
+
value: string;
|
|
128
|
+
isEncodingProblem: boolean;
|
|
129
|
+
isTruncated: boolean;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface EmailHeader {
|
|
133
|
+
name: string;
|
|
134
|
+
value: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Identity Types (for sending)
|
|
138
|
+
export interface Identity {
|
|
139
|
+
id: string;
|
|
140
|
+
name: string;
|
|
141
|
+
email: string;
|
|
142
|
+
replyTo: EmailAddress[] | null;
|
|
143
|
+
bcc: EmailAddress[] | null;
|
|
144
|
+
textSignature: string;
|
|
145
|
+
htmlSignature: string;
|
|
146
|
+
mayDelete: boolean;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// EmailSubmission Types
|
|
150
|
+
export interface EmailSubmission {
|
|
151
|
+
id: string;
|
|
152
|
+
identityId: string;
|
|
153
|
+
emailId: string;
|
|
154
|
+
threadId: string;
|
|
155
|
+
envelope: Envelope | null;
|
|
156
|
+
sendAt: string;
|
|
157
|
+
undoStatus: "pending" | "final" | "canceled";
|
|
158
|
+
deliveryStatus: Record<string, DeliveryStatus> | null;
|
|
159
|
+
dsnBlobIds: string[];
|
|
160
|
+
mdnBlobIds: string[];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface Envelope {
|
|
164
|
+
mailFrom: EnvelopeAddress;
|
|
165
|
+
rcptTo: EnvelopeAddress[];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface EnvelopeAddress {
|
|
169
|
+
email: string;
|
|
170
|
+
parameters: Record<string, string | null> | null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface DeliveryStatus {
|
|
174
|
+
smtpReply: string;
|
|
175
|
+
delivered: "queued" | "yes" | "no" | "unknown";
|
|
176
|
+
displayed: "unknown" | "yes";
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Helper type for creating emails
|
|
180
|
+
export interface EmailCreate {
|
|
181
|
+
mailboxIds: Record<string, boolean>;
|
|
182
|
+
keywords?: Record<string, boolean>;
|
|
183
|
+
from?: EmailAddress[];
|
|
184
|
+
to?: EmailAddress[];
|
|
185
|
+
cc?: EmailAddress[];
|
|
186
|
+
bcc?: EmailAddress[];
|
|
187
|
+
replyTo?: EmailAddress[];
|
|
188
|
+
subject?: string;
|
|
189
|
+
sentAt?: string;
|
|
190
|
+
bodyStructure?: EmailBodyPart;
|
|
191
|
+
bodyValues?: Record<string, EmailBodyValue>;
|
|
192
|
+
textBody?: EmailBodyPart[];
|
|
193
|
+
htmlBody?: EmailBodyPart[];
|
|
194
|
+
inReplyTo?: string[];
|
|
195
|
+
references?: string[];
|
|
196
|
+
messageId?: string[];
|
|
197
|
+
headers?: EmailHeader[];
|
|
198
|
+
}
|