1 | |
2 |
|
3 | import * as wmill from "windmill-client" |
4 |
|
5 | |
6 | * New Email |
7 | * Emits messages received in a folder (default `inbox`) since the last poll, oldest first. The first run sets the watermark to now and emits nothing. Polls because Microsoft Graph push needs a change-notification subscription renewed every few days against a public endpoint, which belongs in a Windmill native trigger; run this as a Scheduled Poll until then. Requires Mail.Read (covered by the default Mail.ReadWrite) (a shared mailbox needs Mail.Read.Shared, not a default). |
8 | */ |
9 | export async function main( |
10 | auth: RT.MsOutlook, |
11 | folder: string = "inbox", |
12 | mailbox: string | undefined |
13 | ) { |
14 | |
15 | |
16 | const state: { since: string; seen: string[] } | undefined = |
17 | await wmill.getState() |
18 |
|
19 | |
20 | if (!state?.since) { |
21 | await wmill.setState({ |
22 | since: new Date().toISOString().replace(/\.\d+Z$/, "Z"), |
23 | seen: [], |
24 | }) |
25 | return [] |
26 | } |
27 |
|
28 | const base = `https://graph.microsoft.com/v1.0/${ |
29 | mailbox != null && mailbox !== "" |
30 | ? `users/${encodeURIComponent(mailbox)}` |
31 | : "me" |
32 | }` |
33 | const first = new URL( |
34 | `${base}/mailFolders/${encodeURIComponent(folder)}/messages` |
35 | ) |
36 | first.searchParams.append("$filter", `receivedDateTime ge ${state.since}`) |
37 | first.searchParams.append("$orderby", "receivedDateTime asc") |
38 | first.searchParams.append("$top", "100") |
39 |
|
40 | const messages: { id: string; receivedDateTime: string }[] = [] |
41 | let next: string | undefined = first.toString() |
42 | while (next) { |
43 | const response = await fetch(next, { |
44 | headers: { |
45 | Authorization: `Bearer ${auth.token}`, |
46 | Accept: "application/json", |
47 | }, |
48 | }) |
49 | if (!response.ok) { |
50 | throw new Error(`${response.status} ${await response.text()}`) |
51 | } |
52 | const page = (await response.json()) as { |
53 | value: { id: string; receivedDateTime: string }[] |
54 | "@odata.nextLink"?: string |
55 | } |
56 | messages.push(...page.value) |
57 | next = page["@odata.nextLink"] |
58 | } |
59 |
|
60 | const fresh = messages.filter((m) => !state.seen.includes(m.id)) |
61 | if (fresh.length > 0) { |
62 | const since = messages[messages.length - 1].receivedDateTime |
63 | await wmill.setState({ |
64 | since, |
65 | seen: messages |
66 | .filter((m) => m.receivedDateTime === since) |
67 | .map((m) => m.id), |
68 | }) |
69 | } |
70 |
|
71 | return fresh |
72 | } |
73 |
|