@workwae/sdk 0.0.0-stage → 0.1.0-alpha.1

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.
Files changed (56) hide show
  1. package/README.md +233 -2
  2. package/client.d.ts +36 -0
  3. package/contract/accounts/models.d.ts +726 -0
  4. package/contract/accounts/routes.d.ts +1318 -0
  5. package/contract/ai/models.d.ts +30 -0
  6. package/contract/ai/routes.d.ts +89 -0
  7. package/contract/audit-log/models.d.ts +107 -0
  8. package/contract/audit-log/routes.d.ts +72 -0
  9. package/contract/calendar/models.d.ts +837 -0
  10. package/contract/calendar/routes.d.ts +1299 -0
  11. package/contract/connect/models.d.ts +272 -0
  12. package/contract/connect/routes.d.ts +531 -0
  13. package/contract/contacts/models.d.ts +269 -0
  14. package/contract/contacts/routes.d.ts +634 -0
  15. package/contract/errors.d.ts +108 -0
  16. package/contract/events/models.d.ts +2364 -0
  17. package/contract/events/routes.d.ts +107 -0
  18. package/contract/exports/models.d.ts +67 -0
  19. package/contract/exports/routes.d.ts +196 -0
  20. package/contract/identities/models.d.ts +100 -0
  21. package/contract/identities/routes.d.ts +249 -0
  22. package/contract/limits.d.ts +170 -0
  23. package/contract/mail/models.d.ts +528 -0
  24. package/contract/mail/routes.d.ts +749 -0
  25. package/contract/meetings/models.d.ts +232 -0
  26. package/contract/meetings/routes.d.ts +386 -0
  27. package/contract/oauth-apps/models.d.ts +92 -0
  28. package/contract/oauth-apps/routes.d.ts +187 -0
  29. package/contract/org-connections/models.d.ts +383 -0
  30. package/contract/org-connections/routes.d.ts +830 -0
  31. package/contract/primitives.d.ts +140 -0
  32. package/contract/public.d.ts +26 -0
  33. package/contract/reasons.d.ts +711 -0
  34. package/contract/router.d.ts +9773 -0
  35. package/contract/send/models.d.ts +750 -0
  36. package/contract/send/routes.d.ts +1239 -0
  37. package/contract/settings/models.d.ts +103 -0
  38. package/contract/settings/routes.d.ts +110 -0
  39. package/contract/suppressions/models.d.ts +231 -0
  40. package/contract/suppressions/routes.d.ts +317 -0
  41. package/contract/templates/models.d.ts +169 -0
  42. package/contract/templates/routes.d.ts +281 -0
  43. package/contract/threads/models.d.ts +117 -0
  44. package/contract/threads/routes.d.ts +336 -0
  45. package/contract/usage/models.d.ts +213 -0
  46. package/contract/usage/routes.d.ts +81 -0
  47. package/contract/webhooks/models.d.ts +879 -0
  48. package/contract/webhooks/routes.d.ts +839 -0
  49. package/contract/webhooks/signing.d.ts +35 -0
  50. package/errors.d.ts +15 -0
  51. package/index.d.ts +6 -0
  52. package/index.js +4334 -0
  53. package/index.js.map +59 -0
  54. package/package.json +45 -4
  55. package/pagination.d.ts +40 -0
  56. package/webhooks.d.ts +28 -0
package/index.js ADDED
@@ -0,0 +1,4334 @@
1
+ // ../contract/src/accounts/models.ts
2
+ import * as z3 from "zod";
3
+
4
+ // ../contract/src/limits.ts
5
+ var MAX_EMAIL_LENGTH = 320;
6
+ var MAX_NAME_LENGTH = 100;
7
+ var MAX_EXTERNAL_REF_LENGTH = 255;
8
+ var MAX_DISPLAY_NAME_LENGTH = 255;
9
+ var MAX_ADDRESS_NAME_LENGTH = 256;
10
+ var MAX_SECRET_LENGTH = 1024;
11
+ var MAX_NOTE_LENGTH = 500;
12
+ var MAX_FILTER_QUERY_LENGTH = 200;
13
+ var MAX_URL_LENGTH = 2048;
14
+ var MAX_TIME_ZONE_LENGTH = 64;
15
+ var MAX_CURSOR_LENGTH = 512;
16
+ var MAX_PAGE_SIZE = 200;
17
+ var DEFAULT_PAGE_SIZE = 50;
18
+ var MAX_PROVIDER_ID_LENGTH = 1024;
19
+ var MAX_HOST_LENGTH = 253;
20
+ var MAX_PORT = 65535;
21
+ var MAX_STATE_LENGTH = 1024;
22
+ var MIN_TOKEN_TTL_SECONDS = 60;
23
+ var MAX_TOKEN_TTL_SECONDS = 86400;
24
+ var MAX_REDIRECT_URIS = 50;
25
+ var MAX_UNDO_SEND_SECONDS = 60;
26
+ var MIN_EVENT_RETENTION_DAYS = 7;
27
+ var MAX_EVENT_RETENTION_DAYS = 30;
28
+ var MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
29
+ var MAX_SUBJECT_LENGTH = 998;
30
+ var MAX_MESSAGE_ID_LENGTH = 998;
31
+ var MAX_BODY_LENGTH = 2 * 1024 * 1024;
32
+ var MAX_RECIPIENTS = 500;
33
+ var MAX_REPLY_TO = 10;
34
+ var MAX_ATTACHMENTS = 50;
35
+ var MAX_FILENAME_LENGTH = 255;
36
+ var MAX_CONTENT_TYPE_LENGTH = 255;
37
+ var MAX_CONTENT_ID_LENGTH = 255;
38
+ var MAX_SEND_REFERENCE_LENGTH = 255;
39
+ var MAX_MAILBOX_CHANGES = 50;
40
+ var MAX_SEARCH_QUERY_LENGTH = 500;
41
+ var MAX_SEARCH_PAGE_SIZE = 100;
42
+ var DEFAULT_SEARCH_PAGE_SIZE = 25;
43
+ var MAX_THREAD_PAGE_SIZE = 100;
44
+ var DEFAULT_THREAD_PAGE_SIZE = 25;
45
+ var MAX_SIGNATURE_HTML_LENGTH = 1e5;
46
+ var MAX_SIGNATURE_TEXT_LENGTH = 20000;
47
+ var MAX_TEMPLATE_NAME_LENGTH = 255;
48
+ var MAX_TEMPLATE_DESCRIPTION_LENGTH = 1000;
49
+ var MAX_TEMPLATE_SUBJECT_LENGTH = 2000;
50
+ var MAX_TEMPLATE_PREVIEW_TEXT_LENGTH = 2000;
51
+ var MAX_TEMPLATE_BODY_LENGTH = 1e6;
52
+ var MAX_MAIL_MERGE_ENTRIES = 100;
53
+ var MAX_MAIL_MERGE_ATTACHMENT_BYTES = 50 * 1024 * 1024;
54
+ var MAX_RENDER_PARAMS_BYTES = 256 * 1024;
55
+ var MAX_RENDER_ITERATIONS = 1e4;
56
+ var MAX_MARKDOWN_LENGTH = 1e5;
57
+ var MAX_SUPPRESSION_BATCH = 1000;
58
+ var MAX_SUPPRESSION_LIST_DESCRIPTION_LENGTH = 500;
59
+ var MAX_SUPPRESSION_REASON_LENGTH = 200;
60
+ var MAX_EVENT_ATTENDEES = 500;
61
+ var MAX_EVENT_SUBJECT_LENGTH = 255;
62
+ var MAX_EVENT_LOCATION_LENGTH = 255;
63
+ var MAX_EVENT_BODY_LENGTH = 1e6;
64
+ var MAX_EVENT_COMMENT_LENGTH = 2000;
65
+ var MAX_RECURRENCE_LINES = 20;
66
+ var MAX_RECURRENCE_LINE_LENGTH = 1000;
67
+ var MAX_REMINDER_MINUTES = 40320;
68
+ var MAX_AVAILABILITY_ATTENDEES = 20;
69
+ var MAX_AVAILABILITY_DAYS = 62;
70
+ var MIN_SLOT_MINUTES = 5;
71
+ var MAX_SLOT_MINUTES = 1440;
72
+ var MAX_SUGGESTIONS = 100;
73
+ var MAX_EVENT_RANGE_DAYS = 400;
74
+ var MAX_CALENDAR_EVENT_PAGE_SIZE = 500;
75
+ var DEFAULT_CALENDAR_EVENT_PAGE_SIZE = 100;
76
+ var MAX_CONTACT_ENTRIES = 20;
77
+ var MAX_CONTACT_FIELD_LENGTH = 256;
78
+ var MAX_CONTACT_PHONE_LENGTH = 64;
79
+ var MAX_CONTACT_NOTES_LENGTH = 1e4;
80
+ var MAX_CONTACT_PAGE_SIZE = 500;
81
+ var DEFAULT_CONTACT_PAGE_SIZE = 100;
82
+ var MAX_WEBHOOK_FILTER_VALUES = 100;
83
+ var MAX_SUBJECT_FILTER_LENGTH = 200;
84
+ var MAX_REPLAY_EVENTS = 1000;
85
+ var MAX_WEBHOOK_ATTEMPT_PAGE_SIZE = 500;
86
+ var DEFAULT_WEBHOOK_ATTEMPT_PAGE_SIZE = 50;
87
+ var MAX_SIGNATURE_CANDIDATES = 8;
88
+ var DEFAULT_WEBHOOK_SECRET_GRACE_SECONDS = 24 * 60 * 60;
89
+ var MAX_WEBHOOK_SECRET_GRACE_SECONDS = 7 * 24 * 60 * 60;
90
+ var MAX_DRAFT_INSTRUCTIONS_LENGTH = 2000;
91
+ var MAX_DRAFT_CONTEXT_LENGTH = 20000;
92
+ var MAX_MEETING_APP_ID_LENGTH = 200;
93
+ var MAX_REFRESH_TOKEN_LENGTH = 4096;
94
+ var MAX_KEY_FILE_LENGTH = 20000;
95
+ var MAX_DIRECTORY_QUERY_LENGTH = 100;
96
+ var MAX_DIRECTORY_PAGE_SIZE = 200;
97
+ var DEFAULT_DIRECTORY_PAGE_SIZE = 100;
98
+ var MAX_ADD_MAILBOXES = 100;
99
+ var MAX_ENTRA_ID_LENGTH = 100;
100
+ var MAX_STRIPE_ID_LENGTH = 255;
101
+ var MAX_USAGE_RANGE_DAYS = 366;
102
+
103
+ // ../contract/src/primitives.ts
104
+ import * as z from "zod";
105
+ var openApiComponents = new Map;
106
+ var componentNames = new WeakMap;
107
+ function named(name, schema) {
108
+ if (openApiComponents.has(name))
109
+ throw new Error(`OpenAPI component ${name} registered twice`);
110
+ openApiComponents.set(name, schema);
111
+ componentNames.set(schema, name);
112
+ return schema;
113
+ }
114
+ function componentName(schema, wrapper) {
115
+ const name = componentNames.get(schema);
116
+ if (name === undefined)
117
+ throw new Error(`${wrapper}() of a schema that is not named()`);
118
+ return name;
119
+ }
120
+ var Id = z.uuid();
121
+ var ProviderId = z.string().min(1).max(MAX_PROVIDER_ID_LENGTH);
122
+ var EventId = named("EventId", z.string().regex(/^[0-9]{1,19}$/, "Expected an event id"));
123
+ var Cursor = z.string().min(1).max(MAX_CURSOR_LENGTH);
124
+ var Email = z.email().max(MAX_EMAIL_LENGTH);
125
+ var Timestamp = z.preprocess((value) => typeof value === "string" ? new Date(value) : value, z.date());
126
+ var QueryTimestamp = z.preprocess((value) => typeof value === "string" ? new Date(value) : value, z.date());
127
+ var DateOnly = z.iso.date();
128
+ function trimmed(max) {
129
+ return z.string().trim().min(1).max(max);
130
+ }
131
+ var Name = z.string().min(1).max(MAX_NAME_LENGTH);
132
+ var ExternalRef = z.string().max(MAX_EXTERNAL_REF_LENGTH);
133
+ var Address = named("Address", z.object({ name: z.string().nullable(), address: z.string() }));
134
+ var AddressInput = named("AddressInput", z.object({ name: z.string().max(MAX_ADDRESS_NAME_LENGTH).optional(), address: Email }));
135
+ var QueryBoolean = z.union([z.boolean(), z.enum(["true", "false"])]).transform((v) => v === true || v === "true");
136
+ var pageFields = (item) => ({
137
+ items: z.array(item),
138
+ nextCursor: Cursor.nullable()
139
+ });
140
+ var Page = (item) => named(`${componentName(item, "Page")}Page`, z.object(pageFields(item)));
141
+ async function* followCursor(fetch2, cursor, next) {
142
+ let at = cursor;
143
+ for (;; ) {
144
+ const page = await fetch2(at);
145
+ yield page;
146
+ at = next(page);
147
+ if (at === undefined)
148
+ return;
149
+ }
150
+ }
151
+ var listFields = (item) => ({ items: z.array(item) });
152
+ var List = (item) => named(`${componentName(item, "List")}List`, z.object(listFields(item)));
153
+ var DAY_MS = 86400000;
154
+ function instant(value) {
155
+ if (value instanceof Date)
156
+ return value.getTime();
157
+ if (typeof value !== "string")
158
+ return null;
159
+ const ms = Date.parse(value);
160
+ return Number.isNaN(ms) ? null : ms;
161
+ }
162
+ function withDateRange(schema, range) {
163
+ return schema.superRefine((value, ctx) => {
164
+ const from = instant(value[range.from]);
165
+ const to = instant(value[range.to]);
166
+ if (from === null || to === null)
167
+ return;
168
+ const span = to - from;
169
+ const params = { reason: "invalid_range" };
170
+ if (span < 0 || span === 0 && !range.inclusive)
171
+ ctx.addIssue({
172
+ code: "custom",
173
+ path: [range.to],
174
+ message: `${range.to} must be ${range.inclusive ? "on or after" : "after"} ${range.from}`,
175
+ params
176
+ });
177
+ else if (range.maxSpanMs !== undefined && span > range.maxSpanMs)
178
+ ctx.addIssue({
179
+ code: "custom",
180
+ path: [range.to],
181
+ message: range.maxSpanMs % DAY_MS === 0 ? `The range may span at most ${range.maxSpanMs / DAY_MS} days` : `The range may span at most ${range.maxSpanMs} ms`,
182
+ params
183
+ });
184
+ });
185
+ }
186
+ function pageLimit(max, fallback) {
187
+ return z.coerce.number().int().min(1).max(max).default(fallback);
188
+ }
189
+ var Limit = pageLimit(MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE);
190
+ var Paged = { limit: Limit, cursor: Cursor.optional() };
191
+ var AccountIdInput = named("AccountIdInput", z.object({ accountId: Id }));
192
+ var StreamedFile = z.custom((value) => value instanceof ReadableStream || value instanceof Blob).transform((stream) => stream);
193
+ var publicOperation = (operation) => ({
194
+ ...operation,
195
+ security: []
196
+ });
197
+ function fileResponse(file) {
198
+ return (operation) => {
199
+ const ok = operation.responses?.["200"];
200
+ if (!ok || "$ref" in ok)
201
+ throw new Error(`OpenAPI: no 200 response for ${operation.operationId}`);
202
+ return {
203
+ ...operation,
204
+ responses: {
205
+ ...operation.responses,
206
+ 200: {
207
+ ...ok,
208
+ description: file.description,
209
+ content: {
210
+ [file.mediaType]: { schema: { type: "string", contentMediaType: file.mediaType } }
211
+ },
212
+ headers: {
213
+ "content-disposition": {
214
+ description: `Always \`${file.disposition}; filename="..."\``,
215
+ schema: { type: "string" }
216
+ },
217
+ ...file.headers
218
+ }
219
+ }
220
+ }
221
+ };
222
+ };
223
+ }
224
+
225
+ // ../contract/src/settings/models.ts
226
+ import * as z2 from "zod";
227
+ var BodyStorage = named("BodyStorage", z2.enum(["none", "cache", "full"]));
228
+ var EffectiveSettings = named("EffectiveSettings", z2.object({
229
+ bodyStorage: BodyStorage,
230
+ syncWindowDays: z2.number().int().positive().nullable(),
231
+ calendarEnabled: z2.boolean(),
232
+ contactsEnabled: z2.boolean(),
233
+ undoSendSeconds: z2.number().int().min(0).max(MAX_UNDO_SEND_SECONDS),
234
+ trackOpens: z2.boolean(),
235
+ trackClicks: z2.boolean(),
236
+ trackingHoneypot: z2.boolean()
237
+ }));
238
+ var SettingsOverride = named("SettingsOverride", EffectiveSettings.partial());
239
+ var LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1"]);
240
+ var OptOutHandling = named("OptOutHandling", z2.enum(["off", "review", "suppress"]));
241
+ var TenantSettings = named("TenantSettings", EffectiveSettings.extend({
242
+ optOutHandling: OptOutHandling,
243
+ eventRetentionDays: z2.number().int().min(MIN_EVENT_RETENTION_DAYS).max(MAX_EVENT_RETENTION_DAYS),
244
+ redirectUris: z2.array(z2.url()).max(MAX_REDIRECT_URIS),
245
+ aiReplyClassification: z2.boolean()
246
+ }));
247
+ var UpdateTenantSettings = named("UpdateTenantSettings", TenantSettings.partial());
248
+
249
+ // ../contract/src/accounts/models.ts
250
+ var OAuthProvider = named("OAuthProvider", z3.enum(["google", "microsoft"]));
251
+ var MicrosoftCloud = named("MicrosoftCloud", z3.enum(["global", "gcc-high", "dod", "china"]));
252
+ var AccountProvider = named("AccountProvider", z3.enum(["gmail", "graph", "imap"]));
253
+ var AccountStatus = named("AccountStatus", z3.enum(["active", "paused", "reauth_required", "disabled"]));
254
+ var SettableAccountStatus = named("SettableAccountStatus", AccountStatus.extract(["active", "paused"]));
255
+ var RuntimeState = named("RuntimeState", z3.enum(["pending", "connecting", "connected", "error", "stopped"]));
256
+ var AccountRuntime = named("AccountRuntime", z3.object({
257
+ state: RuntimeState,
258
+ detail: z3.string().nullable(),
259
+ ownerInstanceId: Id.nullable(),
260
+ updatedAt: Timestamp
261
+ }));
262
+ var MailSecurity = named("MailSecurity", z3.enum(["tls", "starttls", "none"]));
263
+ var MailServer = named("MailServer", z3.object({
264
+ host: z3.string(),
265
+ port: z3.number().int(),
266
+ security: MailSecurity,
267
+ user: z3.string()
268
+ }));
269
+ var DavAuth = named("DavAuth", z3.enum(["imap_password", "dav_password"]));
270
+ var DavSettings = named("DavSettings", z3.object({
271
+ calendarUrl: z3.string().nullable(),
272
+ contactsUrl: z3.string().nullable(),
273
+ user: z3.string(),
274
+ auth: DavAuth
275
+ }));
276
+ var ImapAuth = named("ImapAuth", z3.enum(["password", "google", "microsoft"]));
277
+ var ImapSettings = named("ImapSettings", z3.object({
278
+ imap: MailServer,
279
+ smtp: MailServer.nullable(),
280
+ auth: ImapAuth,
281
+ appendSent: z3.boolean(),
282
+ dav: DavSettings.nullable()
283
+ }));
284
+ var AccountAuthMethod = named("AccountAuthMethod", z3.enum(["oauth", "password", "google_delegation", "microsoft_app", "shared_mailbox"]));
285
+ var AccountAuth = named("AccountAuth", z3.object({
286
+ method: AccountAuthMethod,
287
+ orgConnectionId: Id.nullable(),
288
+ delegateAccountId: Id.nullable()
289
+ }));
290
+ var Account = named("Account", z3.object({
291
+ id: Id,
292
+ externalRef: z3.string().nullable(),
293
+ provider: AccountProvider,
294
+ email: z3.string(),
295
+ displayName: z3.string().nullable(),
296
+ oauthAppId: Id.nullable(),
297
+ cloud: MicrosoftCloud.nullable(),
298
+ status: AccountStatus,
299
+ statusReason: z3.string().nullable(),
300
+ settings: EffectiveSettings,
301
+ settingsOverride: SettingsOverride,
302
+ runtime: AccountRuntime.nullable(),
303
+ imap: ImapSettings.nullable(),
304
+ auth: AccountAuth,
305
+ initializedAt: Timestamp.nullable(),
306
+ calendarInitializedAt: Timestamp.nullable(),
307
+ contactsInitializedAt: Timestamp.nullable(),
308
+ createdAt: Timestamp,
309
+ updatedAt: Timestamp
310
+ }));
311
+ var AccountPage = Page(Account);
312
+ var ListAccountsQuery = named("ListAccountsQuery", z3.object({
313
+ ...Paged,
314
+ externalRef: z3.string().optional(),
315
+ q: trimmed(MAX_FILTER_QUERY_LENGTH).optional(),
316
+ status: AccountStatus.optional(),
317
+ orgConnectionId: Id.optional(),
318
+ delegateAccountId: Id.optional()
319
+ }));
320
+ var CreateAccount = named("CreateAccount", z3.object({
321
+ provider: OAuthProvider,
322
+ microsoftCloud: MicrosoftCloud.default("global"),
323
+ refreshToken: z3.string().min(1),
324
+ oauthAppId: Id.optional(),
325
+ externalRef: ExternalRef.optional(),
326
+ settings: SettingsOverride.optional(),
327
+ sharedMailboxesEnabled: z3.boolean().optional()
328
+ }));
329
+ var UpdateAccount = named("UpdateAccount", z3.object({
330
+ externalRef: ExternalRef.nullable().optional(),
331
+ status: SettableAccountStatus.optional(),
332
+ settings: SettingsOverride.optional()
333
+ }));
334
+ var DeleteAccountQuery = named("DeleteAccountQuery", z3.object({ purge: QueryBoolean.default(false) }));
335
+ var DeleteAccountInput = named("DeleteAccountInput", z3.object({ params: AccountIdInput, query: DeleteAccountQuery }));
336
+ var MintAccountTokenRequest = named("MintAccountTokenRequest", z3.object({
337
+ ttlSeconds: z3.number().int().min(MIN_TOKEN_TTL_SECONDS).max(MAX_TOKEN_TTL_SECONDS).default(900)
338
+ }));
339
+ var AccountToken = named("AccountToken", z3.object({ token: z3.string(), expiresAt: Timestamp }));
340
+ var MailServerInput = named("MailServerInput", z3.object({
341
+ host: z3.string().min(1).max(MAX_HOST_LENGTH),
342
+ port: z3.number().int().min(1).max(MAX_PORT),
343
+ security: MailSecurity,
344
+ user: z3.string().min(1).max(MAX_EMAIL_LENGTH).optional()
345
+ }));
346
+ var Password = z3.string().min(1).max(MAX_SECRET_LENGTH);
347
+ var DavUrl = z3.url({ protocol: /^https?$/ }).max(MAX_URL_LENGTH);
348
+ var DavInput = named("DavInput", z3.object({
349
+ calendarUrl: DavUrl.nullable().optional(),
350
+ contactsUrl: DavUrl.nullable().optional(),
351
+ user: z3.string().min(1).max(MAX_EMAIL_LENGTH).optional(),
352
+ password: Password.nullable().optional()
353
+ }));
354
+ var ImapAccountInput = named("ImapAccountInput", z3.object({
355
+ email: Email,
356
+ displayName: z3.string().max(MAX_DISPLAY_NAME_LENGTH).optional(),
357
+ password: Password,
358
+ smtpPassword: Password.optional(),
359
+ imap: MailServerInput.optional(),
360
+ smtp: MailServerInput.nullable().optional(),
361
+ appendSent: z3.boolean().optional(),
362
+ dav: DavInput.optional()
363
+ }));
364
+ var CreateImapAccount = named("CreateImapAccount", ImapAccountInput.extend({
365
+ externalRef: ExternalRef.optional(),
366
+ settings: SettingsOverride.optional()
367
+ }));
368
+ var UpdateImapSettings = named("UpdateImapSettings", z3.object({
369
+ password: Password.optional(),
370
+ smtpPassword: Password.nullable().optional(),
371
+ imap: MailServerInput.optional(),
372
+ smtp: MailServerInput.nullable().optional(),
373
+ appendSent: z3.boolean().optional(),
374
+ dav: DavInput.nullable().optional()
375
+ }));
376
+ var DavDiscoverySource = named("DavDiscoverySource", z3.enum(["provider", "well-known", "srv"]));
377
+ var DiscoveredDav = named("DiscoveredDav", z3.object({
378
+ source: DavDiscoverySource,
379
+ calendarUrl: z3.string().nullable(),
380
+ contactsUrl: z3.string().nullable()
381
+ }));
382
+ var AutoconfigSource = named("AutoconfigSource", z3.enum(["provider", "autoconfig", "ispdb", "srv", "mx"]));
383
+ var DiscoveredSettings = named("DiscoveredSettings", z3.object({
384
+ source: AutoconfigSource,
385
+ imap: MailServer,
386
+ smtp: MailServer.nullable(),
387
+ oauth: OAuthProvider.nullable(),
388
+ dav: DiscoveredDav.nullable()
389
+ }));
390
+ var AutoconfigQuery = named("AutoconfigQuery", z3.object({ email: Email }));
391
+
392
+ // ../contract/src/ai/models.ts
393
+ import * as z4 from "zod";
394
+ var AiDraftFormat = named("AiDraftFormat", z4.enum(["text", "markdown"]));
395
+ var AiDraftContext = named("AiDraftContext", z4.object({
396
+ accountId: Id.optional(),
397
+ replyToMessageId: Id.optional(),
398
+ currentDraft: z4.string().max(MAX_DRAFT_CONTEXT_LENGTH).optional()
399
+ }).refine((c) => !c.replyToMessageId || c.accountId, {
400
+ message: "replyToMessageId needs accountId",
401
+ path: ["accountId"]
402
+ }));
403
+ var CreateAiDraft = named("CreateAiDraft", z4.object({
404
+ instructions: trimmed(MAX_DRAFT_INSTRUCTIONS_LENGTH),
405
+ context: AiDraftContext.optional(),
406
+ format: AiDraftFormat
407
+ }));
408
+ var AiDraft = named("AiDraft", z4.object({ text: z4.string() }));
409
+
410
+ // ../contract/src/audit-log/models.ts
411
+ import * as z5 from "zod";
412
+ var AuditActorType = named("AuditActorType", z5.enum(["user", "apiKey", "accountToken", "connectSession"]));
413
+ var AuditActor = named("AuditActor", z5.object({
414
+ type: AuditActorType,
415
+ id: Id,
416
+ label: z5.string().nullable()
417
+ }));
418
+ var AuditSurface = named("AuditSurface", z5.enum(["v1", "rpc"]));
419
+ var AuditOutcome = named("AuditOutcome", z5.enum(["success", "failure"]));
420
+ var AuditEntry = named("AuditEntry", z5.object({
421
+ id: Id,
422
+ createdAt: Timestamp,
423
+ actor: AuditActor,
424
+ action: z5.string(),
425
+ surface: AuditSurface,
426
+ targets: z5.record(z5.string(), z5.union([z5.string(), z5.array(z5.string())])),
427
+ ip: z5.string().nullable(),
428
+ userAgent: z5.string().nullable(),
429
+ status: z5.number().int(),
430
+ errorReason: z5.string().nullable(),
431
+ requestId: z5.string().nullable()
432
+ }));
433
+ var AuditEntryPage = Page(AuditEntry);
434
+ var ListAuditLogQuery = named("ListAuditLogQuery", withDateRange(z5.object({
435
+ ...Paged,
436
+ actorType: AuditActorType.optional(),
437
+ actorId: Id.optional(),
438
+ action: z5.string().min(1).max(MAX_FILTER_QUERY_LENGTH).optional(),
439
+ accountId: Id.optional(),
440
+ outcome: AuditOutcome.optional(),
441
+ from: QueryTimestamp.optional(),
442
+ to: QueryTimestamp.optional()
443
+ }), { from: "from", to: "to", inclusive: true }));
444
+
445
+ // ../contract/src/calendar/models.ts
446
+ import * as z8 from "zod";
447
+
448
+ // ../contract/src/meetings/models.ts
449
+ import * as z7 from "zod";
450
+
451
+ // ../contract/src/connect/models.ts
452
+ import * as z6 from "zod";
453
+ var MailAccess = named("MailAccess", z6.enum(["api", "imap"]));
454
+ var ConnectOptions = named("ConnectOptions", z6.object({
455
+ calendarEnabled: z6.boolean().optional(),
456
+ contactsEnabled: z6.boolean().optional(),
457
+ bodyStorage: BodyStorage.optional(),
458
+ mailAccess: MailAccess.optional(),
459
+ sharedMailboxesEnabled: z6.boolean().optional()
460
+ }));
461
+ var ConnectProvider = named("ConnectProvider", z6.enum(["google", "microsoft", "imap"]));
462
+ var ConnectSessionStatus = named("ConnectSessionStatus", z6.enum(["pending", "completed", "failed"]));
463
+ var ConnectErrorCode = named("ConnectErrorCode", z6.enum([
464
+ "session_used",
465
+ "session_expired",
466
+ "provider_not_allowed",
467
+ "access_denied",
468
+ "access_token_rejected",
469
+ "provider_error",
470
+ "provider_unavailable",
471
+ "provider_not_configured",
472
+ "unsupported_provider",
473
+ "token_exchange_failed",
474
+ "invalid_client",
475
+ "invalid_grant",
476
+ "missing_refresh_token",
477
+ "scope_denied",
478
+ "identity_failed",
479
+ "identity_unavailable",
480
+ "credential_conflict",
481
+ "credential_missing",
482
+ "delegate_missing",
483
+ "account_exists",
484
+ "account_mismatch",
485
+ "account_not_found",
486
+ "unknown_oauth_app",
487
+ "oauth_app_mismatch",
488
+ "oauth_app_secret_unreadable",
489
+ "google_imap_requires_own_app",
490
+ "unknown_meeting_app",
491
+ "meeting_app_mismatch",
492
+ "meeting_app_secret_unreadable",
493
+ "consent_failed",
494
+ "consent_required",
495
+ "consent_revoked",
496
+ "consent_unverified",
497
+ "directory_already_connected",
498
+ "directory_mismatch",
499
+ "directory_unavailable",
500
+ "invalid_certificate",
501
+ "invalid_service_account_key",
502
+ "org_connection_not_found",
503
+ "org_connection_secret_unreadable",
504
+ "plan_limit_accounts",
505
+ "plan_module_unavailable",
506
+ "billing_past_due",
507
+ "internal_error"
508
+ ]));
509
+ var ConnectSessionPublic = named("ConnectSessionPublic", z6.object({
510
+ tenantName: z6.string(),
511
+ providers: z6.array(ConnectProvider),
512
+ microsoftCloud: MicrosoftCloud,
513
+ status: ConnectSessionStatus,
514
+ reauthEmail: z6.string().nullable(),
515
+ loginHint: z6.string().nullable(),
516
+ expiresAt: Timestamp
517
+ }));
518
+ var ConnectSession = named("ConnectSession", z6.object({
519
+ id: Id,
520
+ status: ConnectSessionStatus,
521
+ error: ConnectErrorCode.nullable(),
522
+ accountId: Id.nullable(),
523
+ email: z6.string().nullable(),
524
+ expiresAt: Timestamp,
525
+ createdAt: Timestamp
526
+ }));
527
+ var RedirectState = z6.string().max(MAX_STATE_LENGTH);
528
+ var SessionTtlSeconds = z6.number().int().min(MIN_TOKEN_TTL_SECONDS).max(MAX_TOKEN_TTL_SECONDS).default(3600);
529
+ var CreateConnectSession = named("CreateConnectSession", z6.object({
530
+ redirectUri: z6.url().optional(),
531
+ providers: z6.array(ConnectProvider).min(1).default(["google", "microsoft"]),
532
+ microsoftCloud: MicrosoftCloud.default("global"),
533
+ oauthAppIds: z6.object({ google: Id.optional(), microsoft: Id.optional() }).optional(),
534
+ externalRef: ExternalRef.optional(),
535
+ loginHint: Email.optional(),
536
+ state: RedirectState.optional(),
537
+ accountId: Id.optional(),
538
+ options: ConnectOptions.optional(),
539
+ ttlSeconds: SessionTtlSeconds
540
+ }));
541
+ var SessionLink = named("SessionLink", z6.object({ id: Id, url: z6.url(), expiresAt: Timestamp }));
542
+ var ConnectSessionIdInput = named("ConnectSessionIdInput", z6.object({ sessionId: Id }));
543
+ var StartOAuthRequest = named("StartOAuthRequest", z6.object({
544
+ provider: OAuthProvider,
545
+ redirectUri: z6.url(),
546
+ microsoftCloud: MicrosoftCloud.default("global"),
547
+ oauthAppId: Id.optional(),
548
+ externalRef: ExternalRef.optional(),
549
+ state: RedirectState.optional(),
550
+ accountId: Id.optional(),
551
+ options: ConnectOptions.optional()
552
+ }));
553
+ var OAuthAuthorization = named("OAuthAuthorization", z6.object({ authorizeUrl: z6.url() }));
554
+ var SessionTokenInput = named("SessionTokenInput", z6.object({ token: z6.string().min(1) }));
555
+ var HostedImapResult = named("HostedImapResult", z6.object({ redirectUrl: z6.url() }));
556
+
557
+ // ../contract/src/meetings/models.ts
558
+ var MeetingService = named("MeetingService", z7.enum(["zoom", "webex"]));
559
+ var MeetingConnectionStatus = named("MeetingConnectionStatus", z7.enum(["active", "reauth_required"]));
560
+ var MeetingConnection = named("MeetingConnection", z7.object({
561
+ service: MeetingService,
562
+ email: z7.string(),
563
+ displayName: z7.string().nullable(),
564
+ meetingAppId: Id.nullable(),
565
+ status: MeetingConnectionStatus,
566
+ statusReason: z7.string().nullable(),
567
+ createdAt: Timestamp,
568
+ updatedAt: Timestamp
569
+ }));
570
+ var MeetingConnectionList = List(MeetingConnection);
571
+ var MeetingAppKind = named("MeetingAppKind", z7.enum(["zoom_oauth", "zoom_s2s", "webex_oauth", "webex_service_app"]));
572
+ var MeetingAppStatus = named("MeetingAppStatus", z7.enum(["unverified", "active", "failing"]));
573
+ var MeetingApp = named("MeetingApp", z7.object({
574
+ id: Id,
575
+ kind: MeetingAppKind,
576
+ service: MeetingService,
577
+ name: z7.string(),
578
+ clientId: z7.string(),
579
+ zoomAccountId: z7.string().nullable(),
580
+ status: MeetingAppStatus.nullable(),
581
+ statusDetail: z7.string().nullable(),
582
+ checkedAt: Timestamp.nullable(),
583
+ scopes: z7.array(z7.string()),
584
+ redirectUri: z7.string().nullable(),
585
+ createdAt: Timestamp
586
+ }));
587
+ var MeetingAppList = List(MeetingApp);
588
+ var ClientId = z7.string().min(1).max(MAX_MEETING_APP_ID_LENGTH);
589
+ var ClientSecret = z7.string().min(1).max(MAX_SECRET_LENGTH);
590
+ var CreateMeetingApp = named("CreateMeetingApp", z7.discriminatedUnion("kind", [
591
+ z7.object({
592
+ kind: z7.literal("zoom_oauth"),
593
+ name: Name,
594
+ clientId: ClientId,
595
+ clientSecret: ClientSecret
596
+ }),
597
+ z7.object({
598
+ kind: z7.literal("zoom_s2s"),
599
+ name: Name,
600
+ accountId: z7.string().min(1).max(MAX_MEETING_APP_ID_LENGTH),
601
+ clientId: ClientId,
602
+ clientSecret: ClientSecret
603
+ }),
604
+ z7.object({
605
+ kind: z7.literal("webex_oauth"),
606
+ name: Name,
607
+ clientId: ClientId,
608
+ clientSecret: ClientSecret
609
+ }),
610
+ z7.object({
611
+ kind: z7.literal("webex_service_app"),
612
+ name: Name,
613
+ clientId: ClientId,
614
+ clientSecret: ClientSecret,
615
+ refreshToken: z7.string().min(1).max(MAX_REFRESH_TOKEN_LENGTH)
616
+ })
617
+ ]));
618
+ var MeetingAppCheck = named("MeetingAppCheck", z7.object({ name: z7.string(), isOk: z7.boolean(), detail: z7.string() }));
619
+ var MeetingAppTestResult = named("MeetingAppTestResult", z7.object({ app: MeetingApp, checks: z7.array(MeetingAppCheck) }));
620
+ var MeetingConnectSessionPublic = named("MeetingConnectSessionPublic", z7.object({
621
+ tenantName: z7.string(),
622
+ service: MeetingService,
623
+ accountEmail: z7.string(),
624
+ status: ConnectSessionStatus,
625
+ expiresAt: Timestamp
626
+ }));
627
+ var MeetingAppIdInput = named("MeetingAppIdInput", z7.object({ appId: Id }));
628
+ var TestMeetingAppRequest = named("TestMeetingAppRequest", z7.object({ user: Email.optional() }));
629
+ var MeetingConnectionInput = named("MeetingConnectionInput", AccountIdInput.extend({ service: MeetingService }));
630
+ var CreateMeetingConnectSession = named("CreateMeetingConnectSession", z7.object({
631
+ service: MeetingService,
632
+ redirectUri: z7.url().optional(),
633
+ meetingAppId: Id.optional(),
634
+ state: RedirectState.optional(),
635
+ ttlSeconds: SessionTtlSeconds
636
+ }));
637
+
638
+ // ../contract/src/calendar/models.ts
639
+ var CalendarAccess = named("CalendarAccess", z8.enum(["owner", "writer", "reader", "freebusy"]));
640
+ var Calendar = named("Calendar", z8.object({
641
+ id: Id,
642
+ name: z8.string(),
643
+ description: z8.string().nullable(),
644
+ owner: Address.nullable(),
645
+ isPrimary: z8.boolean(),
646
+ access: CalendarAccess,
647
+ canEdit: z8.boolean(),
648
+ color: z8.string().nullable(),
649
+ timeZone: z8.string().nullable(),
650
+ isHidden: z8.boolean()
651
+ }));
652
+ var CalendarList = List(Calendar);
653
+ var CalendarChangedField = named("CalendarChangedField", Calendar.keyof().exclude(["id"]));
654
+ var EventKind = named("EventKind", z8.enum(["single", "series", "occurrence", "exception"]));
655
+ var AttendeeType = named("AttendeeType", z8.enum(["required", "optional", "resource"]));
656
+ var ResponseStatus = named("ResponseStatus", z8.enum(["none", "organizer", "accepted", "tentative", "declined", "needs_action"]));
657
+ var EventStatus = named("EventStatus", z8.enum(["confirmed", "tentative", "cancelled"]));
658
+ var ShowAs = named("ShowAs", z8.enum(["free", "busy", "tentative", "oof", "working_elsewhere", "unknown"]));
659
+ var Sensitivity = named("Sensitivity", z8.enum(["normal", "personal", "private", "confidential"]));
660
+ var Attendee = named("Attendee", Address.extend({ type: AttendeeType, response: ResponseStatus }));
661
+ var EventTime = named("EventTime", z8.object({
662
+ dateTime: Timestamp,
663
+ date: DateOnly.nullable(),
664
+ timeZone: z8.string().nullable()
665
+ }));
666
+ var CalendarEvent = named("CalendarEvent", z8.object({
667
+ id: Id,
668
+ calendarId: Id,
669
+ kind: EventKind,
670
+ seriesId: Id.nullable(),
671
+ originalStart: Timestamp.nullable(),
672
+ subject: z8.string().nullable(),
673
+ snippet: z8.string().nullable(),
674
+ location: z8.string().nullable(),
675
+ start: EventTime,
676
+ end: EventTime,
677
+ isAllDay: z8.boolean(),
678
+ organizer: Address.nullable(),
679
+ attendees: z8.array(Attendee),
680
+ isOrganizer: z8.boolean(),
681
+ selfResponse: ResponseStatus,
682
+ status: EventStatus,
683
+ showAs: ShowAs,
684
+ sensitivity: Sensitivity,
685
+ onlineMeetingUrl: z8.string().nullable(),
686
+ onlineMeetingProvider: z8.string().nullable(),
687
+ iCalUid: z8.string().nullable(),
688
+ recurrence: z8.array(z8.string()).nullable(),
689
+ recurrenceTimeZone: z8.string().nullable(),
690
+ cancelledStarts: z8.array(Timestamp).nullable(),
691
+ categories: z8.array(z8.string()),
692
+ hasAttachments: z8.boolean(),
693
+ reminderMinutes: z8.number().int().nullable(),
694
+ webLink: z8.string().nullable(),
695
+ updatedAt: Timestamp
696
+ }));
697
+ var CalendarEventPage = Page(CalendarEvent);
698
+ var CalendarEventChangedField = named("CalendarEventChangedField", CalendarEvent.keyof().exclude(["id", "calendarId", "seriesId", "updatedAt"]));
699
+ var BodyContentType = named("BodyContentType", z8.enum(["text", "html"]));
700
+ var EventBody = named("EventBody", z8.object({ contentType: BodyContentType, content: z8.string() }));
701
+ var CalendarEventDetail = named("CalendarEventDetail", CalendarEvent.extend({
702
+ body: EventBody.nullable()
703
+ }));
704
+ var EventTimeInput = named("EventTimeInput", z8.union([
705
+ z8.object({
706
+ dateTime: z8.iso.datetime({ local: true }),
707
+ timeZone: z8.string().min(1).max(MAX_TIME_ZONE_LENGTH)
708
+ }),
709
+ z8.object({ date: DateOnly })
710
+ ]));
711
+ var AttendeeInput = named("AttendeeInput", AddressInput.extend({ type: AttendeeType.default("required") }));
712
+ var RecurrenceLine = z8.string().max(MAX_RECURRENCE_LINE_LENGTH).regex(/^(RRULE|RDATE|EXDATE)[:;]/, "Expected an RRULE, RDATE or EXDATE line");
713
+ var EventFields = {
714
+ subject: z8.string().max(MAX_EVENT_SUBJECT_LENGTH),
715
+ body: EventBody.extend({ content: z8.string().max(MAX_EVENT_BODY_LENGTH) }),
716
+ location: z8.string().max(MAX_EVENT_LOCATION_LENGTH),
717
+ start: EventTimeInput,
718
+ end: EventTimeInput,
719
+ attendees: z8.array(AttendeeInput).max(MAX_EVENT_ATTENDEES),
720
+ recurrence: z8.array(RecurrenceLine).min(1).max(MAX_RECURRENCE_LINES),
721
+ showAs: ShowAs.exclude(["unknown"]),
722
+ sensitivity: Sensitivity,
723
+ reminderMinutes: z8.number().int().min(0).max(MAX_REMINDER_MINUTES).nullable(),
724
+ onlineMeetingUrl: z8.url({ protocol: /^https$/ }).max(MAX_URL_LENGTH)
725
+ };
726
+ var Notify = z8.boolean().default(true);
727
+ var CreateCalendarEvent = named("CreateCalendarEvent", z8.object({
728
+ calendarId: Id,
729
+ subject: EventFields.subject.optional(),
730
+ body: EventFields.body.optional(),
731
+ location: EventFields.location.optional(),
732
+ start: EventFields.start,
733
+ end: EventFields.end,
734
+ attendees: EventFields.attendees.default([]),
735
+ recurrence: EventFields.recurrence.optional(),
736
+ showAs: EventFields.showAs.optional(),
737
+ sensitivity: EventFields.sensitivity.optional(),
738
+ reminderMinutes: EventFields.reminderMinutes.optional(),
739
+ onlineMeeting: z8.boolean().default(false),
740
+ onlineMeetingUrl: EventFields.onlineMeetingUrl.optional(),
741
+ onlineMeetingService: MeetingService.optional(),
742
+ notify: Notify
743
+ }));
744
+ var CreateCalendarEventInput = AccountIdInput.extend(CreateCalendarEvent.shape).superRefine((input, ctx) => {
745
+ const options = [
746
+ input.onlineMeeting,
747
+ input.onlineMeetingUrl !== undefined,
748
+ input.onlineMeetingService !== undefined
749
+ ].filter(Boolean).length;
750
+ if (options > 1)
751
+ ctx.addIssue({
752
+ code: "custom",
753
+ path: ["onlineMeetingService"],
754
+ message: "Give one of onlineMeeting, onlineMeetingUrl and onlineMeetingService",
755
+ params: { reason: "multiple_meeting_options" }
756
+ });
757
+ });
758
+ var UpdateCalendarEvent = named("UpdateCalendarEvent", z8.object({
759
+ subject: EventFields.subject.optional(),
760
+ body: EventFields.body.optional(),
761
+ location: EventFields.location.optional(),
762
+ start: EventFields.start.optional(),
763
+ end: EventFields.end.optional(),
764
+ attendees: EventFields.attendees.optional(),
765
+ recurrence: EventFields.recurrence.optional(),
766
+ showAs: EventFields.showAs.optional(),
767
+ sensitivity: EventFields.sensitivity.optional(),
768
+ reminderMinutes: EventFields.reminderMinutes.optional(),
769
+ onlineMeetingUrl: EventFields.onlineMeetingUrl.nullable().optional(),
770
+ notify: Notify
771
+ }));
772
+ var DeleteCalendarEventQuery = named("DeleteCalendarEventQuery", z8.object({
773
+ notify: QueryBoolean.default(true),
774
+ comment: z8.string().max(MAX_EVENT_COMMENT_LENGTH).optional()
775
+ }));
776
+ var RsvpResponse = named("RsvpResponse", z8.enum(["accepted", "tentative", "declined"]));
777
+ var RespondToCalendarEventRequest = named("RespondToCalendarEventRequest", z8.object({
778
+ response: RsvpResponse,
779
+ comment: z8.string().max(MAX_EVENT_COMMENT_LENGTH).optional(),
780
+ notify: z8.boolean().default(true)
781
+ }));
782
+ var CalendarEventIdInput = named("CalendarEventIdInput", AccountIdInput.extend({ eventId: Id }));
783
+ var DeleteCalendarEventInput = named("DeleteCalendarEventInput", z8.object({ params: CalendarEventIdInput, query: DeleteCalendarEventQuery }));
784
+ var ListCalendarEventsQuery = named("ListCalendarEventsQuery", z8.object({
785
+ start: QueryTimestamp,
786
+ end: QueryTimestamp,
787
+ calendarId: Id.optional(),
788
+ limit: pageLimit(MAX_CALENDAR_EVENT_PAGE_SIZE, DEFAULT_CALENDAR_EVENT_PAGE_SIZE),
789
+ cursor: Cursor.optional()
790
+ }));
791
+ var ListCalendarEventsInput = withDateRange(AccountIdInput.extend(ListCalendarEventsQuery.shape), { from: "start", to: "end", maxSpanMs: MAX_EVENT_RANGE_DAYS * DAY_MS });
792
+ var BusyStatus = named("BusyStatus", z8.enum(["busy", "tentative", "oof", "working_elsewhere", "unknown"]));
793
+ var BusyBlock = named("BusyBlock", z8.object({ start: Timestamp, end: Timestamp, status: BusyStatus }));
794
+ var AttendeeAvailability = named("AttendeeAvailability", z8.object({
795
+ address: z8.string(),
796
+ busy: z8.array(BusyBlock),
797
+ error: z8.string().nullable()
798
+ }));
799
+ var Weekday = named("Weekday", z8.enum(["mon", "tue", "wed", "thu", "fri", "sat", "sun"]));
800
+ var TimeOfDay = z8.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "Expected HH:MM");
801
+ var WorkingHours = named("WorkingHours", z8.object({
802
+ start: TimeOfDay,
803
+ end: TimeOfDay,
804
+ days: z8.array(Weekday).min(1).default(["mon", "tue", "wed", "thu", "fri"])
805
+ }).refine((hours) => hours.end > hours.start, {
806
+ message: "workingHours end must be after start",
807
+ path: ["end"],
808
+ params: { reason: "invalid_working_hours" }
809
+ }));
810
+ var SlotMinutes = z8.number().int().min(MIN_SLOT_MINUTES).max(MAX_SLOT_MINUTES).default(30);
811
+ var FindAvailabilityRequest = named("FindAvailabilityRequest", z8.object({
812
+ attendees: z8.array(Email).min(1).max(MAX_AVAILABILITY_ATTENDEES),
813
+ start: QueryTimestamp,
814
+ end: QueryTimestamp,
815
+ durationMinutes: SlotMinutes,
816
+ stepMinutes: SlotMinutes,
817
+ timeZone: z8.string().min(1).max(MAX_TIME_ZONE_LENGTH).default("UTC"),
818
+ workingHours: WorkingHours.optional(),
819
+ maxSuggestions: z8.number().int().min(0).max(MAX_SUGGESTIONS).default(10),
820
+ includeAccount: z8.boolean().default(true)
821
+ }));
822
+ var FindAvailabilityInput = withDateRange(AccountIdInput.extend(FindAvailabilityRequest.shape), { from: "start", to: "end", maxSpanMs: MAX_AVAILABILITY_DAYS * DAY_MS });
823
+ var TimeSlot = named("TimeSlot", z8.object({ start: Timestamp, end: Timestamp }));
824
+ var Availability = named("Availability", z8.object({
825
+ attendees: z8.array(AttendeeAvailability),
826
+ suggestions: z8.array(TimeSlot)
827
+ }));
828
+
829
+ // ../contract/src/contacts/models.ts
830
+ import * as z9 from "zod";
831
+ var ContactEmailType = named("ContactEmailType", z9.enum(["home", "work", "other"]));
832
+ var ContactPhoneType = named("ContactPhoneType", z9.enum(["mobile", "home", "work", "other"]));
833
+ var ContactEmail = named("ContactEmail", Address.pick({ address: true }).extend({ type: ContactEmailType.nullable() }));
834
+ var ContactPhone = named("ContactPhone", z9.object({ number: z9.string(), type: ContactPhoneType.nullable() }));
835
+ var ContactFolder = named("ContactFolder", z9.object({
836
+ id: Id,
837
+ name: z9.string(),
838
+ isDefault: z9.boolean()
839
+ }));
840
+ var ContactFolderList = List(ContactFolder);
841
+ var Birthday = z9.string().regex(/^(\d{4}|--)-\d{2}-\d{2}$/, "Expected YYYY-MM-DD or --MM-DD");
842
+ var Contact = named("Contact", z9.object({
843
+ id: Id,
844
+ folderId: Id.nullable(),
845
+ displayName: z9.string().nullable(),
846
+ givenName: z9.string().nullable(),
847
+ middleName: z9.string().nullable(),
848
+ familyName: z9.string().nullable(),
849
+ nickname: z9.string().nullable(),
850
+ emails: z9.array(ContactEmail),
851
+ phones: z9.array(ContactPhone),
852
+ organization: z9.string().nullable(),
853
+ jobTitle: z9.string().nullable(),
854
+ department: z9.string().nullable(),
855
+ birthday: z9.string().nullable(),
856
+ notes: z9.string().nullable(),
857
+ hasPhoto: z9.boolean().nullable(),
858
+ updatedAt: Timestamp
859
+ }));
860
+ var ContactPage = Page(Contact);
861
+ var ContactChangedField = named("ContactChangedField", z9.enum([...Contact.keyof().exclude(["id", "updatedAt"]).options, "photo"]));
862
+ var ContactEmailInput = named("ContactEmailInput", AddressInput.pick({ address: true }).extend({ type: ContactEmailType.optional() }));
863
+ var ContactPhoneInput = named("ContactPhoneInput", z9.object({
864
+ number: trimmed(MAX_CONTACT_PHONE_LENGTH),
865
+ type: ContactPhoneType.optional()
866
+ }));
867
+ var ContactFields = {
868
+ displayName: trimmed(MAX_CONTACT_FIELD_LENGTH),
869
+ givenName: trimmed(MAX_CONTACT_FIELD_LENGTH),
870
+ middleName: trimmed(MAX_CONTACT_FIELD_LENGTH),
871
+ familyName: trimmed(MAX_CONTACT_FIELD_LENGTH),
872
+ nickname: trimmed(MAX_CONTACT_FIELD_LENGTH),
873
+ organization: trimmed(MAX_CONTACT_FIELD_LENGTH),
874
+ jobTitle: trimmed(MAX_CONTACT_FIELD_LENGTH),
875
+ department: trimmed(MAX_CONTACT_FIELD_LENGTH),
876
+ birthday: Birthday,
877
+ notes: z9.string().max(MAX_CONTACT_NOTES_LENGTH)
878
+ };
879
+ var CreateContact = named("CreateContact", z9.object({
880
+ folderId: Id.optional(),
881
+ displayName: ContactFields.displayName.optional(),
882
+ givenName: ContactFields.givenName.optional(),
883
+ middleName: ContactFields.middleName.optional(),
884
+ familyName: ContactFields.familyName.optional(),
885
+ nickname: ContactFields.nickname.optional(),
886
+ emails: z9.array(ContactEmailInput).max(MAX_CONTACT_ENTRIES).optional(),
887
+ phones: z9.array(ContactPhoneInput).max(MAX_CONTACT_ENTRIES).optional(),
888
+ organization: ContactFields.organization.optional(),
889
+ jobTitle: ContactFields.jobTitle.optional(),
890
+ department: ContactFields.department.optional(),
891
+ birthday: ContactFields.birthday.optional(),
892
+ notes: ContactFields.notes.optional()
893
+ }));
894
+ var CreateContactInput = AccountIdInput.extend(CreateContact.shape).refine((c) => c.displayName !== undefined || c.givenName !== undefined || c.familyName !== undefined || c.organization !== undefined || (c.emails?.length ?? 0) > 0 || (c.phones?.length ?? 0) > 0, {
895
+ message: "A contact needs a name, an organization, an email address or a phone number",
896
+ params: { reason: "contact_unidentifiable" }
897
+ });
898
+ var UpdateContact = named("UpdateContact", z9.object({
899
+ displayName: ContactFields.displayName.nullable().optional(),
900
+ givenName: ContactFields.givenName.nullable().optional(),
901
+ middleName: ContactFields.middleName.nullable().optional(),
902
+ familyName: ContactFields.familyName.nullable().optional(),
903
+ nickname: ContactFields.nickname.nullable().optional(),
904
+ emails: z9.array(ContactEmailInput).max(MAX_CONTACT_ENTRIES).optional(),
905
+ phones: z9.array(ContactPhoneInput).max(MAX_CONTACT_ENTRIES).optional(),
906
+ organization: ContactFields.organization.nullable().optional(),
907
+ jobTitle: ContactFields.jobTitle.nullable().optional(),
908
+ department: ContactFields.department.nullable().optional(),
909
+ birthday: ContactFields.birthday.nullable().optional(),
910
+ notes: ContactFields.notes.nullable().optional()
911
+ }));
912
+ var ContactIdInput = named("ContactIdInput", AccountIdInput.extend({ contactId: Id }));
913
+ var ListContactsQuery = named("ListContactsQuery", z9.object({
914
+ q: trimmed(MAX_FILTER_QUERY_LENGTH).optional(),
915
+ folderId: Id.optional(),
916
+ limit: pageLimit(MAX_CONTACT_PAGE_SIZE, DEFAULT_CONTACT_PAGE_SIZE),
917
+ cursor: Cursor.optional()
918
+ }));
919
+
920
+ // ../contract/src/errors.ts
921
+ import { oc } from "@orpc/contract";
922
+
923
+ // ../contract/src/reasons.ts
924
+ import * as z11 from "zod";
925
+
926
+ // ../contract/src/templates/models.ts
927
+ import * as z10 from "zod";
928
+ var TemplateSource = named("TemplateSource", z10.enum(["handlebars", "react-email"]));
929
+ var TemplateFormat = named("TemplateFormat", z10.enum(["html", "markdown"]));
930
+ var TemplateField = named("TemplateField", z10.enum(["subject", "html", "text", "previewText"]));
931
+ var TemplateFields = named("TemplateFields", z10.object({
932
+ subject: z10.string().max(MAX_TEMPLATE_SUBJECT_LENGTH).nullable(),
933
+ html: z10.string().max(MAX_TEMPLATE_BODY_LENGTH).nullable(),
934
+ text: z10.string().max(MAX_TEMPLATE_BODY_LENGTH).nullable(),
935
+ previewText: z10.string().max(MAX_TEMPLATE_PREVIEW_TEXT_LENGTH).nullable(),
936
+ format: TemplateFormat
937
+ }));
938
+ var TemplateSummary = named("TemplateSummary", z10.object({
939
+ id: Id,
940
+ accountId: Id.nullable(),
941
+ name: z10.string(),
942
+ description: z10.string().nullable(),
943
+ source: TemplateSource,
944
+ externalRef: z10.string().nullable(),
945
+ createdAt: Timestamp,
946
+ updatedAt: Timestamp
947
+ }));
948
+ var Template = named("Template", TemplateSummary.extend(TemplateFields.shape));
949
+ var TemplateSummaryPage = Page(TemplateSummary);
950
+ var RenderParams = named("RenderParams", z10.record(z10.string(), z10.unknown()));
951
+ var RenderedTemplate = named("RenderedTemplate", z10.object({
952
+ subject: z10.string().nullable(),
953
+ html: z10.string().nullable(),
954
+ text: z10.string().nullable(),
955
+ previewText: z10.string().nullable()
956
+ }));
957
+ var TemplateIdInput = named("TemplateIdInput", z10.object({ templateId: Id }));
958
+ var TemplateName = trimmed(MAX_TEMPLATE_NAME_LENGTH);
959
+ var TemplateDescription = z10.string().max(MAX_TEMPLATE_DESCRIPTION_LENGTH);
960
+ var ListTemplatesQuery = named("ListTemplatesQuery", z10.object({
961
+ ...Paged,
962
+ accountId: Id.optional(),
963
+ usableBy: Id.optional(),
964
+ name: z10.string().max(MAX_TEMPLATE_NAME_LENGTH).optional(),
965
+ query: z10.string().trim().max(MAX_TEMPLATE_NAME_LENGTH).optional()
966
+ }));
967
+ var CreateTemplate = named("CreateTemplate", z10.object({
968
+ accountId: Id.optional(),
969
+ name: TemplateName,
970
+ description: TemplateDescription.optional(),
971
+ subject: TemplateFields.shape.subject.unwrap().optional(),
972
+ html: TemplateFields.shape.html.unwrap().optional(),
973
+ text: TemplateFields.shape.text.unwrap().optional(),
974
+ previewText: TemplateFields.shape.previewText.unwrap().optional(),
975
+ format: TemplateFormat.default("html"),
976
+ source: TemplateSource.default("handlebars"),
977
+ externalRef: ExternalRef.optional()
978
+ }));
979
+ var UpdateTemplate = named("UpdateTemplate", z10.object({
980
+ name: TemplateName.optional(),
981
+ description: TemplateDescription.nullable().optional(),
982
+ subject: TemplateFields.shape.subject.optional(),
983
+ html: TemplateFields.shape.html.optional(),
984
+ text: TemplateFields.shape.text.optional(),
985
+ previewText: TemplateFields.shape.previewText.optional(),
986
+ format: TemplateFormat.optional(),
987
+ source: TemplateSource.optional(),
988
+ externalRef: ExternalRef.nullable().optional()
989
+ }));
990
+ var RenderTemplateRequest = named("RenderTemplateRequest", z10.object({
991
+ templateId: Id.optional(),
992
+ accountId: Id.optional(),
993
+ subject: z10.string().max(MAX_TEMPLATE_SUBJECT_LENGTH).optional(),
994
+ html: z10.string().max(MAX_TEMPLATE_BODY_LENGTH).optional(),
995
+ text: z10.string().max(MAX_TEMPLATE_BODY_LENGTH).optional(),
996
+ previewText: z10.string().max(MAX_TEMPLATE_PREVIEW_TEXT_LENGTH).optional(),
997
+ format: TemplateFormat.optional(),
998
+ params: RenderParams.default({})
999
+ }).superRefine((request, ctx) => {
1000
+ const { templateId, subject, html, text, previewText, format } = request;
1001
+ if (templateId === undefined)
1002
+ return;
1003
+ if ([subject, html, text, previewText, format].some((field) => field !== undefined))
1004
+ ctx.addIssue({
1005
+ code: "custom",
1006
+ path: ["templateId"],
1007
+ message: "Render either a template or inline fields, not both",
1008
+ params: { reason: "template_and_inline_fields" }
1009
+ });
1010
+ }));
1011
+
1012
+ // ../contract/src/reasons.ts
1013
+ var InputIssue = named("InputIssue", z11.object({ path: z11.array(z11.union([z11.string(), z11.number()])), message: z11.string() }));
1014
+ var InvalidInputData = named("InvalidInputData", z11.object({ issues: z11.array(InputIssue) }));
1015
+ var UnknownIdsData = named("UnknownIdsData", z11.object({ ids: z11.array(Id) }));
1016
+ var TemplateFieldData = named("TemplateFieldData", z11.object({ field: TemplateField }));
1017
+ var REASONS = {
1018
+ invalid_input: { status: 400, data: InvalidInputData },
1019
+ invalid_cursor: { status: 400 },
1020
+ invalid_range: { status: 400 },
1021
+ payload_too_large: { status: 413 },
1022
+ invalid_payload: { status: 400 },
1023
+ invalid_signature: { status: 400 },
1024
+ route_not_found: { status: 404 },
1025
+ internal_error: { status: 500 },
1026
+ invalid_idempotency_key: { status: 400 },
1027
+ idempotency_key_reused: { status: 422 },
1028
+ idempotency_key_in_progress: { status: 409 },
1029
+ rate_limited: { status: 429 },
1030
+ too_many_requests: { status: 429 },
1031
+ overloaded: { status: 503 },
1032
+ unauthenticated: { status: 401 },
1033
+ api_key_expired: { status: 401 },
1034
+ missing_scope: { status: 403 },
1035
+ role_forbidden: { status: 403 },
1036
+ account_token_forbidden: { status: 403 },
1037
+ session_required: { status: 403 },
1038
+ owner_required: { status: 403 },
1039
+ platform_admin_required: { status: 403 },
1040
+ email_verification_required: { status: 403 },
1041
+ console_only: { status: 403 },
1042
+ plan_limit_accounts: { status: 403 },
1043
+ plan_limit_sends: { status: 403 },
1044
+ plan_limit_webhooks: { status: 403 },
1045
+ plan_limit_storage: { status: 403 },
1046
+ plan_module_unavailable: { status: 403 },
1047
+ storage_unavailable: { status: 400 },
1048
+ tenant_not_found: { status: 404 },
1049
+ billing_past_due: { status: 402 },
1050
+ billing_disabled: { status: 404 },
1051
+ billing_unavailable: { status: 502 },
1052
+ billing_subscription_exists: { status: 409 },
1053
+ billing_customer_missing: { status: 409 },
1054
+ plan_not_billable: { status: 409 },
1055
+ account_not_found: { status: 404 },
1056
+ unknown_account: { status: 422, data: UnknownIdsData },
1057
+ account_paused: { status: 409 },
1058
+ account_disabled: { status: 409 },
1059
+ account_reauth_required: { status: 409 },
1060
+ account_misconfigured: { status: 409 },
1061
+ calendar_disabled: { status: 409 },
1062
+ contacts_disabled: { status: 409 },
1063
+ provider_not_found: { status: 404 },
1064
+ provider_throttled: { status: 429 },
1065
+ provider_unavailable: { status: 502 },
1066
+ provider_forbidden: { status: 403 },
1067
+ provider_conflict: { status: 409 },
1068
+ provider_unsupported: { status: 400 },
1069
+ provider_rejected: { status: 400 },
1070
+ outcome_unknown: { status: 502 },
1071
+ message_not_found: { status: 404 },
1072
+ unknown_mailbox: { status: 422, data: UnknownIdsData },
1073
+ mailbox_not_label: { status: 400 },
1074
+ thread_not_found: { status: 404 },
1075
+ identity_not_found: { status: 404 },
1076
+ identities_managed_by_provider: { status: 400 },
1077
+ identity_exists: { status: 409 },
1078
+ identity_not_custom: { status: 400 },
1079
+ draft_not_found: { status: 404 },
1080
+ draft_lost: { status: 502 },
1081
+ send_not_found: { status: 404 },
1082
+ attachments_too_large: { status: 400 },
1083
+ not_a_draft: { status: 400 },
1084
+ unknown_reply_target: { status: 422 },
1085
+ from_not_allowed: { status: 400 },
1086
+ tracking_unsupported: { status: 400 },
1087
+ honeypot_needs_click_tracking: { status: 400 },
1088
+ list_send_recipients: { status: 400 },
1089
+ suppressed: { status: 400 },
1090
+ no_recipients: { status: 400 },
1091
+ list_unsubscribe_unavailable: { status: 400 },
1092
+ send_not_reschedulable: { status: 409 },
1093
+ send_not_cancellable: { status: 409 },
1094
+ suppression_list_not_found: { status: 404 },
1095
+ suppression_entry_not_found: { status: 404 },
1096
+ suppression_review_not_found: { status: 404 },
1097
+ suppression_list_exists: { status: 409 },
1098
+ suppression_list_not_empty: { status: 409 },
1099
+ template_not_found: { status: 404 },
1100
+ unknown_template: { status: 422 },
1101
+ template_and_inline_fields: { status: 400 },
1102
+ invalid_template: { status: 400, data: TemplateFieldData },
1103
+ template_account_mismatch: { status: 400 },
1104
+ template_render_failed: { status: 400, data: TemplateFieldData },
1105
+ params_too_large: { status: 400 },
1106
+ template_name_taken: { status: 409 },
1107
+ calendar_not_found: { status: 404 },
1108
+ calendar_event_not_found: { status: 404 },
1109
+ contact_not_found: { status: 404 },
1110
+ photo_not_found: { status: 404 },
1111
+ unknown_calendar: { status: 422 },
1112
+ unknown_contact_folder: { status: 422 },
1113
+ calendar_read_only: { status: 403 },
1114
+ organizer_cannot_respond: { status: 409 },
1115
+ invalid_time_zone: { status: 400 },
1116
+ invalid_working_hours: { status: 400 },
1117
+ mixed_event_times: { status: 400 },
1118
+ invalid_recurrence: { status: 400 },
1119
+ recurrence_on_instance: { status: 400 },
1120
+ contact_unidentifiable: { status: 400 },
1121
+ multiple_meeting_options: { status: 400 },
1122
+ meeting_service_not_connected: { status: 409 },
1123
+ meeting_service_reauth_required: { status: 409 },
1124
+ meeting_host_missing: { status: 409 },
1125
+ meeting_recurrence_unsupported: { status: 400 },
1126
+ meeting_service_rejected: { status: 400 },
1127
+ meeting_deleted: { status: 409 },
1128
+ meeting_service_unavailable: { status: 502 },
1129
+ meeting_app_not_found: { status: 404 },
1130
+ unknown_meeting_app: { status: 422 },
1131
+ meeting_connection_not_found: { status: 404 },
1132
+ meeting_connect_session_not_found: { status: 404 },
1133
+ meeting_app_exists: { status: 409 },
1134
+ meeting_app_in_use: { status: 409 },
1135
+ meeting_app_mismatch: { status: 400 },
1136
+ meeting_app_secret_unreadable: { status: 400 },
1137
+ not_testable: { status: 400 },
1138
+ autodiscovery_failed: { status: 422 },
1139
+ password_required: { status: 400 },
1140
+ oauth_servers_fixed: { status: 400 },
1141
+ dav_discovery_failed: { status: 400 },
1142
+ dav_unsupported: { status: 400 },
1143
+ login_failed: { status: 400 },
1144
+ server_not_allowed: { status: 400 },
1145
+ plaintext_not_allowed: { status: 400 },
1146
+ not_dav_server: { status: 400 },
1147
+ not_imap_account: { status: 400 },
1148
+ password_not_stored: { status: 400 },
1149
+ password_unsupported: { status: 400 },
1150
+ connect_session_not_found: { status: 404 },
1151
+ session_used: { status: 409 },
1152
+ session_expired: { status: 400 },
1153
+ provider_not_allowed: { status: 400 },
1154
+ redirect_uri_not_allowed: { status: 400 },
1155
+ access_denied: { status: 400 },
1156
+ access_token_rejected: { status: 400 },
1157
+ account_exists: { status: 409 },
1158
+ account_mismatch: { status: 409 },
1159
+ consent_failed: { status: 400 },
1160
+ consent_revoked: { status: 400 },
1161
+ consent_unverified: { status: 400 },
1162
+ credential_conflict: { status: 400 },
1163
+ credential_missing: { status: 400 },
1164
+ identity_failed: { status: 400 },
1165
+ identity_unavailable: { status: 400 },
1166
+ invalid_client: { status: 400 },
1167
+ invalid_grant: { status: 400 },
1168
+ missing_refresh_token: { status: 400 },
1169
+ unknown_oauth_app: { status: 422 },
1170
+ oauth_app_not_found: { status: 404 },
1171
+ oauth_app_mismatch: { status: 400 },
1172
+ oauth_app_in_use: { status: 409 },
1173
+ oauth_app_secret_unreadable: { status: 400 },
1174
+ google_imap_requires_own_app: { status: 400 },
1175
+ org_connection_account: { status: 400 },
1176
+ shared_mailbox_account: { status: 400 },
1177
+ gmail_push_unsupported: { status: 400 },
1178
+ push_unavailable: { status: 400 },
1179
+ cloud_required: { status: 400 },
1180
+ provider_error: { status: 400 },
1181
+ provider_not_configured: { status: 400 },
1182
+ unsupported_provider: { status: 400 },
1183
+ scope_denied: { status: 400 },
1184
+ token_exchange_failed: { status: 400 },
1185
+ org_connection_not_found: { status: 404 },
1186
+ org_connection_secret_unreadable: { status: 400 },
1187
+ consent_required: { status: 409 },
1188
+ delegate_missing: { status: 400 },
1189
+ directory_unavailable: { status: 409 },
1190
+ directory_already_connected: { status: 409 },
1191
+ directory_mismatch: { status: 400 },
1192
+ directory_id_not_allowed: { status: 400 },
1193
+ credential_mismatch: { status: 400 },
1194
+ connection_in_use: { status: 409 },
1195
+ invalid_certificate: { status: 400 },
1196
+ invalid_service_account_key: { status: 400 },
1197
+ not_microsoft: { status: 400 },
1198
+ user_required: { status: 400 },
1199
+ shared_mailbox_unsupported: { status: 400 },
1200
+ scope_missing: { status: 400 },
1201
+ shared_mailbox_inaccessible: { status: 422 },
1202
+ webhook_endpoint_not_found: { status: 404 },
1203
+ endpoint_disabled: { status: 409 },
1204
+ event_not_found: { status: 404 },
1205
+ replay_range_too_large: { status: 400 },
1206
+ mailbox_outside_accounts: { status: 400 },
1207
+ https_required: { status: 400 },
1208
+ export_not_found: { status: 404 },
1209
+ export_not_ready: { status: 409 },
1210
+ export_failed: { status: 409 },
1211
+ export_running: { status: 409 },
1212
+ export_in_progress: { status: 409 },
1213
+ export_expired: { status: 410 },
1214
+ export_corrupt: { status: 500 },
1215
+ confirmation_mismatch: { status: 400 },
1216
+ erase_scheduled: { status: 409 },
1217
+ erase_started: { status: 409 },
1218
+ erase_not_found: { status: 404 },
1219
+ ai_disabled: { status: 400 },
1220
+ ai_unavailable: { status: 502 },
1221
+ ai_suspended: { status: 502 },
1222
+ ai_refused: { status: 400 },
1223
+ unknown_message: { status: 422 }
1224
+ };
1225
+ function reasonGuards(table) {
1226
+ const isReason = (code) => typeof code === "string" && Object.hasOwn(table, code);
1227
+ return {
1228
+ isReason,
1229
+ reasonOf(error) {
1230
+ if (typeof error !== "object" || error === null || !("code" in error))
1231
+ return null;
1232
+ return isReason(error.code) ? error.code : null;
1233
+ }
1234
+ };
1235
+ }
1236
+ var v1Guards = reasonGuards(REASONS);
1237
+ var isErrorReason = v1Guards.isReason;
1238
+ var reasonOf = v1Guards.reasonOf;
1239
+
1240
+ // ../contract/src/errors.ts
1241
+ function errorMap(table, conditions) {
1242
+ const map = {};
1243
+ for (const [reason, message] of Object.entries(conditions)) {
1244
+ const definition = table[reason];
1245
+ map[reason] = {
1246
+ status: definition.status,
1247
+ message,
1248
+ ...definition.data !== undefined && { data: definition.data }
1249
+ };
1250
+ }
1251
+ return map;
1252
+ }
1253
+ function errors(conditions) {
1254
+ return errorMap(REASONS, conditions);
1255
+ }
1256
+ var GLOBAL_ERRORS = errors({
1257
+ invalid_input: "The request does not match the operation's input schema (a malformed body included); data.issues names each problem.",
1258
+ unauthenticated: "No usable credential: none was sent, or it is malformed, unknown, revoked or an expired account token.",
1259
+ api_key_expired: "The API key is past its expiry (or the grace of the rotation that replaced it).",
1260
+ missing_scope: "The API key lacks the scope the operation needs.",
1261
+ role_forbidden: "The member's role does not allow the operation.",
1262
+ account_token_forbidden: "An account token called an operation account tokens cannot call.",
1263
+ rate_limited: "The tenant used up its plan's request rate; Retry-After says when the window ends.",
1264
+ too_many_requests: "The credential has too many requests in flight at once; retry after Retry-After.",
1265
+ overloaded: "The API is shedding load; retry after Retry-After.",
1266
+ payload_too_large: "The request body is larger than the API accepts.",
1267
+ internal_error: "An unexpected failure on our side.",
1268
+ invalid_idempotency_key: "POST only: the Idempotency-Key header is not 1 to 255 printable ASCII characters.",
1269
+ idempotency_key_reused: "POST only: the Idempotency-Key was used in the last 24 hours with another method, path or body.",
1270
+ idempotency_key_in_progress: "POST only: the request first sent with this Idempotency-Key has not been answered yet."
1271
+ });
1272
+ var base = oc.errors(GLOBAL_ERRORS);
1273
+ var BOARD_SESSION_ONLY = "The caller is not a board session (only board sessions may call this).";
1274
+ var SESSION_ONLY = errors({ session_required: BOARD_SESSION_ONLY });
1275
+ var OWNER_ONLY = errors({
1276
+ session_required: BOARD_SESSION_ONLY,
1277
+ owner_required: "The caller does not own the organization."
1278
+ });
1279
+ var PLATFORM_ONLY = errors({
1280
+ session_required: BOARD_SESSION_ONLY,
1281
+ platform_admin_required: "The caller is not a platform administrator.",
1282
+ email_verification_required: "The platform administrator has not verified their email address yet."
1283
+ });
1284
+ var ACCOUNT_NOT_FOUND = errors({
1285
+ account_not_found: "The account does not exist, or the caller is an account token for another account."
1286
+ });
1287
+ function accountState(account = "The account") {
1288
+ return errors({
1289
+ account_paused: `${account} is paused.`,
1290
+ account_disabled: `${account} is disabled.`,
1291
+ account_reauth_required: `${account} must be reconnected: its stored grant or login no longer works.`,
1292
+ account_misconfigured: `${account}'s stored grant cannot be used until an administrator fixes what it relies on (the tenant's OAuth app or organization connection is refused, missing or unreadable).`
1293
+ });
1294
+ }
1295
+ var PROVIDER_FAILURES = errors({
1296
+ provider_throttled: "The provider asked us to slow down (with Retry-After when it named a wait).",
1297
+ provider_unavailable: "The provider could not be reached (with Retry-After when it named a wait)."
1298
+ });
1299
+ function bodyStorage(field) {
1300
+ return errors({
1301
+ storage_unavailable: `${field} is cache or full and no object store is configured.`,
1302
+ plan_limit_storage: `${field} is cache or full and the tenant's plan does not include body storage.`
1303
+ });
1304
+ }
1305
+ function planModule(module) {
1306
+ return errors({ plan_module_unavailable: `The tenant's plan does not include ${module}.` });
1307
+ }
1308
+ var PLAN_LIMIT_ACCOUNTS = errors({
1309
+ plan_limit_accounts: "The tenant already has as many accounts as its plan allows."
1310
+ });
1311
+ var BILLING_PAST_DUE = errors({
1312
+ billing_past_due: "The tenant's payment is past due beyond the grace period (sends already queued or scheduled still go out)."
1313
+ });
1314
+ var INVALID_CURSOR = errors({ invalid_cursor: "The cursor is not one this list issued." });
1315
+
1316
+ // ../contract/src/events/models.ts
1317
+ import * as z15 from "zod";
1318
+
1319
+ // ../contract/src/mail/models.ts
1320
+ import * as z12 from "zod";
1321
+ var MailboxRole = named("MailboxRole", z12.enum(["inbox", "sent", "drafts", "trash", "junk", "archive", "all", "important", "custom"]));
1322
+ var MailboxKind = named("MailboxKind", z12.enum(["folder", "label"]));
1323
+ var Mailbox = named("Mailbox", z12.object({
1324
+ id: Id,
1325
+ name: z12.string(),
1326
+ path: z12.string(),
1327
+ parentId: Id.nullable(),
1328
+ role: MailboxRole,
1329
+ kind: MailboxKind,
1330
+ totalCount: z12.number().int().nullable(),
1331
+ unreadCount: z12.number().int().nullable()
1332
+ }));
1333
+ var MailboxList = List(Mailbox);
1334
+ var MailboxChangedField = named("MailboxChangedField", z12.enum(["name", "path", "parentId", "role", "kind"]));
1335
+ var MessageDirection = named("MessageDirection", z12.enum(["inbound", "outbound"]));
1336
+ var MessagePriority = named("MessagePriority", z12.enum(["high", "normal", "low"]));
1337
+ var MessageSummary = named("MessageSummary", z12.object({
1338
+ id: Id,
1339
+ threadId: ProviderId.nullable(),
1340
+ internetMessageId: z12.string().nullable(),
1341
+ mailboxIds: z12.array(Id),
1342
+ subject: z12.string().nullable(),
1343
+ from: Address.nullable(),
1344
+ to: z12.array(Address),
1345
+ cc: z12.array(Address),
1346
+ snippet: z12.string().nullable(),
1347
+ receivedAt: Timestamp.nullable(),
1348
+ sentAt: Timestamp.nullable(),
1349
+ isRead: z12.boolean(),
1350
+ isFlagged: z12.boolean(),
1351
+ isDraft: z12.boolean(),
1352
+ hasAttachments: z12.boolean(),
1353
+ categories: z12.array(z12.string()),
1354
+ direction: MessageDirection,
1355
+ outboxId: Id.nullable(),
1356
+ reference: z12.string().nullable(),
1357
+ importance: MessagePriority.nullable()
1358
+ }));
1359
+ var MessageChangedField = named("MessageChangedField", z12.enum([
1360
+ "threadId",
1361
+ "internetMessageId",
1362
+ "subject",
1363
+ "from",
1364
+ "to",
1365
+ "cc",
1366
+ "bcc",
1367
+ "replyTo",
1368
+ "snippet",
1369
+ "receivedAt",
1370
+ "sentAt",
1371
+ "isRead",
1372
+ "isFlagged",
1373
+ "isDraft",
1374
+ "hasAttachments",
1375
+ "categories",
1376
+ "mailboxIds",
1377
+ "direction",
1378
+ "outboxId",
1379
+ "importance"
1380
+ ]));
1381
+ var MessageSummaryPage = Page(MessageSummary);
1382
+ var Attachment = named("Attachment", z12.object({
1383
+ id: ProviderId,
1384
+ filename: z12.string().nullable(),
1385
+ contentType: z12.string(),
1386
+ size: z12.number().int().nullable(),
1387
+ isInline: z12.boolean(),
1388
+ contentId: z12.string().nullable()
1389
+ }));
1390
+ var ReplyKind = named("ReplyKind", z12.enum(["opt_out", "auto_reply", "bounce", "human_reply"]));
1391
+ var AutoReplyCategory = named("AutoReplyCategory", z12.enum([
1392
+ "out_of_office",
1393
+ "left_company",
1394
+ "address_changed",
1395
+ "ticket_acknowledgment",
1396
+ "challenge_response",
1397
+ "other"
1398
+ ]));
1399
+ var MessageClassification = named("MessageClassification", z12.object({
1400
+ kind: ReplyKind,
1401
+ probabilities: z12.record(z12.string(), z12.number()),
1402
+ optOutProbability: z12.number(),
1403
+ autoReplyCategory: AutoReplyCategory.nullable(),
1404
+ returnDate: DateOnly.nullable(),
1405
+ alternateContacts: z12.array(z12.string()),
1406
+ outboxId: Id.nullable(),
1407
+ model: z12.string(),
1408
+ classifiedAt: Timestamp
1409
+ }));
1410
+ var MessageBody = named("MessageBody", z12.object({ text: z12.string().nullable(), html: z12.string().nullable() }));
1411
+ var Message = named("Message", MessageSummary.extend({
1412
+ bcc: z12.array(Address),
1413
+ replyTo: z12.array(Address),
1414
+ body: MessageBody,
1415
+ attachments: z12.array(Attachment),
1416
+ classification: MessageClassification.nullable()
1417
+ }));
1418
+ var SearchSource = named("SearchSource", z12.enum(["auto", "index", "provider"]));
1419
+ var SearchResultSource = named("SearchResultSource", SearchSource.exclude(["auto"]));
1420
+ var SearchHit = named("SearchHit", MessageSummary.extend({ id: Id.nullable() }));
1421
+ var SearchPage = named("SearchPage", z12.object({ ...pageFields(SearchHit), source: SearchResultSource }));
1422
+ var BodyStorageUsage = named("BodyStorageUsage", z12.object({
1423
+ available: z12.boolean(),
1424
+ storedMessages: z12.number().int(),
1425
+ storedBytes: z12.number().int(),
1426
+ cachedBytes: z12.number().int(),
1427
+ cacheBudgetBytes: z12.number().int(),
1428
+ cacheMaxAgeDays: z12.number().int()
1429
+ }));
1430
+ var MessageIdInput = named("MessageIdInput", AccountIdInput.extend({ messageId: Id }));
1431
+ var AttachmentIdInput = named("AttachmentIdInput", MessageIdInput.extend({ attachmentId: ProviderId }));
1432
+ var ListMessagesQuery = named("ListMessagesQuery", z12.object({
1433
+ mailboxId: Id.optional(),
1434
+ unread: QueryBoolean.optional(),
1435
+ importance: MessagePriority.optional(),
1436
+ ...Paged
1437
+ }));
1438
+ var SearchMessagesQuery = named("SearchMessagesQuery", z12.object({
1439
+ q: trimmed(MAX_SEARCH_QUERY_LENGTH),
1440
+ mailboxId: Id.optional(),
1441
+ after: QueryTimestamp.optional(),
1442
+ before: QueryTimestamp.optional(),
1443
+ source: SearchSource.default("auto"),
1444
+ limit: pageLimit(MAX_SEARCH_PAGE_SIZE, DEFAULT_SEARCH_PAGE_SIZE),
1445
+ cursor: Cursor.optional()
1446
+ }));
1447
+ var UpdateMessage = named("UpdateMessage", z12.object({
1448
+ isRead: z12.boolean().optional(),
1449
+ isFlagged: z12.boolean().optional(),
1450
+ mailboxId: Id.optional(),
1451
+ addMailboxIds: z12.array(Id).max(MAX_MAILBOX_CHANGES).optional(),
1452
+ removeMailboxIds: z12.array(Id).max(MAX_MAILBOX_CHANGES).optional()
1453
+ }));
1454
+ var DeleteMessageQuery = named("DeleteMessageQuery", z12.object({
1455
+ permanent: QueryBoolean.default(false)
1456
+ }));
1457
+ var DeleteMessageInput = named("DeleteMessageInput", z12.object({ params: MessageIdInput, query: DeleteMessageQuery }));
1458
+
1459
+ // ../contract/src/send/models.ts
1460
+ import * as z14 from "zod";
1461
+
1462
+ // ../contract/src/suppressions/models.ts
1463
+ import * as z13 from "zod";
1464
+ var GLOBAL_LIST_ID = "global";
1465
+ var LIST_ID_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
1466
+ var SuppressionListId = z13.string().trim().toLowerCase().regex(LIST_ID_PATTERN);
1467
+ var ListId = SuppressionListId.refine((id) => id !== GLOBAL_LIST_ID, {
1468
+ message: `"${GLOBAL_LIST_ID}" is reserved`
1469
+ });
1470
+ var Reason = trimmed(MAX_SUPPRESSION_REASON_LENGTH);
1471
+ var ListName = Name.trim().min(1);
1472
+ var ListDescription = z13.string().trim().max(MAX_SUPPRESSION_LIST_DESCRIPTION_LENGTH);
1473
+ var SuppressionSource = named("SuppressionSource", z13.enum(["unsubscribe", "bounce", "api", "reply", "complaint", "inactive"]));
1474
+ var DetectedOptOutSource = named("DetectedOptOutSource", SuppressionSource.extract(["reply", "complaint", "inactive"]));
1475
+ var SuppressionEntry = named("SuppressionEntry", z13.object({
1476
+ address: z13.string(),
1477
+ source: SuppressionSource,
1478
+ reason: z13.string().nullable(),
1479
+ createdAt: Timestamp
1480
+ }));
1481
+ var SuppressionEntryPage = Page(SuppressionEntry);
1482
+ var SuppressionCounts = named("SuppressionCounts", z13.object({
1483
+ unsubscribe: z13.number().int(),
1484
+ bounce: z13.number().int(),
1485
+ api: z13.number().int(),
1486
+ reply: z13.number().int(),
1487
+ complaint: z13.number().int(),
1488
+ inactive: z13.number().int()
1489
+ }));
1490
+ var SuppressionList = named("SuppressionList", z13.object({
1491
+ id: z13.string(),
1492
+ name: z13.string(),
1493
+ description: z13.string().nullable(),
1494
+ count: z13.number().int(),
1495
+ counts: SuppressionCounts,
1496
+ createdAt: Timestamp.nullable()
1497
+ }));
1498
+ var SuppressionListList = List(SuppressionList);
1499
+ var SuppressionReview = named("SuppressionReview", z13.object({
1500
+ id: Id,
1501
+ listId: z13.string(),
1502
+ address: z13.string(),
1503
+ source: DetectedOptOutSource,
1504
+ accountId: Id,
1505
+ messageId: Id,
1506
+ probability: z13.number(),
1507
+ alternateContacts: z13.array(z13.string()),
1508
+ createdAt: Timestamp
1509
+ }));
1510
+ var SuppressionReviewPage = Page(SuppressionReview);
1511
+ var ListIdInput = named("ListIdInput", z13.object({ listId: ListId }));
1512
+ var SuppressionListIdInput = named("SuppressionListIdInput", z13.object({ listId: SuppressionListId }));
1513
+ var ReviewIdInput = named("ReviewIdInput", z13.object({ reviewId: Id }));
1514
+ var CreateSuppressionList = named("CreateSuppressionList", z13.object({
1515
+ id: ListId,
1516
+ name: ListName,
1517
+ description: ListDescription.nullable().optional()
1518
+ }));
1519
+ var UpdateSuppressionList = named("UpdateSuppressionList", z13.object({
1520
+ name: ListName.optional(),
1521
+ description: ListDescription.nullable().optional()
1522
+ }));
1523
+ var DeleteSuppressionListQuery = named("DeleteSuppressionListQuery", z13.object({ withEntries: QueryBoolean.default(false) }));
1524
+ var DeleteSuppressionListInput = named("DeleteSuppressionListInput", z13.object({ params: ListIdInput, query: DeleteSuppressionListQuery }));
1525
+ var ListSuppressionEntriesQuery = named("ListSuppressionEntriesQuery", z13.object({
1526
+ address: z13.string().trim().max(MAX_EMAIL_LENGTH).optional(),
1527
+ source: SuppressionSource.optional(),
1528
+ ...Paged
1529
+ }));
1530
+ var AddSuppressionEntriesRequest = named("AddSuppressionEntriesRequest", z13.object({
1531
+ addresses: z13.array(Email).min(1).max(MAX_SUPPRESSION_BATCH),
1532
+ reason: Reason.optional()
1533
+ }));
1534
+ var SuppressionEntryInput = named("SuppressionEntryInput", SuppressionListIdInput.extend({ address: trimmed(MAX_EMAIL_LENGTH) }));
1535
+ var ListSuppressionReviewsQuery = named("ListSuppressionReviewsQuery", z13.object({ listId: SuppressionListId.optional(), ...Paged }));
1536
+ var SuppressionAdded = named("SuppressionAdded", z13.object({ added: z13.boolean() }));
1537
+ var SuppressionEntriesAdded = named("SuppressionEntriesAdded", z13.object({ added: z13.number().int(), alreadyListed: z13.number().int() }));
1538
+
1539
+ // ../contract/src/send/models.ts
1540
+ var AttachmentInput = named("AttachmentInput", z14.object({
1541
+ filename: z14.string().min(1).max(MAX_FILENAME_LENGTH),
1542
+ contentType: z14.string().min(1).max(MAX_CONTENT_TYPE_LENGTH),
1543
+ content: z14.base64(),
1544
+ contentId: z14.string().max(MAX_CONTENT_ID_LENGTH).optional()
1545
+ }));
1546
+ var Compose = named("Compose", z14.object({
1547
+ to: z14.array(AddressInput).max(MAX_RECIPIENTS).default([]),
1548
+ cc: z14.array(AddressInput).max(MAX_RECIPIENTS).default([]),
1549
+ bcc: z14.array(AddressInput).max(MAX_RECIPIENTS).default([]),
1550
+ replyTo: z14.array(AddressInput).max(MAX_REPLY_TO).default([]),
1551
+ subject: z14.string().max(MAX_SUBJECT_LENGTH).optional(),
1552
+ text: z14.string().max(MAX_BODY_LENGTH).optional(),
1553
+ html: z14.string().max(MAX_BODY_LENGTH).optional(),
1554
+ attachments: z14.array(AttachmentInput).max(MAX_ATTACHMENTS).default([]),
1555
+ inReplyToMessageId: Id.optional(),
1556
+ from: AddressInput.optional(),
1557
+ signature: z14.boolean().optional(),
1558
+ priority: MessagePriority.default("normal")
1559
+ }));
1560
+ var ComposeWithTemplate = named("ComposeWithTemplate", Compose.extend({
1561
+ template: Id.optional(),
1562
+ render: z14.object({ params: RenderParams.default({}) }).optional(),
1563
+ previewText: z14.string().max(MAX_TEMPLATE_PREVIEW_TEXT_LENGTH).optional(),
1564
+ format: TemplateFormat.optional()
1565
+ }));
1566
+ var OutboxStatus = named("OutboxStatus", z14.enum(["queued", "sending", "sent", "failed", "cancelled"]));
1567
+ var OutboxEntry = named("OutboxEntry", z14.object({
1568
+ id: Id,
1569
+ accountId: Id,
1570
+ status: OutboxStatus,
1571
+ reference: z14.string().nullable(),
1572
+ sendAt: Timestamp,
1573
+ attempt: z14.number().int(),
1574
+ lastError: z14.string().nullable(),
1575
+ providerMessageId: ProviderId.nullable(),
1576
+ internetMessageId: z14.string().nullable(),
1577
+ sentAt: Timestamp.nullable(),
1578
+ createdAt: Timestamp,
1579
+ trackOpens: z14.boolean(),
1580
+ trackClicks: z14.boolean(),
1581
+ trackHoneypot: z14.boolean(),
1582
+ humanOpenCount: z14.number().int(),
1583
+ machineOpenCount: z14.number().int(),
1584
+ humanClickCount: z14.number().int(),
1585
+ machineClickCount: z14.number().int(),
1586
+ firstOpenedAt: Timestamp.nullable(),
1587
+ lastOpenedAt: Timestamp.nullable()
1588
+ }));
1589
+ var QueuedSend = named("QueuedSend", OutboxEntry.extend({
1590
+ undoUntil: Timestamp.nullable()
1591
+ }));
1592
+ var SendTracking = named("SendTracking", z14.object({
1593
+ opens: z14.boolean().optional(),
1594
+ clicks: z14.boolean().optional(),
1595
+ honeypot: z14.boolean().optional()
1596
+ }));
1597
+ var SendOptions = named("SendOptions", z14.object({
1598
+ sendAt: QueryTimestamp.optional(),
1599
+ reference: z14.string().max(MAX_SEND_REFERENCE_LENGTH).optional(),
1600
+ tracking: SendTracking.optional()
1601
+ }));
1602
+ var ListOption = {
1603
+ listId: ListId.optional()
1604
+ };
1605
+ var MailMergeEntry = named("MailMergeEntry", z14.object({
1606
+ to: AddressInput.extend({ address: z14.string().max(MAX_EMAIL_LENGTH) }),
1607
+ params: RenderParams.optional(),
1608
+ messageId: z14.string().max(MAX_MESSAGE_ID_LENGTH).optional(),
1609
+ sendAt: QueryTimestamp.optional()
1610
+ }));
1611
+ var SendMessageRequest = named("SendMessageRequest", ComposeWithTemplate.extend(SendOptions.shape).extend(ListOption));
1612
+ var MailMergeRequest = named("MailMergeRequest", ComposeWithTemplate.omit({ to: true, cc: true, bcc: true, render: true }).extend(SendOptions.shape).extend({
1613
+ ...ListOption,
1614
+ entries: z14.array(MailMergeEntry).min(1).max(MAX_MAIL_MERGE_ENTRIES)
1615
+ }).strict());
1616
+ var MailMergeSuccess = named("MailMergeSuccess", z14.object({
1617
+ success: z14.literal(true),
1618
+ to: Address,
1619
+ outboxId: Id,
1620
+ messageId: z14.string(),
1621
+ sendAt: Timestamp,
1622
+ undoUntil: Timestamp.nullable()
1623
+ }));
1624
+ var MailMergeFailure = named("MailMergeFailure", z14.object({
1625
+ success: z14.literal(false),
1626
+ to: Address,
1627
+ reason: z14.string(),
1628
+ error: z14.string(),
1629
+ status: z14.number().int()
1630
+ }));
1631
+ var MailMergeResult = named("MailMergeResult", z14.discriminatedUnion("success", [MailMergeSuccess, MailMergeFailure]));
1632
+ var MailMergeResultList = List(MailMergeResult);
1633
+ var OutboxIdInput = named("OutboxIdInput", AccountIdInput.extend({ outboxId: Id }));
1634
+ var OutboxEntryPage = Page(OutboxEntry);
1635
+ var ListOutboxQuery = named("ListOutboxQuery", z14.object({ status: OutboxStatus.optional(), ...Paged }));
1636
+ var RescheduleSendRequest = named("RescheduleSendRequest", z14.object({ sendAt: QueryTimestamp }));
1637
+ var ListTrackingEventsQuery = named("ListTrackingEventsQuery", z14.object(Paged));
1638
+ var TrackingEventKind = named("TrackingEventKind", z14.enum(["open", "click"]));
1639
+ var TrackingActor = named("TrackingActor", z14.enum(["human", "proxy", "prefetch", "scanner", "unknown"]));
1640
+ var TrackingClient = named("TrackingClient", z14.enum([
1641
+ "gmail_proxy",
1642
+ "gmail_web",
1643
+ "gmail_app",
1644
+ "outlook_web",
1645
+ "outlook_desktop",
1646
+ "outlook_mobile",
1647
+ "apple_mail",
1648
+ "apple_mail_privacy",
1649
+ "yahoo",
1650
+ "aol",
1651
+ "thunderbird",
1652
+ "proton",
1653
+ "fastmail",
1654
+ "zoho",
1655
+ "icloud_web",
1656
+ "webmail",
1657
+ "unknown"
1658
+ ]));
1659
+ var TrackingDevice = named("TrackingDevice", z14.enum(["desktop", "mobile", "tablet", "bot", "unknown"]));
1660
+ var TrackingOs = named("TrackingOs", z14.enum(["windows", "macos", "ios", "ipados", "android", "linux", "chromeos"]));
1661
+ var TrackingBrowser = named("TrackingBrowser", z14.enum(["chrome", "edge", "firefox", "safari", "opera", "samsung"]));
1662
+ var TrackingSecFetch = named("TrackingSecFetch", z14.object({
1663
+ dest: z14.string().nullable(),
1664
+ mode: z14.string().nullable(),
1665
+ site: z14.string().nullable(),
1666
+ user: z14.boolean().nullable()
1667
+ }));
1668
+ var TrackingSignals = named("TrackingSignals", z14.object({
1669
+ method: z14.string(),
1670
+ refererHost: z14.string().nullable(),
1671
+ originHost: z14.string().nullable(),
1672
+ client: TrackingClient,
1673
+ device: TrackingDevice,
1674
+ os: TrackingOs.nullable(),
1675
+ browser: TrackingBrowser.nullable(),
1676
+ country: z14.string().nullable(),
1677
+ region: z14.string().nullable(),
1678
+ city: z14.string().nullable(),
1679
+ timezone: z14.string().nullable(),
1680
+ latitude: z14.number().nullable(),
1681
+ longitude: z14.number().nullable(),
1682
+ secondsAfterSend: z14.number().nullable(),
1683
+ secondsSincePrevious: z14.number().nullable(),
1684
+ sequence: z14.number().int(),
1685
+ isConditional: z14.boolean(),
1686
+ isRange: z14.boolean(),
1687
+ isPrefetch: z14.boolean(),
1688
+ secFetch: TrackingSecFetch
1689
+ }));
1690
+ var TrackingEvent = named("TrackingEvent", z14.object({
1691
+ id: Id,
1692
+ kind: TrackingEventKind,
1693
+ linkIndex: z14.number().int().nullable(),
1694
+ url: z14.string().nullable(),
1695
+ occurredAt: Timestamp,
1696
+ userAgent: z14.string().nullable(),
1697
+ ip: z14.string().nullable(),
1698
+ actor: TrackingActor,
1699
+ reasons: z14.array(z14.string()),
1700
+ isMachine: z14.boolean(),
1701
+ ...TrackingSignals.shape,
1702
+ headers: z14.record(z14.string(), z14.string())
1703
+ }));
1704
+ var TrackingEventPage = Page(TrackingEvent);
1705
+
1706
+ // ../contract/src/events/models.ts
1707
+ var AccountConnectedData = named("AccountConnectedData", z15.object({
1708
+ email: z15.string(),
1709
+ provider: AccountProvider,
1710
+ cloud: MicrosoftCloud.nullable(),
1711
+ externalRef: z15.string().nullable(),
1712
+ reauthorized: z15.boolean()
1713
+ }));
1714
+ var AccountUpdatedField = named("AccountUpdatedField", z15.enum(["status", "externalRef", "settings", "imap", "dav", "credentials"]));
1715
+ var AccountUpdatedData = named("AccountUpdatedData", z15.object({ fields: z15.array(AccountUpdatedField) }));
1716
+ var AccountDeletedData = named("AccountDeletedData", z15.object({
1717
+ email: z15.string(),
1718
+ externalRef: z15.string().nullable(),
1719
+ purge: z15.boolean()
1720
+ }));
1721
+ var AccountReauthRequiredData = named("AccountReauthRequiredData", AccountConnectedData.omit({ reauthorized: true }).extend({
1722
+ reason: z15.string()
1723
+ }));
1724
+ var AccountInitializedData = named("AccountInitializedData", z15.object({ mailboxes: z15.number().int(), messages: z15.number().int() }));
1725
+ var AccountPurgedData = named("AccountPurgedData", z15.object({
1726
+ accountId: Id,
1727
+ externalRef: z15.string().nullable()
1728
+ }));
1729
+ var MailboxData = named("MailboxData", z15.object({ mailbox: Mailbox }));
1730
+ var MailboxUpdatedData = named("MailboxUpdatedData", MailboxData.extend({ changes: z15.array(MailboxChangedField) }));
1731
+ var MailboxRef = named("MailboxRef", Mailbox.pick({ id: true, name: true, role: true }));
1732
+ var MessageData = named("MessageData", z15.object({ message: MessageSummary, mailboxes: z15.array(MailboxRef) }));
1733
+ var MessageUpdatedData = named("MessageUpdatedData", MessageData.extend({ changes: z15.array(MessageChangedField) }));
1734
+ var MessageDeletedData = named("MessageDeletedData", MessageData.extend({
1735
+ messageId: Id,
1736
+ providerMessageId: ProviderId
1737
+ }));
1738
+ var MessageClassifiedData = named("MessageClassifiedData", MessageData.extend({ classification: MessageClassification }));
1739
+ var BounceRecipient = named("BounceRecipient", z15.object({
1740
+ address: z15.string(),
1741
+ status: z15.string().nullable(),
1742
+ diagnostic: z15.string().nullable()
1743
+ }));
1744
+ var MessageBouncedData = named("MessageBouncedData", z15.object({
1745
+ messageId: Id,
1746
+ outboxId: Id.nullable(),
1747
+ reference: z15.string().nullable(),
1748
+ originalMessageId: z15.string().nullable(),
1749
+ recipients: z15.array(BounceRecipient)
1750
+ }));
1751
+ var TrackingHitData = named("TrackingHitData", TrackingEvent.omit({ id: true, kind: true, linkIndex: true, ip: true, headers: true }).extend({
1752
+ outboxId: Id,
1753
+ reference: z15.string().nullable(),
1754
+ messageId: Id.nullable()
1755
+ }));
1756
+ var SendError = named("SendError", z15.object({ code: z15.string(), message: z15.string() }));
1757
+ var SendFields = {
1758
+ outboxId: Id,
1759
+ reference: z15.string().nullable()
1760
+ };
1761
+ var SendSentData = named("SendSentData", z15.object({
1762
+ ...SendFields,
1763
+ attempt: z15.number().int(),
1764
+ providerMessageId: ProviderId.nullable(),
1765
+ internetMessageId: z15.string().nullable(),
1766
+ sentAt: Timestamp
1767
+ }));
1768
+ var SendRetryingData = named("SendRetryingData", z15.object({
1769
+ ...SendFields,
1770
+ attempt: z15.number().int(),
1771
+ nextAttemptAt: Timestamp,
1772
+ error: SendError
1773
+ }));
1774
+ var SendFailedData = named("SendFailedData", z15.object({ ...SendFields, attempt: z15.number().int(), error: SendError }));
1775
+ var SendRescheduledData = named("SendRescheduledData", z15.object({ ...SendFields, sendAt: Timestamp, previousSendAt: Timestamp }));
1776
+ var SuppressionCreatedData = named("SuppressionCreatedData", z15.object({ listId: z15.string(), address: z15.string(), source: SuppressionSource }));
1777
+ var CalendarInitializedData = named("CalendarInitializedData", z15.object({ calendars: z15.number().int(), events: z15.number().int() }));
1778
+ var CalendarData = named("CalendarData", z15.object({ calendar: Calendar }));
1779
+ var CalendarUpdatedData = named("CalendarUpdatedData", CalendarData.extend({ changes: z15.array(CalendarChangedField) }));
1780
+ var CalendarEventData = named("CalendarEventData", z15.object({ event: CalendarEvent }));
1781
+ var CalendarEventUpdatedData = named("CalendarEventUpdatedData", CalendarEventData.extend({ changes: z15.array(CalendarEventChangedField) }));
1782
+ var CalendarEventDeletedData = named("CalendarEventDeletedData", z15.object({ eventId: Id, calendarId: Id, kind: EventKind, seriesId: Id.nullable() }));
1783
+ var ContactsInitializedData = named("ContactsInitializedData", z15.object({ contacts: z15.number().int() }));
1784
+ var ContactData = named("ContactData", z15.object({ contact: Contact }));
1785
+ var ContactUpdatedData = named("ContactUpdatedData", ContactData.extend({ changes: z15.array(ContactChangedField) }));
1786
+ var ContactDeletedData = named("ContactDeletedData", z15.object({ contactId: Id, folderId: Id.nullable() }));
1787
+ var WebhookTestData = named("WebhookTestData", z15.object({ endpointId: Id }));
1788
+ var ExportCompletedData = named("ExportCompletedData", z15.object({
1789
+ exportId: Id,
1790
+ bytes: z15.number().int(),
1791
+ sha256: z15.string(),
1792
+ expiresAt: Timestamp
1793
+ }));
1794
+ var ExportFailedData = named("ExportFailedData", z15.object({ exportId: Id, error: z15.string() }));
1795
+ var MeetingConnectionConnectedData = named("MeetingConnectionConnectedData", z15.object({
1796
+ service: MeetingService,
1797
+ email: z15.string(),
1798
+ reconnected: z15.boolean()
1799
+ }));
1800
+ var MeetingConnectionReauthRequiredData = named("MeetingConnectionReauthRequiredData", MeetingConnectionConnectedData.omit({ reconnected: true }).extend({
1801
+ reason: z15.string()
1802
+ }));
1803
+ function defineEvent(scope, data, description) {
1804
+ return { scope, data, description };
1805
+ }
1806
+ var EVENTS = {
1807
+ "account.connected": defineEvent("account", AccountConnectedData, "An account was connected, or an existing account signed in again (reauthorized)."),
1808
+ "account.updated": defineEvent("account", AccountUpdatedData, "An account's status, externalRef, settings or connection changed."),
1809
+ "account.deleted": defineEvent("account", AccountDeletedData, "An account was deleted; its data is purged after the retention period, or at once with purge."),
1810
+ "account.reauth_required": defineEvent("account", AccountReauthRequiredData, "The provider refused the account's grant: the account must sign in again."),
1811
+ "account.initialized": defineEvent("account", AccountInitializedData, "The account's first mail sync finished; nothing before it is announced message by message."),
1812
+ "account.purged": defineEvent("tenant", AccountPurgedData, "A deleted account's data is gone for good (retention, or an immediate purge). The event names no account, since it outlives the account it reports on."),
1813
+ "mailbox.created": defineEvent("account", MailboxData, "A mailbox (folder or label) appeared."),
1814
+ "mailbox.updated": defineEvent("account", MailboxUpdatedData, "A mailbox was renamed, moved or given another role. Counts change silently."),
1815
+ "mailbox.deleted": defineEvent("account", MailboxData, "A mailbox went away."),
1816
+ "message.created": defineEvent("account", MessageData, "A message was indexed: received, sent, drafted or moved in from outside the sync window."),
1817
+ "message.updated": defineEvent("account", MessageUpdatedData, "A message changed: read, flagged, moved, or linked to the outbox entry it is a copy of."),
1818
+ "message.deleted": defineEvent("account", MessageDeletedData, "A message was removed; carries it and its mailboxes as last indexed."),
1819
+ "message.bounced": defineEvent("account", MessageBouncedData, "A delivery status notification reporting failed recipients reached the account; carries the outbox id when it answers one of our sends."),
1820
+ "message.classified": defineEvent("account", MessageClassifiedData, "AI classified a reply to one of our sends (tenant setting aiReplyClassification): carries the message, its mailboxes and the classification."),
1821
+ "message.opened": defineEvent("account", TrackingHitData, "A recipient's client (or a proxy fetching for it) loaded the open-tracking pixel of a send. Carries the actor, its reasons and the derived signals (client, device, location, referer host, sec-fetch tuple, timing); the raw headers are only on the tracking list."),
1822
+ "message.clicked": defineEvent("account", TrackingHitData, "A tracked link of a send was followed; carries the same as message.opened."),
1823
+ "send.sent": defineEvent("account", SendSentData, "The provider accepted a queued send."),
1824
+ "send.retrying": defineEvent("account", SendRetryingData, "An attempt failed and the send is queued again for nextAttemptAt."),
1825
+ "send.failed": defineEvent("account", SendFailedData, "The send failed for good: refused, or out of attempts."),
1826
+ "send.rescheduled": defineEvent("account", SendRescheduledData, "A queued send got a new sendAt before its first attempt."),
1827
+ "suppression.created": defineEvent("account", SuppressionCreatedData, "An address went onto a suppression list: the recipient unsubscribed (source unsubscribe, listId the send's), a permanent bounce said the address is gone (source bounce, listId global), or a detected opt-out was suppressed, at once or on approval (source reply or complaint, listId the send's or global; source inactive, on approval only, listId global). Only a change is announced; API adds are audited instead. The account is the one whose send it concerns."),
1828
+ "calendar.initialized": defineEvent("account", CalendarInitializedData, "The account's first calendar sync finished; nothing before it is announced event by event."),
1829
+ "calendar.created": defineEvent("account", CalendarData, "A calendar appeared."),
1830
+ "calendar.updated": defineEvent("account", CalendarUpdatedData, "A calendar changed."),
1831
+ "calendar.deleted": defineEvent("account", CalendarData, "A calendar went away."),
1832
+ "calendar_event.created": defineEvent("account", CalendarEventData, "A single event, series or exception appeared. Unmodified occurrences of a series are never announced; an occurrence changed on its own is announced as a new exception."),
1833
+ "calendar_event.updated": defineEvent("account", CalendarEventUpdatedData, "A single event, series or exception changed; a change to a series is one update of the series."),
1834
+ "calendar_event.deleted": defineEvent("account", CalendarEventDeletedData, "A single event, series or exception went away."),
1835
+ "contacts.initialized": defineEvent("account", ContactsInitializedData, "The account's first contacts sync finished; nothing before it is announced contact by contact."),
1836
+ "contact.created": defineEvent("account", ContactData, "A contact appeared."),
1837
+ "contact.updated": defineEvent("account", ContactUpdatedData, "A contact changed."),
1838
+ "contact.deleted": defineEvent("account", ContactDeletedData, "A contact went away."),
1839
+ "export.completed": defineEvent("either", ExportCompletedData, "A data export finished and can be downloaded until data.expiresAt. accountId is null for an export of the whole tenant."),
1840
+ "export.failed": defineEvent("either", ExportFailedData, "A data export failed. accountId is null for an export of the whole tenant."),
1841
+ "meeting_connection.connected": defineEvent("account", MeetingConnectionConnectedData, "An account connected (or reconnected) its own grant at a meeting service (Zoom, Webex)."),
1842
+ "meeting_connection.reauth_required": defineEvent("account", MeetingConnectionReauthRequiredData, "The service refused the account's meeting grant; meetings need a new connect. Mail and calendar sync go on."),
1843
+ "webhook.test": defineEvent("tenant", WebhookTestData, "Sent by the test action to the endpoint it names, whatever that endpoint's filters.")
1844
+ };
1845
+ var EVENT_TYPES = Object.keys(EVENTS);
1846
+ var EventType = named("EventType", z15.enum(EVENT_TYPES));
1847
+ var TEST_EVENT_TYPE = "webhook.test";
1848
+ var ENVELOPE_ACCOUNT_ID = { account: Id, tenant: z15.null(), either: Id.nullable() };
1849
+ function envelope(type) {
1850
+ const { scope, data, description } = EVENTS[type];
1851
+ const name = type.replace(/(?:^|[._])([a-z])/g, (_, letter) => letter.toUpperCase());
1852
+ return named(`${name}Event`, z15.object({
1853
+ id: EventId,
1854
+ type: z15.literal(type),
1855
+ accountId: ENVELOPE_ACCOUNT_ID[scope],
1856
+ createdAt: Timestamp,
1857
+ data,
1858
+ isTest: z15.literal(true).exactOptional()
1859
+ }).meta({ description }));
1860
+ }
1861
+ var Event = named("Event", z15.discriminatedUnion("type", EVENT_TYPES.map(envelope)));
1862
+ var ListChangesQuery = named("ListChangesQuery", z15.object({
1863
+ cursor: Cursor.optional(),
1864
+ limit: Limit,
1865
+ types: z15.union([z15.string(), z15.array(EventType)]).transform((v) => Array.isArray(v) ? v : v.split(",")).pipe(z15.array(EventType)).optional()
1866
+ }));
1867
+ var EventFeedPage = named("EventFeedPage", z15.object({ items: z15.array(Event), nextCursor: Cursor }));
1868
+
1869
+ // ../contract/src/exports/models.ts
1870
+ import * as z16 from "zod";
1871
+ var ExportStatus = named("ExportStatus", z16.enum(["queued", "running", "completed", "failed", "expired"]));
1872
+ var DataExport = named("DataExport", z16.object({
1873
+ id: Id,
1874
+ accountId: Id.nullable(),
1875
+ accountEmail: z16.string().nullable(),
1876
+ status: ExportStatus,
1877
+ bytes: z16.number().int(),
1878
+ sha256: z16.string().nullable(),
1879
+ contents: z16.record(z16.string(), z16.number().int()),
1880
+ error: z16.string().nullable(),
1881
+ createdAt: Timestamp,
1882
+ startedAt: Timestamp.nullable(),
1883
+ completedAt: Timestamp.nullable(),
1884
+ expiresAt: Timestamp.nullable()
1885
+ }));
1886
+ var DataExportPage = Page(DataExport);
1887
+ var CreateDataExport = named("CreateDataExport", z16.object({
1888
+ accountId: Id.optional()
1889
+ }));
1890
+ var ListDataExportsQuery = named("ListDataExportsQuery", z16.object(Paged));
1891
+ var DataExportIdInput = named("DataExportIdInput", z16.object({ exportId: Id }));
1892
+
1893
+ // ../contract/src/identities/models.ts
1894
+ import * as z17 from "zod";
1895
+ var IdentitySource = named("IdentitySource", z17.enum(["primary", "provider", "custom"]));
1896
+ var Signature = named("Signature", z17.object({ html: z17.string().nullable(), text: z17.string().nullable() }));
1897
+ var SignatureInput = named("SignatureInput", z17.object({
1898
+ html: z17.string().max(MAX_SIGNATURE_HTML_LENGTH).optional(),
1899
+ text: z17.string().max(MAX_SIGNATURE_TEXT_LENGTH).optional()
1900
+ }).refine((s) => s.html !== undefined || s.text !== undefined, {
1901
+ message: "A signature needs html or text"
1902
+ }));
1903
+ var Identity = named("Identity", Address.extend({
1904
+ id: Id,
1905
+ source: IdentitySource,
1906
+ signature: Signature.nullable(),
1907
+ providerSignature: Signature.nullable()
1908
+ }));
1909
+ var IdentityList = named("IdentityList", z17.object({ ...listFields(Identity), refreshedAt: Timestamp.nullable() }));
1910
+ var MailSettings = named("MailSettings", z17.object({
1911
+ signByDefault: z17.boolean()
1912
+ }));
1913
+ var UpdateMailSettings = named("UpdateMailSettings", MailSettings.partial());
1914
+ var IdentityIdInput = named("IdentityIdInput", AccountIdInput.extend({ identityId: Id }));
1915
+ var ListIdentitiesQuery = named("ListIdentitiesQuery", z17.object({ refresh: QueryBoolean.optional() }));
1916
+ var CreateIdentity = named("CreateIdentity", AddressInput.extend({ signature: SignatureInput.optional() }));
1917
+ var UpdateIdentity = named("UpdateIdentity", z17.object({
1918
+ name: z17.string().max(MAX_ADDRESS_NAME_LENGTH).nullable().optional(),
1919
+ signature: SignatureInput.nullable().optional()
1920
+ }));
1921
+
1922
+ // ../contract/src/oauth-apps/models.ts
1923
+ import * as z18 from "zod";
1924
+ var PUBSUB_TOPIC_PATTERN = /^projects\/[a-z][a-z0-9.:-]{4,61}[a-z0-9]\/topics\/(?!goog)[A-Za-z][A-Za-z0-9._~+%-]{2,254}$/;
1925
+ var GmailPushSettings = named("GmailPushSettings", z18.object({
1926
+ topic: z18.string().regex(PUBSUB_TOPIC_PATTERN, "Expected projects/<project>/topics/<topic>"),
1927
+ serviceAccount: z18.email().regex(/\.gserviceaccount\.com$/i, "Expected a Google service account email")
1928
+ }));
1929
+ var OAuthApp = named("OAuthApp", z18.object({
1930
+ id: Id,
1931
+ provider: OAuthProvider,
1932
+ name: z18.string(),
1933
+ clientId: z18.string(),
1934
+ cloud: MicrosoftCloud.nullable(),
1935
+ authority: z18.string().nullable(),
1936
+ gmailPush: GmailPushSettings.nullable(),
1937
+ gmailPushEndpoint: z18.url().nullable(),
1938
+ createdAt: Timestamp
1939
+ }));
1940
+ var OAuthAppList = List(OAuthApp);
1941
+ var CreateOAuthApp = named("CreateOAuthApp", z18.object({
1942
+ provider: OAuthProvider,
1943
+ cloud: MicrosoftCloud.optional(),
1944
+ name: Name,
1945
+ clientId: z18.string().min(1),
1946
+ clientSecret: z18.string().min(1),
1947
+ authority: z18.string().optional()
1948
+ }));
1949
+ var UpdateOAuthApp = named("UpdateOAuthApp", z18.object({
1950
+ name: Name.optional(),
1951
+ clientSecret: z18.string().min(1).optional(),
1952
+ authority: z18.string().min(1).optional(),
1953
+ gmailPush: GmailPushSettings.nullable().optional()
1954
+ }));
1955
+ var OAuthAppIdInput = named("OAuthAppIdInput", z18.object({ appId: Id }));
1956
+
1957
+ // ../contract/src/org-connections/models.ts
1958
+ import * as z19 from "zod";
1959
+ var OrgConnectionKind = named("OrgConnectionKind", z19.enum(["google_delegation", "microsoft_app"]));
1960
+ var OrgConnectionStatus = named("OrgConnectionStatus", z19.enum(["pending_consent", "unverified", "active", "failing"]));
1961
+ var MicrosoftAppCredential = named("MicrosoftAppCredential", z19.enum(["platform", "client_secret", "certificate"]));
1962
+ var GoogleDelegation = named("GoogleDelegation", z19.object({
1963
+ domain: z19.string(),
1964
+ serviceAccountEmail: z19.string(),
1965
+ directoryAdmin: z19.string().nullable(),
1966
+ scopes: z19.array(z19.string())
1967
+ }));
1968
+ var GraphPermission = named("GraphPermission", z19.object({ name: z19.string(), isRequired: z19.boolean() }));
1969
+ var MicrosoftAppAccess = named("MicrosoftAppAccess", z19.object({
1970
+ cloud: MicrosoftCloud,
1971
+ credentialType: MicrosoftAppCredential,
1972
+ directoryId: z19.string().nullable(),
1973
+ permissions: z19.array(GraphPermission)
1974
+ }));
1975
+ var OrgConnection = named("OrgConnection", z19.object({
1976
+ id: Id,
1977
+ kind: OrgConnectionKind,
1978
+ name: z19.string(),
1979
+ clientId: z19.string(),
1980
+ status: OrgConnectionStatus,
1981
+ statusDetail: z19.string().nullable(),
1982
+ checkedAt: Timestamp.nullable(),
1983
+ google: GoogleDelegation.nullable(),
1984
+ microsoft: MicrosoftAppAccess.nullable(),
1985
+ accounts: z19.number().int(),
1986
+ createdAt: Timestamp,
1987
+ updatedAt: Timestamp
1988
+ }));
1989
+ var OrgConnectionList = List(OrgConnection);
1990
+ var Pem = z19.string().min(1).max(MAX_KEY_FILE_LENGTH);
1991
+ var ServiceAccountKey = z19.string().min(2).max(MAX_KEY_FILE_LENGTH);
1992
+ var EntraId = z19.string().min(1).max(MAX_ENTRA_ID_LENGTH);
1993
+ var ClientSecret2 = z19.string().min(1).max(MAX_SECRET_LENGTH);
1994
+ var CreateOrgConnection = named("CreateOrgConnection", z19.discriminatedUnion("kind", [
1995
+ z19.object({
1996
+ kind: z19.literal("google_delegation"),
1997
+ name: Name,
1998
+ serviceAccountKey: ServiceAccountKey,
1999
+ domain: z19.string().min(3).max(MAX_HOST_LENGTH),
2000
+ calendarEnabled: z19.boolean().default(true),
2001
+ contactsEnabled: z19.boolean().default(false),
2002
+ directoryAdmin: Email.optional()
2003
+ }),
2004
+ z19.object({
2005
+ kind: z19.literal("microsoft_app"),
2006
+ name: Name,
2007
+ cloud: MicrosoftCloud.default("global"),
2008
+ credential: z19.discriminatedUnion("type", [
2009
+ z19.object({ type: z19.literal("platform") }),
2010
+ z19.object({
2011
+ type: z19.literal("client_secret"),
2012
+ clientId: EntraId,
2013
+ clientSecret: ClientSecret2
2014
+ }),
2015
+ z19.object({
2016
+ type: z19.literal("certificate"),
2017
+ clientId: EntraId,
2018
+ certificate: Pem,
2019
+ privateKey: Pem
2020
+ })
2021
+ ]),
2022
+ directoryId: EntraId.optional()
2023
+ })
2024
+ ]));
2025
+ var UpdateOrgConnection = named("UpdateOrgConnection", z19.object({
2026
+ name: Name.optional(),
2027
+ serviceAccountKey: ServiceAccountKey.optional(),
2028
+ calendarEnabled: z19.boolean().optional(),
2029
+ contactsEnabled: z19.boolean().optional(),
2030
+ directoryAdmin: Email.nullable().optional(),
2031
+ clientSecret: ClientSecret2.optional(),
2032
+ certificate: Pem.optional(),
2033
+ privateKey: Pem.optional(),
2034
+ directoryId: EntraId.optional()
2035
+ }));
2036
+ var OrgConnectionCheck = named("OrgConnectionCheck", z19.object({
2037
+ name: z19.string(),
2038
+ isOk: z19.boolean(),
2039
+ isRequired: z19.boolean(),
2040
+ detail: z19.string()
2041
+ }));
2042
+ var OrgConnectionTestResult = named("OrgConnectionTestResult", z19.object({ connection: OrgConnection, checks: z19.array(OrgConnectionCheck) }));
2043
+ var DirectoryEntry = named("DirectoryEntry", z19.object({
2044
+ id: ProviderId,
2045
+ email: z19.string(),
2046
+ displayName: z19.string().nullable(),
2047
+ accountId: Id.nullable()
2048
+ }));
2049
+ var DirectoryEntryPage = Page(DirectoryEntry);
2050
+ var AddMailboxInput = named("AddMailboxInput", z19.object({
2051
+ email: Email,
2052
+ displayName: z19.string().max(MAX_DISPLAY_NAME_LENGTH).optional(),
2053
+ externalRef: ExternalRef.optional()
2054
+ }));
2055
+ var AddMailboxesRequest = named("AddMailboxesRequest", z19.object({
2056
+ mailboxes: z19.array(AddMailboxInput).min(1).max(MAX_ADD_MAILBOXES),
2057
+ settings: SettingsOverride.optional()
2058
+ }));
2059
+ var AddedMailboxStatus = named("AddedMailboxStatus", z19.enum(["created", "exists", "failed"]));
2060
+ var AddedMailboxError = named("AddedMailboxError", z19.object({ reason: z19.string(), message: z19.string() }));
2061
+ var AddedMailbox = named("AddedMailbox", z19.object({
2062
+ email: z19.string(),
2063
+ status: AddedMailboxStatus,
2064
+ accountId: Id.nullable(),
2065
+ error: AddedMailboxError.nullable()
2066
+ }));
2067
+ var AddedMailboxList = List(AddedMailbox);
2068
+ var OrgConnectionIdInput = named("OrgConnectionIdInput", z19.object({ connectionId: Id }));
2069
+ var StartConsentRequest = named("StartConsentRequest", z19.object({ redirectUri: z19.url(), state: RedirectState.optional() }));
2070
+ var ConsentLink = named("ConsentLink", z19.object({ consentUrl: z19.url() }));
2071
+ var TestOrgConnectionRequest = named("TestOrgConnectionRequest", z19.object({ user: Email.optional() }));
2072
+ var ListDirectoryEntriesQuery = named("ListDirectoryEntriesQuery", z19.object({
2073
+ cursor: Cursor.optional(),
2074
+ limit: pageLimit(MAX_DIRECTORY_PAGE_SIZE, DEFAULT_DIRECTORY_PAGE_SIZE),
2075
+ query: z19.string().max(MAX_DIRECTORY_QUERY_LENGTH).optional()
2076
+ }));
2077
+ var OpenSharedMailboxRequest = named("OpenSharedMailboxRequest", z19.object({
2078
+ email: Email,
2079
+ displayName: z19.string().max(MAX_DISPLAY_NAME_LENGTH).optional(),
2080
+ externalRef: ExternalRef.optional(),
2081
+ settings: SettingsOverride.optional()
2082
+ }));
2083
+
2084
+ // ../contract/src/accounts/routes.ts
2085
+ import * as z20 from "zod";
2086
+ var MAIL_SETTINGS_CHECK = errors({
2087
+ login_failed: "A server (IMAP, SMTP, CalDAV or CardDAV) refuses the login.",
2088
+ server_not_allowed: "A server resolves to a private or loopback address.",
2089
+ plaintext_not_allowed: "A server is named with security none (a plaintext login)."
2090
+ });
2091
+ var AUTODISCOVERY_FAILED = errors({
2092
+ autodiscovery_failed: "imap was left out and autodiscovery found no IMAP server for the address."
2093
+ });
2094
+ var DAV_SETTINGS_CHECK = errors({
2095
+ dav_discovery_failed: "Neither a CalDAV nor a CardDAV server was found or answered.",
2096
+ not_dav_server: "A named CalDAV or CardDAV URL is no DAV server (it answers 404, 403, or not as one)."
2097
+ });
2098
+ var OAUTH_CLIENT = errors({
2099
+ unknown_oauth_app: "oauthAppId names no OAuth app of the tenant.",
2100
+ oauth_app_mismatch: "The OAuth app is for the other provider.",
2101
+ oauth_app_secret_unreadable: "The OAuth app's client secret cannot be decrypted; replace it.",
2102
+ provider_not_configured: "No OAuth app is given and the platform has none for the provider (and cloud)."
2103
+ });
2104
+ var accounts = {
2105
+ list: base.route({
2106
+ method: "GET",
2107
+ path: "/v1/accounts",
2108
+ tags: ["Accounts"],
2109
+ summary: "List accounts",
2110
+ description: "Newest first. q matches part of the email address or the external reference, case-insensitively; externalRef matches it exactly."
2111
+ }).errors(INVALID_CURSOR).input(ListAccountsQuery).output(AccountPage),
2112
+ get: base.route({
2113
+ method: "GET",
2114
+ path: "/v1/accounts/{accountId}",
2115
+ tags: ["Accounts"],
2116
+ summary: "Get an account"
2117
+ }).errors(ACCOUNT_NOT_FOUND).input(AccountIdInput).output(Account),
2118
+ create: base.route({
2119
+ method: "POST",
2120
+ path: "/v1/accounts",
2121
+ tags: ["Accounts"],
2122
+ summary: "Connect an account from an existing refresh token",
2123
+ description: "The address is the provider's (Google's userinfo, Graph's /me).",
2124
+ successStatus: 201
2125
+ }).errors({
2126
+ ...bodyStorage("settings.bodyStorage"),
2127
+ ...planModule("shared mailboxes (sharedMailboxesEnabled: true)"),
2128
+ ...PLAN_LIMIT_ACCOUNTS,
2129
+ ...BILLING_PAST_DUE,
2130
+ ...OAUTH_CLIENT,
2131
+ ...errors({
2132
+ invalid_grant: "The provider refuses the refresh token (revoked, expired or never valid).",
2133
+ invalid_client: "The provider refuses the OAuth app's client credentials.",
2134
+ token_exchange_failed: "The provider's token endpoint refuses the refresh for another reason.",
2135
+ scope_denied: "The grant lacks a scope the account needs.",
2136
+ access_token_rejected: "The provider refuses the access token the refresh returned.",
2137
+ identity_failed: "The provider's identity answer names no address.",
2138
+ identity_unavailable: "Google has not verified the account's address.",
2139
+ provider_unavailable: "The provider's token or identity endpoint could not be reached."
2140
+ })
2141
+ }).input(CreateAccount).output(Account),
2142
+ update: base.route({
2143
+ method: "PATCH",
2144
+ path: "/v1/accounts/{accountId}",
2145
+ tags: ["Accounts"],
2146
+ summary: "Update an account",
2147
+ description: "Changing settings.bodyStorage, calendarEnabled or syncWindowDays restarts the account's syncs: full stores every indexed message and backfills, none deletes stored content. status only moves between active and paused; only a new grant clears reauth_required and disabled."
2148
+ }).errors({
2149
+ ...ACCOUNT_NOT_FOUND,
2150
+ ...bodyStorage("settings.bodyStorage"),
2151
+ ...errors({
2152
+ account_reauth_required: "status is changed on an account that must be reconnected.",
2153
+ account_disabled: "status is changed on a disabled account."
2154
+ })
2155
+ }).input(AccountIdInput.extend(UpdateAccount.shape)).output(Account),
2156
+ delete: base.route({
2157
+ method: "DELETE",
2158
+ path: "/v1/accounts/{accountId}",
2159
+ tags: ["Accounts"],
2160
+ summary: "Delete an account",
2161
+ description: "Stops syncing and removes the credential at once. The account's data is purged for good after the retention period (default 30 days), or as soon as the maintenance role gets to it with ?purge=true; account.purged announces it. Its events are left to age out of the event log (within 31 days). purge=true also purges an account that is already deleted.",
2162
+ successStatus: 204,
2163
+ inputStructure: "detailed"
2164
+ }).errors(errors({
2165
+ account_not_found: "The account does not exist, or (without purge) is already deleted."
2166
+ })).input(DeleteAccountInput).output(z20.void()),
2167
+ mintToken: base.route({
2168
+ method: "POST",
2169
+ path: "/v1/accounts/{accountId}/tokens",
2170
+ tags: ["Accounts"],
2171
+ summary: "Mint an account-scoped token",
2172
+ description: "The token is returned once; it is not retrievable later."
2173
+ }).errors(ACCOUNT_NOT_FOUND).input(AccountIdInput.extend(MintAccountTokenRequest.shape)).output(AccountToken)
2174
+ };
2175
+ var imapAccounts = {
2176
+ create: base.route({
2177
+ method: "POST",
2178
+ path: "/v1/imap-accounts",
2179
+ tags: ["Accounts"],
2180
+ summary: "Connect an IMAP/SMTP account with a password",
2181
+ description: "The login is checked before anything is stored, for DAV too. Giving dav switches settings.calendarEnabled and contactsEnabled on for the halves found unless settings names them.",
2182
+ successStatus: 201
2183
+ }).errors({
2184
+ ...MAIL_SETTINGS_CHECK,
2185
+ ...AUTODISCOVERY_FAILED,
2186
+ ...DAV_SETTINGS_CHECK,
2187
+ ...PROVIDER_FAILURES,
2188
+ ...bodyStorage("settings.bodyStorage"),
2189
+ ...PLAN_LIMIT_ACCOUNTS,
2190
+ ...BILLING_PAST_DUE
2191
+ }).input(CreateImapAccount).output(Account)
2192
+ };
2193
+ var imapSettings = {
2194
+ update: base.route({
2195
+ method: "PATCH",
2196
+ path: "/v1/accounts/{accountId}/imap-settings",
2197
+ tags: ["Accounts"],
2198
+ summary: "Change an IMAP account's servers or password",
2199
+ description: "Present fields replace the stored ones and the result is checked like a new account. An account in reauth_required becomes active again. A stored password is never sent to a new server or tried for another login: changing the IMAP server (host, port, security or user) needs password in the same request, changing the SMTP server (host, port, security or user) needs smtpPassword (or password when SMTP uses the IMAP password), switching SMTP from its own password to the IMAP one (smtpPassword: null) needs password, and adding DAV settings or changing their URLs, user or password source needs the password DAV logs in with (dav.password, or password when DAV uses the IMAP password). Sending dav: null removes the calendar and contacts; a CalDAV or CardDAV half the account did not have before switches settings.calendarEnabled or contactsEnabled on, unless the account's settings already name that key (an earlier opt-out stays); dav fields left out keep their stored values, or are discovered for an account without DAV settings. An account that signs in with OAuth always uses its provider's servers."
2200
+ }).errors({
2201
+ ...ACCOUNT_NOT_FOUND,
2202
+ ...MAIL_SETTINGS_CHECK,
2203
+ ...DAV_SETTINGS_CHECK,
2204
+ ...PROVIDER_FAILURES,
2205
+ ...errors({
2206
+ not_imap_account: "The account reads mail through a provider's API, not IMAP.",
2207
+ password_required: "A server, login user or password source changes without its password.",
2208
+ password_not_stored: "The account has no stored password and none is sent.",
2209
+ dav_unsupported: "dav is given for an account that signs in with OAuth.",
2210
+ oauth_servers_fixed: "The servers of an account that signs in with OAuth are changed.",
2211
+ password_unsupported: "A password is sent for an account that signs in with OAuth.",
2212
+ account_reauth_required: "The OAuth account's stored grant no longer works, so its servers cannot be checked.",
2213
+ credential_missing: "The OAuth account has no stored credential.",
2214
+ credential_conflict: "The OAuth account's credential kept changing while it was refreshed.",
2215
+ invalid_client: "The provider refuses the OAuth account's client credentials.",
2216
+ token_exchange_failed: "The provider's token endpoint refuses the OAuth account's refresh.",
2217
+ provider_not_configured: "The platform OAuth app the account signed in with is gone.",
2218
+ oauth_app_secret_unreadable: "The client secret of the account's OAuth app cannot be decrypted; replace it."
2219
+ })
2220
+ }).input(AccountIdInput.extend(UpdateImapSettings.shape)).output(Account)
2221
+ };
2222
+ var autoconfig = {
2223
+ lookup: base.route({
2224
+ method: "GET",
2225
+ path: "/v1/autoconfig",
2226
+ tags: ["Accounts"],
2227
+ summary: "Find IMAP, SMTP and DAV settings for an address",
2228
+ description: "dav carries CalDAV and CardDAV context URLs when a source names them; they need the login to resolve to the account's home sets, which creating the account does."
2229
+ }).errors(errors({
2230
+ autodiscovery_failed: "No IMAP settings were found for the address; ask the user for the servers then."
2231
+ })).input(AutoconfigQuery).output(DiscoveredSettings)
2232
+ };
2233
+
2234
+ // ../contract/src/ai/routes.ts
2235
+ var ai = {
2236
+ draft: base.route({
2237
+ method: "POST",
2238
+ path: "/v1/ai/draft",
2239
+ tags: ["AI"],
2240
+ summary: "Draft email text with AI",
2241
+ description: "Writes an email body from instructions, optionally answering a message or reworking a draft, in plain text or Markdown. Nothing is sent or saved: the text is for a person to edit. Needs the write scope; the tenant may ask for AI_DRAFTS_PER_MINUTE drafts a minute (beyond that, rate_limited with Retry-After)."
2242
+ }).errors({
2243
+ ...planModule("AI"),
2244
+ ...accountState("The account of the message being answered"),
2245
+ ...PROVIDER_FAILURES,
2246
+ ...errors({
2247
+ ai_disabled: "The deployment has no AI gateway configured.",
2248
+ unknown_account: "context.accountId names an account the tenant does not have; data.ids lists it.",
2249
+ unknown_message: "context.replyToMessageId names a message the account does not have.",
2250
+ provider_not_found: "The provider no longer has the message being answered.",
2251
+ provider_forbidden: "The provider refused to read the message being answered.",
2252
+ ai_refused: "The model declines the instructions.",
2253
+ ai_unavailable: "The AI gateway fails or does not answer in time.",
2254
+ ai_suspended: "The AI gateway refuses the deployment's key or account, until an operator fixes it."
2255
+ })
2256
+ }).input(CreateAiDraft).output(AiDraft)
2257
+ };
2258
+
2259
+ // ../contract/src/audit-log/routes.ts
2260
+ var auditLog = {
2261
+ list: base.route({
2262
+ method: "GET",
2263
+ path: "/v1/audit-log",
2264
+ tags: ["Audit log"],
2265
+ summary: "Read the audit log",
2266
+ description: "Every mutating request on /v1 and from the board, newest first. Needs the admin scope. action matches exactly, or as a prefix when it ends with a dot (accounts.). outcome success is a 2xx status."
2267
+ }).errors({
2268
+ ...INVALID_CURSOR,
2269
+ ...errors({ invalid_range: "to is before from." })
2270
+ }).input(ListAuditLogQuery).output(AuditEntryPage)
2271
+ };
2272
+
2273
+ // ../contract/src/calendar/routes.ts
2274
+ import * as z21 from "zod";
2275
+ var CALENDAR_ACCOUNT = {
2276
+ ...planModule("calendar"),
2277
+ ...ACCOUNT_NOT_FOUND,
2278
+ ...errors({
2279
+ calendar_disabled: "The account's calendar sync is off, or it is an IMAP account without CalDAV settings."
2280
+ })
2281
+ };
2282
+ var CALENDAR_PROVIDER = {
2283
+ ...accountState(),
2284
+ ...PROVIDER_FAILURES,
2285
+ ...errors({
2286
+ provider_forbidden: "The provider refused the account access (e.g. the grant lacks calendar access).",
2287
+ provider_not_found: "The event or calendar is gone at the provider.",
2288
+ provider_unsupported: "The provider cannot express the request (e.g. a rule Microsoft has no pattern for), or an IMAP account whose CalDAV server does not schedule has no SMTP server to mail the iTIP message from.",
2289
+ provider_rejected: `The provider refused the request, or more than ${MAX_EVENT_ATTENDEES} attendees would be mailed an iTIP message.`
2290
+ })
2291
+ };
2292
+ var CALENDAR_WRITE = {
2293
+ ...CALENDAR_PROVIDER,
2294
+ ...errors({
2295
+ provider_conflict: "The event changed at the provider meanwhile.",
2296
+ calendar_read_only: "The account can only read the event's calendar."
2297
+ })
2298
+ };
2299
+ var EVENT_TIMES = errors({
2300
+ invalid_time_zone: "A timed start or end names an unknown time zone.",
2301
+ mixed_event_times: "One end is timed and the other all-day.",
2302
+ invalid_range: "end is not after start.",
2303
+ invalid_recurrence: "recurrence has no RRULE or RDATE line, or does not parse as RFC 5545."
2304
+ });
2305
+ var EVENT_NOT_FOUND = errors({
2306
+ calendar_event_not_found: "The event does not exist in the account's synced calendars."
2307
+ });
2308
+ var MEETING_SERVICE = errors({
2309
+ meeting_service_not_connected: "The account has no connection to the service and the tenant no server app it may use (a password IMAP account may not use one).",
2310
+ meeting_service_reauth_required: "The account's connection or the tenant's server app was refused and must be connected or fixed again.",
2311
+ meeting_recurrence_unsupported: "The service cannot repeat a meeting by the event's rule.",
2312
+ meeting_service_rejected: "The service refused the meeting.",
2313
+ meeting_service_unavailable: "The service could not be reached."
2314
+ });
2315
+ var calendars = {
2316
+ list: base.route({
2317
+ method: "GET",
2318
+ path: "/v1/accounts/{accountId}/calendars",
2319
+ tags: ["Calendar"],
2320
+ summary: "List calendars"
2321
+ }).errors(CALENDAR_ACCOUNT).input(AccountIdInput).output(CalendarList)
2322
+ };
2323
+ var calendarEvents = {
2324
+ list: base.route({
2325
+ method: "GET",
2326
+ path: "/v1/accounts/{accountId}/calendar-events",
2327
+ tags: ["Calendar"],
2328
+ summary: "List events in a time range, by start",
2329
+ description: "Returns singles, occurrences and exceptions overlapping [start, end), not series definitions. Only the sync window (by default 90 days back to 365 days ahead) is indexed."
2330
+ }).errors({
2331
+ ...CALENDAR_ACCOUNT,
2332
+ ...INVALID_CURSOR,
2333
+ ...errors({
2334
+ invalid_range: `end is not after start, or the range spans more than ${MAX_EVENT_RANGE_DAYS} days.`,
2335
+ unknown_calendar: "calendarId names no synced calendar of the account."
2336
+ })
2337
+ }).input(ListCalendarEventsInput).output(CalendarEventPage),
2338
+ get: base.route({
2339
+ method: "GET",
2340
+ path: "/v1/accounts/{accountId}/calendar-events/{eventId}",
2341
+ tags: ["Calendar"],
2342
+ summary: "Get an event with its body",
2343
+ description: "Works for every kind, including a series (its recurrence rule)."
2344
+ }).errors({ ...CALENDAR_ACCOUNT, ...EVENT_NOT_FOUND, ...CALENDAR_PROVIDER }).input(CalendarEventIdInput).output(CalendarEventDetail),
2345
+ create: base.route({
2346
+ method: "POST",
2347
+ path: "/v1/accounts/{accountId}/calendar-events",
2348
+ tags: ["Calendar"],
2349
+ summary: "Create an event or a recurring series",
2350
+ description: "onlineMeeting asks the provider for a Teams (Microsoft) or Meet (Google) link; onlineMeetingUrl attaches a link from another service (Zoom, Webex, ...), read back as onlineMeetingUrl. On Microsoft that link is the location's link (location defaults to the URL) and a join line is added to a body sent along. onlineMeetingService has workwae create a Zoom or Webex meeting (recurring for a series) hosted by the account's own grant at the service, else by the tenant's server app as the service user with the mailbox's address, and attaches its join link the same way. The server app hosts only accounts whose address the provider verified (Gmail, Microsoft, IMAP signed in with OAuth): a password IMAP account's address is whatever was typed, so it needs its own grant. The meeting is created only once the account is active, and deleted again if anything fails before its link is saved. On an IMAP account whose CalDAV server does not schedule, the invitation is mailed from the account's SMTP server when notify is true and there are attendees; a missing SMTP server is refused before anything is created, and a send that fails after the event is created is logged and the event stands.",
2351
+ successStatus: 201
2352
+ }).errors({
2353
+ ...CALENDAR_ACCOUNT,
2354
+ ...CALENDAR_WRITE,
2355
+ ...EVENT_TIMES,
2356
+ ...MEETING_SERVICE,
2357
+ ...errors({
2358
+ plan_module_unavailable: "The tenant's plan does not include calendar, or onlineMeetingService is given and it does not include meetings.",
2359
+ calendar_not_found: "calendarId names no synced calendar of the account.",
2360
+ calendar_event_not_found: "The provider no longer has the event when it is read back.",
2361
+ multiple_meeting_options: "More than one of onlineMeeting, onlineMeetingUrl and onlineMeetingService is given.",
2362
+ meeting_host_missing: "The tenant's server app has no user with the mailbox's address."
2363
+ })
2364
+ }).input(CreateCalendarEventInput).output(CalendarEventDetail),
2365
+ update: base.route({
2366
+ method: "PATCH",
2367
+ path: "/v1/accounts/{accountId}/calendar-events/{eventId}",
2368
+ tags: ["Calendar"],
2369
+ summary: "Change an event, one occurrence, or a whole series",
2370
+ description: "Addressing an occurrence changes only that instance; addressing the series changes every occurrence. onlineMeetingUrl replaces a link from another service (as on create); null removes it (on Google, whatever conference the event has; on Microsoft, the location's link, leaving a Teams meeting alone). For an event whose Zoom or Webex meeting workwae created, a new subject, start, end or recurrence moves that meeting too, and an onlineMeetingUrl other than its own join link deletes it (sending that link back replaces nothing); changing one occurrence leaves the meeting's series as it is. A missing or refused meeting connection is reported before anything changes; a service failure after the event changed is reported with the event changed (repeat the request). On an IMAP account whose CalDAV server does not schedule, an update of a meeting the account organizes is mailed from its SMTP server when it changes start, end, recurrence, location or attendees, to the attendees workwae has indexed for it and those the request names; a missing SMTP server or too many attendees is refused before anything changes, and a send that fails after the change is logged and the change stands."
2371
+ }).errors({
2372
+ ...CALENDAR_ACCOUNT,
2373
+ ...EVENT_NOT_FOUND,
2374
+ ...CALENDAR_WRITE,
2375
+ ...EVENT_TIMES,
2376
+ ...MEETING_SERVICE,
2377
+ ...errors({
2378
+ recurrence_on_instance: "recurrence is given for an occurrence or exception.",
2379
+ meeting_deleted: "The event's meeting is gone at the service when it is moved."
2380
+ })
2381
+ }).input(CalendarEventIdInput.extend(UpdateCalendarEvent.shape)).output(CalendarEventDetail),
2382
+ delete: base.route({
2383
+ method: "DELETE",
2384
+ path: "/v1/accounts/{accountId}/calendar-events/{eventId}",
2385
+ tags: ["Calendar"],
2386
+ summary: "Delete an event, one occurrence, or a whole series",
2387
+ description: "Cancels the meeting for its attendees when the account organizes it. A Zoom or Webex meeting workwae created for the event (a series, not one occurrence) is deleted after the provider event: a provider refusal leaves both, and once the event is gone a meeting the service cannot delete (connection gone or refused, service unreachable) is logged and stays at the service while the request still succeeds. On an IMAP account whose CalDAV server does not schedule, the cancellation is mailed from the account's SMTP server to the attendees workwae has indexed; a missing SMTP server or too many attendees is refused before anything is deleted, and a send that fails after the deletion is logged.",
2388
+ successStatus: 204,
2389
+ inputStructure: "detailed"
2390
+ }).errors({ ...CALENDAR_ACCOUNT, ...EVENT_NOT_FOUND, ...CALENDAR_WRITE }).input(DeleteCalendarEventInput).output(z21.void()),
2391
+ respond: base.route({
2392
+ method: "POST",
2393
+ path: "/v1/accounts/{accountId}/calendar-events/{eventId}/respond",
2394
+ tags: ["Calendar"],
2395
+ summary: "Accept, tentatively accept or decline an invitation",
2396
+ description: "Addressing an occurrence answers that instance; the series answers all of them. On an IMAP account whose CalDAV server does not schedule, the answer is mailed to the organizer from the account's SMTP server; a missing SMTP server is refused before anything changes, and a send that fails after the answer is recorded is logged."
2397
+ }).errors({
2398
+ ...CALENDAR_ACCOUNT,
2399
+ ...EVENT_NOT_FOUND,
2400
+ ...CALENDAR_PROVIDER,
2401
+ ...errors({
2402
+ provider_conflict: "The event changed at the provider meanwhile.",
2403
+ organizer_cannot_respond: "The account organizes the event, so there is no invitation to answer."
2404
+ })
2405
+ }).input(CalendarEventIdInput.extend(RespondToCalendarEventRequest.shape)).output(CalendarEvent)
2406
+ };
2407
+ var availability = {
2408
+ find: base.route({
2409
+ method: "POST",
2410
+ path: "/v1/accounts/{accountId}/availability/find",
2411
+ tags: ["Calendar"],
2412
+ summary: "Find busy times and common free slots",
2413
+ description: "A read sent as a POST, the question not fitting a URL. One provider request covers every attendee (Microsoft getSchedule, Google freeBusy.query); the answer is cached briefly per account, attendees and range."
2414
+ }).errors({
2415
+ ...CALENDAR_ACCOUNT,
2416
+ ...CALENDAR_PROVIDER,
2417
+ ...errors({
2418
+ invalid_range: `end is not after start, or the range spans more than ${MAX_AVAILABILITY_DAYS} days.`,
2419
+ invalid_time_zone: "timeZone is unknown.",
2420
+ invalid_working_hours: "workingHours does not end after it starts."
2421
+ })
2422
+ }).input(FindAvailabilityInput).output(Availability)
2423
+ };
2424
+
2425
+ // ../contract/src/connect/routes.ts
2426
+ var CONNECT_REQUEST = errors({
2427
+ redirect_uri_not_allowed: "redirectUri is not on the tenant allowlist (settings.redirectUris).",
2428
+ billing_past_due: "accountId is not given and the tenant's payment is past due beyond the grace period.",
2429
+ unknown_account: "accountId names no live account of the tenant; data.ids names it.",
2430
+ org_connection_account: "accountId names an account that authenticates through an organization connection (test the connection to restore it).",
2431
+ shared_mailbox_account: "accountId names a shared mailbox (reconnect the account that opened it)."
2432
+ });
2433
+ var CONNECT_SESSION_NOT_FOUND = errors({
2434
+ connect_session_not_found: "No session has this id (or token)."
2435
+ });
2436
+ var HOSTED_IMAP_SESSION = {
2437
+ ...CONNECT_SESSION_NOT_FOUND,
2438
+ ...errors({
2439
+ session_used: "The session already completed or failed.",
2440
+ session_expired: "The session is past expiresAt.",
2441
+ redirect_uri_not_allowed: "The session's return address is no longer on the tenant allowlist.",
2442
+ provider_not_allowed: "The session does not offer the IMAP form."
2443
+ })
2444
+ };
2445
+ var connectSessions = {
2446
+ create: base.route({
2447
+ method: "POST",
2448
+ path: "/v1/connect-sessions",
2449
+ tags: ["Connect"],
2450
+ summary: "Create a hosted connect session",
2451
+ description: "Returns a link to the hosted connect page. Its OAuth sign-ins pass a Continue page on the API origin naming the tenant and provider, and must finish in the browser that pressed Continue (an HttpOnly cookie on the API origin); a callback in any other browser renders an error page and connects nothing. Without redirectUri the owner ends on workwae's own confirmation page, which needs no allowlist entry; follow the outcome with GET /v1/connect-sessions/{sessionId} or the account.connected event. A failed sign-in returns with status=error and error set to a ConnectErrorCode.",
2452
+ successStatus: 201
2453
+ }).errors({
2454
+ ...CONNECT_REQUEST,
2455
+ ...bodyStorage("options.bodyStorage"),
2456
+ ...errors({
2457
+ unknown_oauth_app: "oauthAppIds names no OAuth app of the tenant.",
2458
+ oauth_app_mismatch: "An OAuth app in oauthAppIds is for the other provider.",
2459
+ account_mismatch: "providers leaves out the sign-in the account reconnects with.",
2460
+ google_imap_requires_own_app: "providers includes google with options.mailAccess imap (given, or kept by the account being reconnected) and no Google app (oauthAppIds, or the reconnected account's own): Google over IMAP needs the tenant's own Google OAuth app."
2461
+ })
2462
+ }).input(CreateConnectSession).output(SessionLink),
2463
+ get: base.route({
2464
+ method: "GET",
2465
+ path: "/v1/connect-sessions/{sessionId}",
2466
+ tags: ["Connect"],
2467
+ summary: "Read a hosted connect session's outcome"
2468
+ }).errors(CONNECT_SESSION_NOT_FOUND).input(ConnectSessionIdInput).output(ConnectSession)
2469
+ };
2470
+ var oauthAuthorizations = {
2471
+ create: base.route({
2472
+ method: "POST",
2473
+ path: "/v1/oauth-authorizations",
2474
+ tags: ["Connect"],
2475
+ summary: "Start a headless OAuth connect",
2476
+ description: "authorizeUrl is a page on the API origin naming the tenant and the provider; only its Continue button (a form POST, accepted only from the API or board origin) leads on to the provider. Continuing works once and binds the sign-in to that browser with an HttpOnly cookie scoped to the callback; a callback arriving in any other browser renders an error page and connects nothing, so send the user's own browser to it. A failed sign-in returns with status=error and error set to a ConnectErrorCode."
2477
+ }).errors({
2478
+ ...CONNECT_REQUEST,
2479
+ ...bodyStorage("options.bodyStorage"),
2480
+ ...planModule("shared mailboxes (options.sharedMailboxesEnabled: true)"),
2481
+ ...OAUTH_CLIENT,
2482
+ ...errors({
2483
+ account_mismatch: "The account given as accountId signs in with another provider, or with a password.",
2484
+ google_imap_requires_own_app: "provider google with options.mailAccess imap (given, or kept by the account being reconnected) and no Google app (oauthAppId, or the reconnected account's own): Google over IMAP needs the tenant's own Google OAuth app."
2485
+ })
2486
+ }).input(StartOAuthRequest).output(OAuthAuthorization)
2487
+ };
2488
+ var HOSTED_TAGS = ["Hosted pages"];
2489
+ var hostedConnectSessions = {
2490
+ get: base.route({
2491
+ method: "GET",
2492
+ path: "/v1/hosted/connect-sessions/{token}",
2493
+ tags: HOSTED_TAGS,
2494
+ summary: "Read a hosted connect session",
2495
+ spec: publicOperation
2496
+ }).errors(CONNECT_SESSION_NOT_FOUND).input(SessionTokenInput).output(ConnectSessionPublic),
2497
+ createImapAccount: base.route({
2498
+ method: "POST",
2499
+ path: "/v1/hosted/connect-sessions/{token}/imap-accounts",
2500
+ tags: HOSTED_TAGS,
2501
+ summary: "Connect an IMAP account from the hosted page",
2502
+ description: "When the tenant's plan allows no more accounts, or its payment is past due beyond the grace period, the session fails and redirectUrl carries status=error&error=plan_limit_accounts (or billing_past_due), as the OAuth callback does. Giving dav switches settings.calendarEnabled and contactsEnabled on for the halves found unless the session's options name them, as POST /v1/imap-accounts does. A reconnect session replaces the password only: the account keeps its stored servers and DAV settings, and any sent here are ignored. Its DAV login is checked again: with the new password when DAV uses the IMAP password, otherwise with dav.password when given (which then replaces the stored DAV password) or the stored one.",
2503
+ spec: publicOperation
2504
+ }).errors({
2505
+ ...HOSTED_IMAP_SESSION,
2506
+ ...MAIL_SETTINGS_CHECK,
2507
+ ...AUTODISCOVERY_FAILED,
2508
+ ...DAV_SETTINGS_CHECK,
2509
+ ...PROVIDER_FAILURES,
2510
+ ...errors({
2511
+ account_exists: "The tenant already has a live IMAP account with this address and the session was not created to reconnect it (accountId).",
2512
+ account_not_found: "The account a reconnect session was created for was deleted.",
2513
+ account_mismatch: "A reconnect session's account has another address, or is not an IMAP account.",
2514
+ password_required: "A reconnect session's DAV has a password of its own, none is stored and dav.password is left out."
2515
+ })
2516
+ }).input(SessionTokenInput.extend(ImapAccountInput.shape)).output(HostedImapResult),
2517
+ autoconfig: base.route({
2518
+ method: "GET",
2519
+ path: "/v1/hosted/connect-sessions/{token}/autoconfig",
2520
+ tags: HOSTED_TAGS,
2521
+ summary: "Find IMAP and SMTP settings from the hosted page",
2522
+ spec: publicOperation
2523
+ }).errors({
2524
+ ...HOSTED_IMAP_SESSION,
2525
+ ...errors({ autodiscovery_failed: "No IMAP settings were found for the address." })
2526
+ }).input(SessionTokenInput.extend(AutoconfigQuery.shape)).output(DiscoveredSettings)
2527
+ };
2528
+
2529
+ // ../contract/src/contacts/routes.ts
2530
+ import * as z22 from "zod";
2531
+ var CONTACTS_ACCOUNT = {
2532
+ ...planModule("contacts"),
2533
+ ...ACCOUNT_NOT_FOUND,
2534
+ ...errors({
2535
+ contacts_disabled: "The account's contacts sync is off, or it is an IMAP account without CardDAV settings."
2536
+ })
2537
+ };
2538
+ var CONTACT_NOT_FOUND = errors({ contact_not_found: "The contact does not exist." });
2539
+ var CONTACTS_PROVIDER = {
2540
+ ...accountState(),
2541
+ ...PROVIDER_FAILURES,
2542
+ ...errors({
2543
+ provider_forbidden: "The provider refused the account access (e.g. the grant lacks contacts access).",
2544
+ provider_not_found: "The contact is gone at the provider (the index drops it).",
2545
+ provider_conflict: "The contact changed at the provider meanwhile.",
2546
+ provider_rejected: "The provider refused the request."
2547
+ })
2548
+ };
2549
+ var CONTACT_WRITE = {
2550
+ ...CONTACTS_PROVIDER,
2551
+ ...errors({
2552
+ contact_unidentifiable: "The contact would have no name, organization, email address or phone number.",
2553
+ provider_unsupported: 'The provider cannot store what was given (a folder on Google, or on Microsoft 365 more than 3 email addresses, more than one mobile number or an "other" number).'
2554
+ })
2555
+ };
2556
+ var contactFolders = {
2557
+ list: base.route({
2558
+ method: "GET",
2559
+ path: "/v1/accounts/{accountId}/contact-folders",
2560
+ tags: ["Contacts"],
2561
+ summary: "List contact folders",
2562
+ description: "Microsoft 365 contact folders, the default folder first; empty for Google accounts."
2563
+ }).errors(CONTACTS_ACCOUNT).input(AccountIdInput).output(ContactFolderList)
2564
+ };
2565
+ var contacts = {
2566
+ list: base.route({
2567
+ method: "GET",
2568
+ path: "/v1/accounts/{accountId}/contacts",
2569
+ tags: ["Contacts"],
2570
+ summary: "List or search contacts, by name",
2571
+ description: "Reads the index. q matches part of any name, the organization or any email address, case-insensitively."
2572
+ }).errors({
2573
+ ...CONTACTS_ACCOUNT,
2574
+ ...INVALID_CURSOR,
2575
+ ...errors({ unknown_contact_folder: "folderId names no contact folder of the account." })
2576
+ }).input(AccountIdInput.extend(ListContactsQuery.shape)).output(ContactPage),
2577
+ get: base.route({
2578
+ method: "GET",
2579
+ path: "/v1/accounts/{accountId}/contacts/{contactId}",
2580
+ tags: ["Contacts"],
2581
+ summary: "Get a contact",
2582
+ description: "Reads the index."
2583
+ }).errors({ ...CONTACTS_ACCOUNT, ...CONTACT_NOT_FOUND }).input(ContactIdInput).output(Contact),
2584
+ photo: base.route({
2585
+ method: "GET",
2586
+ path: "/v1/accounts/{accountId}/contacts/{contactId}/photo",
2587
+ tags: ["Contacts"],
2588
+ summary: "Download a contact's photo",
2589
+ description: "Fetched from the provider on first request and cached until the photo changes.",
2590
+ spec: fileResponse({
2591
+ mediaType: "image/*",
2592
+ description: "The photo as the provider stores it (image/jpeg, image/png, ...)",
2593
+ disposition: "inline"
2594
+ })
2595
+ }).errors({
2596
+ ...CONTACTS_ACCOUNT,
2597
+ ...CONTACT_NOT_FOUND,
2598
+ ...CONTACTS_PROVIDER,
2599
+ ...errors({ photo_not_found: "The contact has no photo." })
2600
+ }).input(ContactIdInput).output(z22.file()),
2601
+ create: base.route({
2602
+ method: "POST",
2603
+ path: "/v1/accounts/{accountId}/contacts",
2604
+ tags: ["Contacts"],
2605
+ summary: "Create a contact",
2606
+ description: "Written to the provider, then to the index, so it is listed at once.",
2607
+ successStatus: 201
2608
+ }).errors({
2609
+ ...CONTACTS_ACCOUNT,
2610
+ ...CONTACT_WRITE,
2611
+ ...errors({
2612
+ unknown_contact_folder: "folderId names no contact folder of the account.",
2613
+ contact_not_found: "The provider's answer could not be read back into the index."
2614
+ })
2615
+ }).input(CreateContactInput).output(Contact),
2616
+ update: base.route({
2617
+ method: "PATCH",
2618
+ path: "/v1/accounts/{accountId}/contacts/{contactId}",
2619
+ tags: ["Contacts"],
2620
+ summary: "Change a contact",
2621
+ description: "Fields present change (null clears); emails and phones replace the whole list."
2622
+ }).errors({ ...CONTACTS_ACCOUNT, ...CONTACT_NOT_FOUND, ...CONTACT_WRITE }).input(ContactIdInput.extend(UpdateContact.shape)).output(Contact),
2623
+ delete: base.route({
2624
+ method: "DELETE",
2625
+ path: "/v1/accounts/{accountId}/contacts/{contactId}",
2626
+ tags: ["Contacts"],
2627
+ summary: "Delete a contact",
2628
+ successStatus: 204
2629
+ }).errors({ ...CONTACTS_ACCOUNT, ...CONTACT_NOT_FOUND, ...CONTACTS_PROVIDER }).input(ContactIdInput).output(z22.void())
2630
+ };
2631
+
2632
+ // ../contract/src/events/routes.ts
2633
+ var changes = {
2634
+ list: base.route({
2635
+ method: "GET",
2636
+ path: "/v1/changes",
2637
+ tags: ["Changes"],
2638
+ summary: "Read the change feed",
2639
+ description: "Events oldest first. Pass nextCursor back as cursor to continue; it is never null, since the feed has no end, only a present: an empty page means caught up, so poll again from the same cursor later."
2640
+ }).errors(INVALID_CURSOR).input(ListChangesQuery).output(EventFeedPage)
2641
+ };
2642
+
2643
+ // ../contract/src/exports/routes.ts
2644
+ import * as z23 from "zod";
2645
+ var EXPORT_NOT_FOUND = errors({ export_not_found: "The export does not exist." });
2646
+ var exports = {
2647
+ create: base.route({
2648
+ method: "POST",
2649
+ path: "/v1/exports",
2650
+ tags: ["Exports"],
2651
+ summary: "Export the tenant or one account",
2652
+ description: `Builds a gzip-compressed tar in the background: accounts without secrets, mailboxes, message metadata (JSON lines), stored message bodies as .eml, calendars and events as JSON and .ics, and for a tenant export its webhooks (without signing secrets), templates and settings. Follow it with GET /v1/exports/{exportId}, or the export.completed and export.failed events. Requires the admin scope.`,
2653
+ successStatus: 202
2654
+ }).errors(errors({
2655
+ storage_unavailable: "No object store is configured.",
2656
+ unknown_account: "accountId names an account the tenant does not have; data.ids lists it.",
2657
+ export_in_progress: "An export of the same scope (the tenant, or the same account) is queued or running."
2658
+ })).input(CreateDataExport).output(DataExport),
2659
+ list: base.route({
2660
+ method: "GET",
2661
+ path: "/v1/exports",
2662
+ tags: ["Exports"],
2663
+ summary: "List exports, newest first",
2664
+ description: "Requires the admin scope."
2665
+ }).errors(INVALID_CURSOR).input(ListDataExportsQuery).output(DataExportPage),
2666
+ get: base.route({
2667
+ method: "GET",
2668
+ path: "/v1/exports/{exportId}",
2669
+ tags: ["Exports"],
2670
+ summary: "Get an export",
2671
+ description: "Requires the admin scope."
2672
+ }).errors(EXPORT_NOT_FOUND).input(DataExportIdInput).output(DataExport),
2673
+ download: base.route({
2674
+ method: "GET",
2675
+ path: "/v1/exports/{exportId}/download",
2676
+ tags: ["Exports"],
2677
+ summary: "Download an export (application/gzip)",
2678
+ description: `Streams the decrypted .tar.gz; compare it with the export's sha256. Requires the admin scope.`,
2679
+ spec: fileResponse({
2680
+ mediaType: "application/gzip",
2681
+ description: "The export as a gzip-compressed tar",
2682
+ disposition: "attachment",
2683
+ headers: {
2684
+ "x-export-sha256": {
2685
+ description: "sha256 hex of the body, the export's sha256",
2686
+ schema: { type: "string" }
2687
+ }
2688
+ }
2689
+ })
2690
+ }).errors({
2691
+ ...EXPORT_NOT_FOUND,
2692
+ ...errors({
2693
+ export_not_ready: "The export is queued or running.",
2694
+ export_failed: "The export failed.",
2695
+ export_expired: "The export's file was deleted.",
2696
+ storage_unavailable: "No object store is configured.",
2697
+ export_corrupt: "A stored part of the export fails its integrity check."
2698
+ })
2699
+ }).input(DataExportIdInput).output(StreamedFile),
2700
+ delete: base.route({
2701
+ method: "DELETE",
2702
+ path: "/v1/exports/{exportId}",
2703
+ tags: ["Exports"],
2704
+ summary: "Delete an export and its file",
2705
+ description: "Requires the admin scope.",
2706
+ successStatus: 204
2707
+ }).errors({ ...EXPORT_NOT_FOUND, ...errors({ export_running: "The export is being built." }) }).input(DataExportIdInput).output(z23.void())
2708
+ };
2709
+
2710
+ // ../contract/src/identities/routes.ts
2711
+ import * as z24 from "zod";
2712
+ var IDENTITY_NOT_FOUND = errors({ identity_not_found: "The identity does not exist." });
2713
+ var identities = {
2714
+ list: base.route({
2715
+ method: "GET",
2716
+ path: "/v1/accounts/{accountId}/identities",
2717
+ tags: ["Identities"],
2718
+ summary: "List the addresses the account may send as",
2719
+ description: "The account's own address, the provider's (Gmail send-as addresses with their Gmail signatures in one users.settings.sendAs request; Microsoft proxy addresses from /me) and custom ones. The provider's list is read when the cached one is a day old, or with refresh=true at most once a minute; a paused or disconnected account serves the cached list."
2720
+ }).errors({
2721
+ ...ACCOUNT_NOT_FOUND,
2722
+ ...errors({
2723
+ account_reauth_required: "Reading the provider's list found that the account must be reconnected."
2724
+ }),
2725
+ ...PROVIDER_FAILURES
2726
+ }).input(AccountIdInput.extend(ListIdentitiesQuery.shape)).output(IdentityList),
2727
+ create: base.route({
2728
+ method: "POST",
2729
+ path: "/v1/accounts/{accountId}/identities",
2730
+ tags: ["Identities"],
2731
+ summary: "Add a custom identity",
2732
+ description: "For IMAP aliases, and Microsoft mailboxes the account has Send As permission on (Graph cannot list those). Sending as it is checked by the provider when the message goes out.",
2733
+ successStatus: 201
2734
+ }).errors({
2735
+ ...ACCOUNT_NOT_FOUND,
2736
+ ...errors({
2737
+ identities_managed_by_provider: "The account is a Gmail account (add send-as addresses in Gmail).",
2738
+ identity_exists: "The address is already an identity of the account."
2739
+ })
2740
+ }).input(AccountIdInput.extend(CreateIdentity.shape)).output(Identity),
2741
+ update: base.route({
2742
+ method: "PATCH",
2743
+ path: "/v1/accounts/{accountId}/identities/{identityId}",
2744
+ tags: ["Identities"],
2745
+ summary: "Change an identity's signature or name",
2746
+ description: "signature: null removes ours (Gmail's own then applies again). Only custom identities take a name; the others carry the provider's."
2747
+ }).errors({
2748
+ ...ACCOUNT_NOT_FOUND,
2749
+ ...IDENTITY_NOT_FOUND,
2750
+ ...errors({
2751
+ identity_not_custom: "A name is given for a primary or provider identity."
2752
+ })
2753
+ }).input(IdentityIdInput.extend(UpdateIdentity.shape)).output(Identity),
2754
+ delete: base.route({
2755
+ method: "DELETE",
2756
+ path: "/v1/accounts/{accountId}/identities/{identityId}",
2757
+ tags: ["Identities"],
2758
+ summary: "Remove a custom identity",
2759
+ successStatus: 204
2760
+ }).errors({
2761
+ ...ACCOUNT_NOT_FOUND,
2762
+ ...IDENTITY_NOT_FOUND,
2763
+ ...errors({
2764
+ identity_not_custom: "The identity is the primary or a provider identity (those follow the provider)."
2765
+ })
2766
+ }).input(IdentityIdInput).output(z24.void())
2767
+ };
2768
+ var mailSettings = {
2769
+ get: base.route({
2770
+ method: "GET",
2771
+ path: "/v1/accounts/{accountId}/mail-settings",
2772
+ tags: ["Identities"],
2773
+ summary: "Get the account's mail settings"
2774
+ }).errors(ACCOUNT_NOT_FOUND).input(AccountIdInput).output(MailSettings),
2775
+ update: base.route({
2776
+ method: "PATCH",
2777
+ path: "/v1/accounts/{accountId}/mail-settings",
2778
+ tags: ["Identities"],
2779
+ summary: "Update the account's mail settings"
2780
+ }).errors(ACCOUNT_NOT_FOUND).input(AccountIdInput.extend(UpdateMailSettings.shape)).output(MailSettings)
2781
+ };
2782
+
2783
+ // ../contract/src/mail/routes.ts
2784
+ import * as z25 from "zod";
2785
+ var UNKNOWN_MAILBOX = errors({
2786
+ unknown_mailbox: "A mailbox id names no live mailbox of the account; data.ids lists them."
2787
+ });
2788
+ var PROVIDER_CHANGE = {
2789
+ ...PROVIDER_FAILURES,
2790
+ ...errors({
2791
+ provider_not_found: "The provider no longer has the item.",
2792
+ provider_forbidden: "The provider refused the change for lack of permission.",
2793
+ provider_rejected: "The provider rejected the change as invalid.",
2794
+ provider_unsupported: "The provider does not support the change.",
2795
+ provider_conflict: "The provider refused the change because of the item's state."
2796
+ })
2797
+ };
2798
+ var MESSAGE_READ = {
2799
+ ...ACCOUNT_NOT_FOUND,
2800
+ ...errors({ message_not_found: "The message does not exist." }),
2801
+ ...accountState("The provider is needed and the account"),
2802
+ ...PROVIDER_FAILURES,
2803
+ ...errors({
2804
+ provider_not_found: "The provider no longer has the message or the attachment.",
2805
+ provider_forbidden: "The provider refused to serve the message."
2806
+ })
2807
+ };
2808
+ var mailboxes = {
2809
+ list: base.route({
2810
+ method: "GET",
2811
+ path: "/v1/accounts/{accountId}/mailboxes",
2812
+ tags: ["Mail"],
2813
+ summary: "List mailboxes (folders or labels)"
2814
+ }).errors(ACCOUNT_NOT_FOUND).input(AccountIdInput).output(MailboxList)
2815
+ };
2816
+ var messages = {
2817
+ list: base.route({
2818
+ method: "GET",
2819
+ path: "/v1/accounts/{accountId}/messages",
2820
+ tags: ["Mail"],
2821
+ summary: "List messages, newest first",
2822
+ description: "Covers the account's sync window. Without mailboxId, lists every mailbox. importance keeps the messages with that priority; messages indexed before priorities were read (importance null) match none."
2823
+ }).errors({ ...ACCOUNT_NOT_FOUND, ...INVALID_CURSOR, ...UNKNOWN_MAILBOX }).input(AccountIdInput.extend(ListMessagesQuery.shape)).output(MessageSummaryPage),
2824
+ get: base.route({
2825
+ method: "GET",
2826
+ path: "/v1/accounts/{accountId}/messages/{messageId}",
2827
+ tags: ["Mail"],
2828
+ summary: "Get a message with its body",
2829
+ description: 'With bodyStorage cache or full the body comes from stored content (cache fetches and stores the raw message on a miss), and attachment ids are MIME part numbers ("1.2"); otherwise it is read from the provider. Drafts are always read from the provider.'
2830
+ }).errors(MESSAGE_READ).input(MessageIdInput).output(Message),
2831
+ raw: base.route({
2832
+ method: "GET",
2833
+ path: "/v1/accounts/{accountId}/messages/{messageId}/raw",
2834
+ tags: ["Mail"],
2835
+ summary: "Download the message as RFC 5322 (message/rfc822)",
2836
+ description: "Served from stored content when the account stores bodies.",
2837
+ spec: fileResponse({
2838
+ mediaType: "message/rfc822",
2839
+ description: "The message as message/rfc822",
2840
+ disposition: "attachment"
2841
+ })
2842
+ }).errors(MESSAGE_READ).input(MessageIdInput).output(StreamedFile),
2843
+ attachment: base.route({
2844
+ method: "GET",
2845
+ path: "/v1/accounts/{accountId}/messages/{messageId}/attachments/{attachmentId}",
2846
+ tags: ["Mail"],
2847
+ summary: "Download an attachment",
2848
+ description: "Served from stored content when the account stores bodies; an id the stored message does not have is asked of the provider, which answers provider_not_found when it has no such attachment either.",
2849
+ spec: fileResponse({
2850
+ mediaType: "*/*",
2851
+ description: "The attachment, with the Content-Type it was sent with",
2852
+ disposition: "attachment"
2853
+ })
2854
+ }).errors(MESSAGE_READ).input(AttachmentIdInput).output(StreamedFile),
2855
+ update: base.route({
2856
+ method: "PATCH",
2857
+ path: "/v1/accounts/{accountId}/messages/{messageId}",
2858
+ tags: ["Mail"],
2859
+ summary: "Change flags, move, or change labels",
2860
+ description: "mailboxId moves the message (Microsoft folders, or Gmail: replaces the message's folder-like labels). addMailboxIds/removeMailboxIds apply Gmail labels."
2861
+ }).errors({
2862
+ ...ACCOUNT_NOT_FOUND,
2863
+ ...errors({
2864
+ message_not_found: "The message does not exist.",
2865
+ mailbox_not_label: "addMailboxIds or removeMailboxIds names a mailbox that is not a label."
2866
+ }),
2867
+ ...UNKNOWN_MAILBOX,
2868
+ ...accountState(),
2869
+ ...PROVIDER_CHANGE
2870
+ }).input(MessageIdInput.extend(UpdateMessage.shape)).output(MessageSummary),
2871
+ delete: base.route({
2872
+ method: "DELETE",
2873
+ path: "/v1/accounts/{accountId}/messages/{messageId}",
2874
+ tags: ["Mail"],
2875
+ summary: "Delete a message",
2876
+ description: "Moves it to Trash; ?permanent=true deletes it outright.",
2877
+ successStatus: 204,
2878
+ inputStructure: "detailed"
2879
+ }).errors({
2880
+ ...ACCOUNT_NOT_FOUND,
2881
+ ...errors({ message_not_found: "The message does not exist." }),
2882
+ ...accountState(),
2883
+ ...PROVIDER_CHANGE
2884
+ }).input(DeleteMessageInput).output(z25.void())
2885
+ };
2886
+ var search = {
2887
+ messages: base.route({
2888
+ method: "GET",
2889
+ path: "/v1/accounts/{accountId}/search/messages",
2890
+ tags: ["Mail"],
2891
+ summary: "Search messages",
2892
+ description: [
2893
+ 'source=index searches the index with full-text search (q in web search syntax: words, "phrases", OR, -word) over subject, sender and recipient names and addresses, the snippet, and the body text of stored messages (bodyStorage cache or full), best matches first.',
2894
+ "source=provider passes q to the provider unchanged (Gmail search syntax, Microsoft KQL, IMAP SEARCH TEXT), newest first; results the index holds carry their id, others id null.",
2895
+ "source=auto (default) asks the provider when the index cannot cover the request: `after` lies before the sync window, or the index found nothing while it does not hold everything (the account has a sync window, or bodyStorage is not full). Otherwise, and whenever the provider cannot be asked (inactive account, rate limit, provider error), the index answers.",
2896
+ "The response names the source that answered; nextCursor stays with it.",
2897
+ "A provider search of a Gmail account counts against the account's Gmail searches per minute; past them it answers rate_limited with Retry-After."
2898
+ ].join(" ")
2899
+ }).errors({
2900
+ ...ACCOUNT_NOT_FOUND,
2901
+ ...errors({ invalid_range: "before is not after after." }),
2902
+ ...errors({
2903
+ invalid_cursor: "The cursor is invalid, was issued for another account, or is a provider cursor older than an hour."
2904
+ }),
2905
+ ...UNKNOWN_MAILBOX,
2906
+ ...accountState("With source=provider, the account"),
2907
+ ...PROVIDER_FAILURES,
2908
+ ...errors({
2909
+ provider_forbidden: "With source=provider, the provider refused the search.",
2910
+ provider_rejected: "With source=provider, the provider rejected q.",
2911
+ provider_unsupported: "With source=provider, the provider cannot run the search."
2912
+ })
2913
+ }).input(withDateRange(AccountIdInput.extend(SearchMessagesQuery.shape), {
2914
+ from: "after",
2915
+ to: "before"
2916
+ })).output(SearchPage)
2917
+ };
2918
+
2919
+ // ../contract/src/meetings/routes.ts
2920
+ import * as z26 from "zod";
2921
+ var APP_TAGS = ["Meeting apps"];
2922
+ var CONNECTION_TAGS = ["Meeting connections"];
2923
+ var MEETINGS = planModule("Zoom and Webex meetings");
2924
+ var APP_NOT_FOUND = errors({ meeting_app_not_found: "The app does not exist." });
2925
+ var meetingApps = {
2926
+ list: base.route({
2927
+ method: "GET",
2928
+ path: "/v1/meeting-apps",
2929
+ tags: APP_TAGS,
2930
+ summary: "List meeting apps"
2931
+ }).errors(MEETINGS).output(MeetingAppList),
2932
+ create: base.route({
2933
+ method: "POST",
2934
+ path: "/v1/meeting-apps",
2935
+ tags: APP_TAGS,
2936
+ summary: "Register a meeting app",
2937
+ description: `A sign-in app (zoom_oauth, webex_oauth) is named by meeting connect sessions in place of the platform's; register \`redirectUri\` at the service. A server app (zoom_s2s, webex_service_app) starts unverified; test it.`,
2938
+ successStatus: 201
2939
+ }).errors({
2940
+ ...MEETINGS,
2941
+ ...errors({ meeting_app_exists: "The tenant already has a server app for the service." })
2942
+ }).input(CreateMeetingApp).output(MeetingApp),
2943
+ test: base.route({
2944
+ method: "POST",
2945
+ path: "/v1/meeting-apps/{appId}/test",
2946
+ tags: APP_TAGS,
2947
+ summary: "Test a meeting server app",
2948
+ description: `Requests a token the way meetings will and, with \`user\`, looks that address up as a host; the app becomes active or failing.`
2949
+ }).errors({
2950
+ ...MEETINGS,
2951
+ ...APP_NOT_FOUND,
2952
+ ...errors({ not_testable: "The app is a sign-in app, which only account connects use." })
2953
+ }).input(MeetingAppIdInput.extend(TestMeetingAppRequest.shape)).output(MeetingAppTestResult),
2954
+ delete: base.route({
2955
+ method: "DELETE",
2956
+ path: "/v1/meeting-apps/{appId}",
2957
+ tags: APP_TAGS,
2958
+ summary: "Delete a meeting app",
2959
+ description: "Meetings already created stay at the service. Allowed without the meetings module, so a tenant can remove an app it no longer uses.",
2960
+ successStatus: 204
2961
+ }).errors({
2962
+ ...APP_NOT_FOUND,
2963
+ ...errors({
2964
+ meeting_app_in_use: "Account connections were issued through the sign-in app."
2965
+ })
2966
+ }).input(MeetingAppIdInput).output(z26.void())
2967
+ };
2968
+ var meetingConnections = {
2969
+ list: base.route({
2970
+ method: "GET",
2971
+ path: "/v1/accounts/{accountId}/meeting-connections",
2972
+ tags: CONNECTION_TAGS,
2973
+ summary: "List an account's meeting connections"
2974
+ }).errors({ ...MEETINGS, ...ACCOUNT_NOT_FOUND }).input(AccountIdInput).output(MeetingConnectionList),
2975
+ delete: base.route({
2976
+ method: "DELETE",
2977
+ path: "/v1/accounts/{accountId}/meeting-connections/{service}",
2978
+ tags: CONNECTION_TAGS,
2979
+ summary: "Remove a meeting connection",
2980
+ description: "Deletes the grant (and revokes it at Zoom); meetings created with it stay. Later events use the tenant's server app, if any. Allowed without the meetings module, so a tenant can remove a grant it no longer uses.",
2981
+ successStatus: 204
2982
+ }).errors({
2983
+ ...ACCOUNT_NOT_FOUND,
2984
+ ...errors({ meeting_connection_not_found: "The account has no connection to the service." })
2985
+ }).input(MeetingConnectionInput).output(z26.void())
2986
+ };
2987
+ var meetingConnectSessions = {
2988
+ create: base.route({
2989
+ method: "POST",
2990
+ path: "/v1/accounts/{accountId}/meeting-connect-sessions",
2991
+ tags: CONNECTION_TAGS,
2992
+ summary: "Create a hosted meeting connect session",
2993
+ description: "Returns a link to the hosted page where the account's user signs in to the service; the browser then returns to redirectUri with status=success|error, service, error and state, or without redirectUri to workwae's own confirmation page, which needs no allowlist entry. The sign-in passes a Continue page on the API origin naming the tenant, the service and the account, and must finish in the browser that pressed Continue (an HttpOnly cookie on the API origin); a callback in any other browser renders an error page and connects nothing. A session yields at most one grant: once one sign-in completed it, the others end with error=session_used, and one completing after expiresAt ends with error=session_expired. Connecting again replaces the account's grant for the service."
2994
+ }).errors({
2995
+ ...MEETINGS,
2996
+ ...ACCOUNT_NOT_FOUND,
2997
+ ...errors({
2998
+ redirect_uri_not_allowed: "redirectUri is not on the tenant's allowlist.",
2999
+ unknown_meeting_app: "meetingAppId names no meeting app of the tenant.",
3000
+ meeting_app_mismatch: "meetingAppId is not a sign-in app of the service.",
3001
+ meeting_app_secret_unreadable: "The meetingAppId app's secret cannot be decrypted; register the app again.",
3002
+ provider_not_configured: "No meetingAppId is given and the platform has no app for the service."
3003
+ })
3004
+ }).input(AccountIdInput.extend(CreateMeetingConnectSession.shape)).output(SessionLink)
3005
+ };
3006
+ var hostedMeetingConnectSessions = {
3007
+ get: base.route({
3008
+ method: "GET",
3009
+ path: "/v1/hosted/meeting-connect-sessions/{token}",
3010
+ tags: ["Hosted pages"],
3011
+ summary: "Read a hosted meeting connect session",
3012
+ spec: publicOperation
3013
+ }).errors(errors({
3014
+ meeting_connect_session_not_found: "No session has this token, or its account no longer exists."
3015
+ })).input(SessionTokenInput).output(MeetingConnectSessionPublic)
3016
+ };
3017
+
3018
+ // ../contract/src/oauth-apps/routes.ts
3019
+ import * as z27 from "zod";
3020
+ var NOT_FOUND = errors({ oauth_app_not_found: "The OAuth app does not exist." });
3021
+ var oauthApps = {
3022
+ list: base.route({
3023
+ method: "GET",
3024
+ path: "/v1/oauth-apps",
3025
+ tags: ["OAuth apps"],
3026
+ summary: "List tenant OAuth apps"
3027
+ }).output(OAuthAppList),
3028
+ create: base.route({
3029
+ method: "POST",
3030
+ path: "/v1/oauth-apps",
3031
+ tags: ["OAuth apps"],
3032
+ summary: "Register a tenant OAuth app",
3033
+ successStatus: 201
3034
+ }).errors(errors({
3035
+ cloud_required: "provider is microsoft and no cloud is given.",
3036
+ not_microsoft: "cloud or authority is given for a Google app."
3037
+ })).input(CreateOAuthApp).output(OAuthApp),
3038
+ update: base.route({
3039
+ method: "PATCH",
3040
+ path: "/v1/oauth-apps/{appId}",
3041
+ tags: ["OAuth apps"],
3042
+ summary: "Change a tenant OAuth app: name, client secret, authority or Gmail push",
3043
+ description: "Accounts on the app use the new secret from their next token refresh. gmailPush (Google apps) makes the app's accounts watch the topic with users.watch instead of being polled; null stops their watches and returns them to polling. Changing it wakes the app's accounts so it applies at once, and saving it again retries a watch Gmail refused."
3044
+ }).errors({
3045
+ ...NOT_FOUND,
3046
+ ...errors({
3047
+ not_microsoft: "authority is given for a Google app.",
3048
+ gmail_push_unsupported: "gmailPush is given for a Microsoft app.",
3049
+ push_unavailable: "gmailPush is set and the instance has no PUSH_BASE_URL."
3050
+ })
3051
+ }).input(OAuthAppIdInput.extend(UpdateOAuthApp.shape)).output(OAuthApp),
3052
+ delete: base.route({
3053
+ method: "DELETE",
3054
+ path: "/v1/oauth-apps/{appId}",
3055
+ tags: ["OAuth apps"],
3056
+ summary: "Delete a tenant OAuth app",
3057
+ successStatus: 204
3058
+ }).errors({
3059
+ ...NOT_FOUND,
3060
+ ...errors({ oauth_app_in_use: "Live accounts still use the app." })
3061
+ }).input(OAuthAppIdInput).output(z27.void())
3062
+ };
3063
+
3064
+ // ../contract/src/org-connections/routes.ts
3065
+ import * as z28 from "zod";
3066
+ var NOT_FOUND2 = errors({
3067
+ org_connection_not_found: "The organization connection does not exist."
3068
+ });
3069
+ var CONSENT_REQUIRED = errors({
3070
+ consent_required: "No Microsoft directory has consented to the app yet."
3071
+ });
3072
+ var CREDENTIALS = errors({
3073
+ invalid_service_account_key: "serviceAccountKey is not a usable Google service account key.",
3074
+ invalid_certificate: "The certificate or private key is not a usable PEM certificate and unencrypted PKCS#8 RSA key.",
3075
+ directory_id_not_allowed: "A directoryId is given for a platform connection."
3076
+ });
3077
+ var TAGS = ["Organization connections"];
3078
+ var orgConnections = {
3079
+ list: base.route({
3080
+ method: "GET",
3081
+ path: "/v1/org-connections",
3082
+ tags: TAGS,
3083
+ summary: "List organization connections"
3084
+ }).output(OrgConnectionList),
3085
+ create: base.route({
3086
+ method: "POST",
3087
+ path: "/v1/org-connections",
3088
+ tags: TAGS,
3089
+ summary: "Register an organization connection",
3090
+ description: "Google: the service account key is checked and sealed; authorize the returned clientId for the returned scopes in the Admin console, then test. Microsoft: a platform connection starts in pending_consent (POST .../consent); a tenant app with a directoryId starts unverified.",
3091
+ successStatus: 201
3092
+ }).errors({
3093
+ ...CREDENTIALS,
3094
+ ...errors({
3095
+ provider_not_configured: "A platform connection names a cloud the platform has no app for."
3096
+ })
3097
+ }).input(CreateOrgConnection).output(OrgConnection),
3098
+ get: base.route({
3099
+ method: "GET",
3100
+ path: "/v1/org-connections/{connectionId}",
3101
+ tags: TAGS,
3102
+ summary: "Get an organization connection"
3103
+ }).errors(NOT_FOUND2).input(OrgConnectionIdInput).output(OrgConnection),
3104
+ update: base.route({
3105
+ method: "PATCH",
3106
+ path: "/v1/org-connections/{connectionId}",
3107
+ tags: TAGS,
3108
+ summary: "Update an organization connection",
3109
+ description: "Replacing a credential or the authorized scopes makes the connection unverified until the next test."
3110
+ }).errors({
3111
+ ...NOT_FOUND2,
3112
+ ...CREDENTIALS,
3113
+ ...errors({
3114
+ credential_mismatch: "A field does not apply to this connection's kind or credential type, or a certificate is replaced without its private key."
3115
+ })
3116
+ }).input(OrgConnectionIdInput.extend(UpdateOrgConnection.shape)).output(OrgConnection),
3117
+ delete: base.route({
3118
+ method: "DELETE",
3119
+ path: "/v1/org-connections/{connectionId}",
3120
+ tags: TAGS,
3121
+ summary: "Delete an organization connection",
3122
+ successStatus: 204
3123
+ }).errors({
3124
+ ...NOT_FOUND2,
3125
+ ...errors({ connection_in_use: "Live accounts still authenticate through the connection." })
3126
+ }).input(OrgConnectionIdInput).output(z28.void()),
3127
+ consent: base.route({
3128
+ method: "POST",
3129
+ path: "/v1/org-connections/{connectionId}/consent",
3130
+ tags: TAGS,
3131
+ summary: "Start Microsoft admin consent",
3132
+ description: "Returns the URL an administrator of the directory grants consent at. Afterwards the browser returns to redirectUri with status=success|error, connection_id, error and state; the connection records the consenting directory and is tested. A tenant app uses /adminconsent. The platform app is consented through a sign-in (authorize with prompt=admin_consent) and records the directory named by the sign-in's ID token; it can only be started from the board, and the board user who started it has to be signed in, in the browser that returns, for the directory to be recorded. Errors on return include access_denied, consent_failed, consent_unverified, directory_mismatch and directory_already_connected (another platform connection holds the directory)."
3133
+ }).errors({
3134
+ ...NOT_FOUND2,
3135
+ ...errors({
3136
+ not_microsoft: "The connection is a Google connection.",
3137
+ redirect_uri_not_allowed: "redirectUri is neither on the tenant allowlist (settings.redirectUris) nor the board.",
3138
+ session_required: "A platform connection's consent is started by a caller that is not a board session.",
3139
+ provider_not_configured: "A platform connection's cloud has no platform app (the platform's configuration changed)."
3140
+ })
3141
+ }).input(OrgConnectionIdInput.extend(StartConsentRequest.shape)).output(ConsentLink),
3142
+ test: base.route({
3143
+ method: "POST",
3144
+ path: "/v1/org-connections/{connectionId}/test",
3145
+ tags: TAGS,
3146
+ summary: "Test an organization connection",
3147
+ description: "Requests a token the way accounts will and reports each check; the connection becomes active or failing. A passing test returns accounts that were moved to reauth_required to active. Google impersonates `user` (default: the directory admin)."
3148
+ }).errors({
3149
+ ...NOT_FOUND2,
3150
+ ...CONSENT_REQUIRED,
3151
+ ...errors({
3152
+ user_required: "A Google connection has neither a user nor a directory admin to test as."
3153
+ })
3154
+ }).input(OrgConnectionIdInput.extend(TestOrgConnectionRequest.shape)).output(OrgConnectionTestResult),
3155
+ directoryEntries: base.route({
3156
+ method: "GET",
3157
+ path: "/v1/org-connections/{connectionId}/directory-entries",
3158
+ tags: TAGS,
3159
+ summary: "List the directory's mailboxes",
3160
+ description: "Microsoft: Graph /users (needs User.Read.All). Google: the Admin SDK users.list as the directory admin (needs admin.directory.user.readonly). `query` matches the start of names and addresses. A nextCursor is good for an hour, on this connection only."
3161
+ }).errors({
3162
+ ...NOT_FOUND2,
3163
+ ...CONSENT_REQUIRED,
3164
+ ...INVALID_CURSOR,
3165
+ ...PROVIDER_FAILURES,
3166
+ ...errors({
3167
+ directory_unavailable: "The connection may not list the directory: there is no directory admin, the permission or scope is not granted, or the connection cannot get a token (test it).",
3168
+ provider_rejected: "The provider rejected the listing (e.g. the query).",
3169
+ provider_not_found: "The provider no longer has the directory."
3170
+ })
3171
+ }).input(OrgConnectionIdInput.extend(ListDirectoryEntriesQuery.shape)).output(DirectoryEntryPage),
3172
+ addAccounts: base.route({
3173
+ method: "POST",
3174
+ path: "/v1/org-connections/{connectionId}/accounts",
3175
+ tags: TAGS,
3176
+ summary: "Add mailboxes as accounts",
3177
+ description: "Each mailbox is checked through the connection before its account is created (Google: a delegated token for it; Microsoft: its inbox). items holds one result per mailbox, in request order. Per-mailbox failures are reported there with the reason the API would answer with, e.g. invalid_grant (no such user), consent_revoked (the scopes or permissions are not granted), provider_forbidden or provider_not_found (no mailbox access), plan_limit_accounts (the plan's account limit was reached part way through; the mailboxes before it are created)."
3178
+ }).errors({
3179
+ ...NOT_FOUND2,
3180
+ ...CONSENT_REQUIRED,
3181
+ ...bodyStorage("settings.bodyStorage"),
3182
+ ...BILLING_PAST_DUE
3183
+ }).input(OrgConnectionIdInput.extend(AddMailboxesRequest.shape)).output(AddedMailboxList)
3184
+ };
3185
+ var sharedMailboxes = {
3186
+ create: base.route({
3187
+ method: "POST",
3188
+ path: "/v1/accounts/{accountId}/shared-mailboxes",
3189
+ tags: ["Shared mailboxes"],
3190
+ summary: "Open a shared mailbox with an account's grant",
3191
+ description: "Creates (or re-points) an account for a shared mailbox the delegating account's user can access, authenticated with that account's grant: Graph addresses it as /users/{email}; an Exchange IMAP account logs in as user\\shared with XOAUTH2. Gmail has no delegated shared mailboxes: add them through a Google domain-wide delegation connection.",
3192
+ successStatus: 201
3193
+ }).errors({
3194
+ ...planModule("shared mailboxes"),
3195
+ ...bodyStorage("settings.bodyStorage"),
3196
+ ...PLAN_LIMIT_ACCOUNTS,
3197
+ ...BILLING_PAST_DUE,
3198
+ ...accountState("The delegating account"),
3199
+ ...PROVIDER_FAILURES,
3200
+ ...errors({
3201
+ account_not_found: "The delegating account does not exist.",
3202
+ shared_mailbox_unsupported: "The delegating account is Gmail, password IMAP, or itself shared or organization-authenticated, or the mailbox is its own.",
3203
+ scope_missing: "The delegating account's grant lacks the shared mailbox scope (reconnect it with options.sharedMailboxesEnabled).",
3204
+ shared_mailbox_inaccessible: "The delegating account's user cannot open the mailbox (no access, no such mailbox, or the login is refused).",
3205
+ account_exists: "The mailbox is already an account authenticated another way."
3206
+ })
3207
+ }).input(AccountIdInput.extend(OpenSharedMailboxRequest.shape)).output(Account)
3208
+ };
3209
+
3210
+ // ../contract/src/send/routes.ts
3211
+ import * as z29 from "zod";
3212
+ var LIST = "With listId, content is always rendered, `{{rcpt.unsubscribeUrl}}`, `{{rcpt.address}}` and `{{rcpt.name}}` are the recipient's, and the message carries List-Unsubscribe with that same URL and List-Unsubscribe-Post: List-Unsubscribe=One-Click (RFC 8058). Click tracking never rewrites a List-Unsubscribe URL, so the body's link stays as written. A recipient on that list or on the global list is not sent to, at queue time and again at delivery (send.failed with code suppressed). Without listId no list is consulted. Whether the provider's DKIM signature covers the List-Unsubscribe headers is up to its signing.";
3213
+ var UNDO = "A message without a sendAt later than the account's undo window (undoSendSeconds) from now waits out the window; undoUntil says until when cancelling the entry (DELETE .../outbox/{outboxId}) undoes the send.";
3214
+ var DELIVERY = "Delivery is at-least-once, except outcome_unknown (below), and without order across retries: an account's entries are first tried in queue order, but one backing off after a failed attempt does not hold up the later ones. A retry first looks for a copy that already went out. When no SMTP reply comes after the final dot was handed to the operating system (the connection dropped or timed out, or the attempt ran out of time), the server may have accepted the message, so it is not sent again: send.failed carries code outcome_unknown and the client app decides whether to resend.";
3215
+ var TRACKING = "With tracking, the HTML part gets a 1x1 pixel (opens) and its http(s) links point at workwae's redirect (clicks), reported as message.opened and message.clicked and counted on the outbox entry; text parts are left as they are. With clicks, tracking.honeypot (else the account's trackingHoneypot) also adds one hidden link only machines follow; it does nothing without clicks, so asking for it with clicks off is refused rather than ignored. Tracking is per message, not per recipient: every recipient gets the same MIME.";
3216
+ var MAIL_MERGE = `Each of at most ${MAX_MAIL_MERGE_ENTRIES} entries becomes its own outbox entry and message, addressed to its to alone, with its own Message-ID, events, bounce matching and tracking; the answer has one result per entry, in order. Content is rendered per entry with its params (a template always; inline content only for entries that carry params, or with listId). An entry that cannot be queued fails alone, its result naming the reason (invalid_recipient, invalid_message_id, duplicate_message_id, template_render_failed, plan_limit_sends once the day's sends run out, suppressed when listId is given and the address is on that list or the global list, internal_error), while the others go ahead; the call itself succeeds. The body is the send body without to, cc, bcc and render: an unknown field is refused.`;
3217
+ var COMPOSE_ERRORS = {
3218
+ ...ACCOUNT_NOT_FOUND,
3219
+ ...errors({
3220
+ from_not_allowed: "from is not one of the account's identities.",
3221
+ attachments_too_large: "The attachments exceed the provider's limit.",
3222
+ unknown_reply_target: "inReplyToMessageId names no message of the account."
3223
+ }),
3224
+ ...accountState(),
3225
+ ...PROVIDER_FAILURES
3226
+ };
3227
+ var TEMPLATE_ERRORS = errors({
3228
+ unknown_template: "template names no template of the tenant.",
3229
+ template_account_mismatch: "The template is bound to another account.",
3230
+ template_and_inline_fields: "template comes with an inline subject, html, text, previewText or format.",
3231
+ invalid_template: "Inline content to render is not valid Handlebars or uses partials or decorators; data.field names the field.",
3232
+ params_too_large: `params exceed ${MAX_RENDER_PARAMS_BYTES} bytes as JSON.`,
3233
+ template_render_failed: `Rendering fails (a missing helper, more than ${MAX_RENDER_ITERATIONS} block iterations, a field rendered past its limit, Markdown longer than ${MAX_MARKDOWN_LENGTH} characters, rendered or not); data.field names the field.`
3234
+ });
3235
+ var RENDERED_ONCE = "Content is rendered when the request is taken, so later template edits never change it.";
3236
+ var TRACKING_ERRORS = errors({
3237
+ honeypot_needs_click_tracking: "tracking.honeypot is true and click tracking is off.",
3238
+ plan_module_unavailable: "Tracking is asked for and the tenant's plan does not include it (nor do the account's tracking defaults apply then)."
3239
+ });
3240
+ var LIST_UNSUBSCRIBE_UNAVAILABLE = errors({
3241
+ list_unsubscribe_unavailable: "listId is given and TRACKING_BASE_URL is not https."
3242
+ });
3243
+ var PLAN_LIMIT_SENDS = errors({
3244
+ plan_limit_sends: "The tenant already queued as many sends today (UTC) as its plan allows."
3245
+ });
3246
+ var DRAFT_NOT_FOUND = errors({ draft_not_found: "The message does not exist or is no draft." });
3247
+ var drafts = {
3248
+ create: base.route({
3249
+ method: "POST",
3250
+ path: "/v1/accounts/{accountId}/drafts",
3251
+ tags: ["Mail"],
3252
+ summary: "Create a draft",
3253
+ description: RENDERED_ONCE,
3254
+ successStatus: 201
3255
+ }).errors({
3256
+ ...COMPOSE_ERRORS,
3257
+ ...TEMPLATE_ERRORS,
3258
+ ...PROVIDER_CHANGE,
3259
+ ...errors({ draft_lost: "The provider took the draft but did not answer it back." })
3260
+ }).input(AccountIdInput.extend(ComposeWithTemplate.shape)).output(MessageSummary),
3261
+ update: base.route({
3262
+ method: "PUT",
3263
+ path: "/v1/accounts/{accountId}/drafts/{messageId}",
3264
+ tags: ["Mail"],
3265
+ summary: "Replace a draft's content",
3266
+ description: RENDERED_ONCE
3267
+ }).errors({ ...COMPOSE_ERRORS, ...TEMPLATE_ERRORS, ...PROVIDER_CHANGE, ...DRAFT_NOT_FOUND }).input(MessageIdInput.extend(ComposeWithTemplate.shape)).output(MessageSummary),
3268
+ delete: base.route({
3269
+ method: "DELETE",
3270
+ path: "/v1/accounts/{accountId}/drafts/{messageId}",
3271
+ tags: ["Mail"],
3272
+ summary: "Delete a draft",
3273
+ successStatus: 204
3274
+ }).errors({ ...ACCOUNT_NOT_FOUND, ...DRAFT_NOT_FOUND, ...accountState(), ...PROVIDER_CHANGE }).input(MessageIdInput).output(z29.void()),
3275
+ send: base.route({
3276
+ method: "POST",
3277
+ path: "/v1/accounts/{accountId}/drafts/{messageId}/send",
3278
+ tags: ["Mail"],
3279
+ summary: "Send a draft",
3280
+ description: `${UNDO} ${TRACKING} A Gmail or Microsoft 365 draft is sent by the provider as it is stored, so it is never tracked (the account's defaults do not apply); an IMAP draft is. A cancelled draft-send leaves the draft in place.`,
3281
+ successStatus: 201
3282
+ }).errors({
3283
+ ...ACCOUNT_NOT_FOUND,
3284
+ ...DRAFT_NOT_FOUND,
3285
+ ...accountState(),
3286
+ ...errors({
3287
+ not_a_draft: "The message stopped being a draft while the send was queued.",
3288
+ no_recipients: "The draft has no to, cc or bcc recipients (as last indexed).",
3289
+ tracking_unsupported: "Tracking (opens, clicks or honeypot) is asked for a Gmail or Microsoft 365 draft."
3290
+ }),
3291
+ ...TRACKING_ERRORS,
3292
+ ...PLAN_LIMIT_SENDS,
3293
+ ...BILLING_PAST_DUE
3294
+ }).input(MessageIdInput.extend(SendOptions.shape)).output(QueuedSend)
3295
+ };
3296
+ var SEND_INPUT = AccountIdInput.extend(SendMessageRequest.shape).strict().superRefine((send, ctx) => {
3297
+ if (send.to.length + send.cc.length + send.bcc.length === 0)
3298
+ ctx.addIssue({
3299
+ code: "custom",
3300
+ path: ["to"],
3301
+ message: "The message has no recipients (to, cc or bcc)",
3302
+ params: { reason: "no_recipients" }
3303
+ });
3304
+ });
3305
+ var SEND_NOT_FOUND = errors({ send_not_found: "The account has no such outbox entry." });
3306
+ var outbox = {
3307
+ create: base.route({
3308
+ method: "POST",
3309
+ path: "/v1/accounts/{accountId}/outbox",
3310
+ tags: ["Mail"],
3311
+ summary: "Send a message",
3312
+ description: `Queues the message as an outbox entry; delivery is reported with send.sent, send.retrying and send.failed events carrying its id as outboxId, and a later bounce with message.bounced. ${DELIVERY} ${UNDO} ${TRACKING} ${LIST} For one message per recipient, see POST .../mail-merges. ${RENDERED_ONCE} An unknown field is refused.`,
3313
+ successStatus: 201
3314
+ }).errors({
3315
+ ...COMPOSE_ERRORS,
3316
+ ...TEMPLATE_ERRORS,
3317
+ ...errors({
3318
+ no_recipients: "to, cc and bcc are all empty.",
3319
+ list_send_recipients: "listId is given and the message has not exactly one to and no cc or bcc.",
3320
+ suppressed: "listId is given and the recipient is on that list or the global list."
3321
+ }),
3322
+ ...TRACKING_ERRORS,
3323
+ ...LIST_UNSUBSCRIBE_UNAVAILABLE,
3324
+ ...PLAN_LIMIT_SENDS,
3325
+ ...BILLING_PAST_DUE
3326
+ }).input(SEND_INPUT).output(QueuedSend),
3327
+ list: base.route({
3328
+ method: "GET",
3329
+ path: "/v1/accounts/{accountId}/outbox",
3330
+ tags: ["Mail"],
3331
+ summary: "List queued and recent sends, newest first",
3332
+ description: "status keeps the entries in that status."
3333
+ }).errors({ ...ACCOUNT_NOT_FOUND, ...INVALID_CURSOR }).input(AccountIdInput.extend(ListOutboxQuery.shape)).output(OutboxEntryPage),
3334
+ get: base.route({
3335
+ method: "GET",
3336
+ path: "/v1/accounts/{accountId}/outbox/{outboxId}",
3337
+ tags: ["Mail"],
3338
+ summary: "Get a send",
3339
+ description: "One entry with its tracking counts, for as long as the outbox keeps it: a sent message's outboxId names it."
3340
+ }).errors({ ...ACCOUNT_NOT_FOUND, ...SEND_NOT_FOUND }).input(OutboxIdInput).output(OutboxEntry),
3341
+ cancel: base.route({
3342
+ method: "DELETE",
3343
+ path: "/v1/accounts/{accountId}/outbox/{outboxId}",
3344
+ tags: ["Mail"],
3345
+ summary: "Cancel a queued send",
3346
+ description: "Undoes a send while it waits for its sendAt.",
3347
+ successStatus: 204
3348
+ }).errors({
3349
+ ...ACCOUNT_NOT_FOUND,
3350
+ ...SEND_NOT_FOUND,
3351
+ ...errors({
3352
+ send_not_cancellable: "The entry is no longer queued: it is being sent, was sent, failed for good or was cancelled (the message names which)."
3353
+ })
3354
+ }).input(OutboxIdInput).output(z29.void()),
3355
+ reschedule: base.route({
3356
+ method: "PATCH",
3357
+ path: "/v1/accounts/{accountId}/outbox/{outboxId}",
3358
+ tags: ["Mail"],
3359
+ summary: "Reschedule a queued send",
3360
+ description: "Moves sendAt, earlier or later; a past sendAt sends as soon as possible. Announced as send.rescheduled."
3361
+ }).errors({
3362
+ ...ACCOUNT_NOT_FOUND,
3363
+ ...SEND_NOT_FOUND,
3364
+ ...errors({
3365
+ send_not_reschedulable: "The entry is no longer queued (it is being sent, was sent, failed for good or was cancelled), or it is queued again after a failed attempt; the message names which."
3366
+ })
3367
+ }).input(OutboxIdInput.extend(RescheduleSendRequest.shape)).output(OutboxEntry),
3368
+ tracking: base.route({
3369
+ method: "GET",
3370
+ path: "/v1/accounts/{accountId}/outbox/{outboxId}/tracking",
3371
+ tags: ["Mail"],
3372
+ summary: "List a send's opens and clicks",
3373
+ description: "Newest first. At most 1000 are kept per send (the counts on the entry keep going), each for the tracking retention period. Each event carries what its request showed: the method (GET or HEAD), the Referer and Origin hosts, the mail client (Gmail's image proxy, web or app; Outlook on the web, desktop or mobile; Apple Mail and its Mail Privacy Protection; Yahoo, AOL, Thunderbird, Proton, Fastmail, Zoho, iCloud, a self-hosted webmail; else unknown), the device, OS and browser families, Cloudflare's visitor location (country, region, city, timezone, latitude and longitude, behind a trusted proxy only), the seconds after the send and since the send's previous hit, its sequence number, whether it was conditional, a Range request or a prefetch, the sec-fetch tuple, and the request headers (lower-cased, values cut at 1024 characters, at most 64, never Cookie, Authorization or Proxy-Authorization, forwarding and cf-* headers only from the trusted proxy; the full Referer is only here). Full addresses and headers stay with the tenant for the retention period."
3374
+ }).errors({ ...ACCOUNT_NOT_FOUND, ...INVALID_CURSOR, ...SEND_NOT_FOUND }).input(OutboxIdInput.extend(ListTrackingEventsQuery.shape)).output(TrackingEventPage)
3375
+ };
3376
+ var mailMerges = {
3377
+ create: base.route({
3378
+ method: "POST",
3379
+ path: "/v1/accounts/{accountId}/mail-merges",
3380
+ tags: ["Mail"],
3381
+ summary: "Send one message per recipient (mail merge)",
3382
+ description: `${MAIL_MERGE} ${DELIVERY} ${UNDO} ${TRACKING} ${LIST} What holds for the whole call is checked once and refuses it. ${RENDERED_ONCE} Answers 200: the merge itself is not kept, its entries are in the outbox.`
3383
+ }).errors({
3384
+ ...COMPOSE_ERRORS,
3385
+ ...TEMPLATE_ERRORS,
3386
+ ...errors({
3387
+ attachments_too_large: `The attachments exceed the provider's limit, or the attachments times the entries exceed ${MAX_MAIL_MERGE_ATTACHMENT_BYTES} bytes.`
3388
+ }),
3389
+ ...TRACKING_ERRORS,
3390
+ ...LIST_UNSUBSCRIBE_UNAVAILABLE,
3391
+ ...BILLING_PAST_DUE
3392
+ }).input(AccountIdInput.extend(MailMergeRequest.shape).strict()).output(MailMergeResultList)
3393
+ };
3394
+
3395
+ // ../contract/src/settings/routes.ts
3396
+ var settings = {
3397
+ get: base.route({
3398
+ method: "GET",
3399
+ path: "/v1/settings",
3400
+ tags: ["Settings"],
3401
+ summary: "Get tenant settings"
3402
+ }).output(TenantSettings),
3403
+ update: base.route({
3404
+ method: "PATCH",
3405
+ path: "/v1/settings",
3406
+ tags: ["Settings"],
3407
+ summary: "Update tenant settings",
3408
+ description: "bodyStorage applies to every account without its own override; changing it restarts their syncs (full stores and backfills every indexed message, none deletes stored content)."
3409
+ }).errors({
3410
+ ...bodyStorage("bodyStorage"),
3411
+ ...errors({
3412
+ redirect_uri_not_allowed: "A redirect URI is not absolute https (http only for localhost)."
3413
+ })
3414
+ }).input(UpdateTenantSettings).output(TenantSettings)
3415
+ };
3416
+
3417
+ // ../contract/src/suppressions/routes.ts
3418
+ import * as z30 from "zod";
3419
+ var LIST_NOT_FOUND = errors({
3420
+ suppression_list_not_found: "The list does not exist (the global list has none to change)."
3421
+ });
3422
+ var REVIEW_NOT_FOUND = errors({
3423
+ suppression_review_not_found: "The review does not exist."
3424
+ });
3425
+ var suppressions = {
3426
+ list: base.route({
3427
+ method: "GET",
3428
+ path: "/v1/suppressions",
3429
+ tags: ["Suppressions"],
3430
+ summary: "List suppression lists",
3431
+ description: "The global list first, then every named list by listId, with its counts by source."
3432
+ }).output(SuppressionListList),
3433
+ create: base.route({
3434
+ method: "POST",
3435
+ path: "/v1/suppressions",
3436
+ tags: ["Suppressions"],
3437
+ summary: "Create a suppression list",
3438
+ description: "id is what sends name as listId; it cannot change later, the name and description can. A send or add naming a list that does not exist creates it too, named after its id.",
3439
+ successStatus: 201
3440
+ }).errors(errors({ suppression_list_exists: "A list with that id exists." })).input(CreateSuppressionList).output(SuppressionList),
3441
+ update: base.route({
3442
+ method: "PATCH",
3443
+ path: "/v1/suppressions/{listId}",
3444
+ tags: ["Suppressions"],
3445
+ summary: "Update a suppression list",
3446
+ description: "Only the fields given change; description null clears it. The global list cannot be changed."
3447
+ }).errors(LIST_NOT_FOUND).input(ListIdInput.extend(UpdateSuppressionList.shape)).output(SuppressionList),
3448
+ delete: base.route({
3449
+ method: "DELETE",
3450
+ path: "/v1/suppressions/{listId}",
3451
+ tags: ["Suppressions"],
3452
+ summary: "Delete a suppression list",
3453
+ description: "Deletes the list and its pending reviews and, with ?withEntries=true, every address on it: later sends naming it reach those addresses again (unless the global list holds them), and the next one naming it creates it anew. The global list cannot be deleted.",
3454
+ successStatus: 204,
3455
+ inputStructure: "detailed"
3456
+ }).errors({
3457
+ ...LIST_NOT_FOUND,
3458
+ ...errors({
3459
+ suppression_list_not_empty: "The list holds addresses and withEntries is not true."
3460
+ })
3461
+ }).input(DeleteSuppressionListInput).output(z30.void()),
3462
+ entries: {
3463
+ list: base.route({
3464
+ method: "GET",
3465
+ path: "/v1/suppressions/{listId}/entries",
3466
+ tags: ["Suppressions"],
3467
+ summary: "List suppressed addresses",
3468
+ description: "Addresses on one list, by address; a list without addresses is empty, not missing. address keeps those containing it, source those that came from it."
3469
+ }).errors(INVALID_CURSOR).input(SuppressionListIdInput.extend(ListSuppressionEntriesQuery.shape)).output(SuppressionEntryPage),
3470
+ add: base.route({
3471
+ method: "POST",
3472
+ path: "/v1/suppressions/{listId}/entries",
3473
+ tags: ["Suppressions"],
3474
+ summary: "Suppress addresses",
3475
+ description: `Puts up to ${MAX_SUPPRESSION_BATCH} addresses on the list in one call; a repeated address counts once. added counts the addresses put on the list, alreadyListed those that were on it already, which are left as they were. A list that does not exist is created, named after its listId. Answers 200: entries are listed, not fetched one by one. Emits no suppression.created: that event reports what recipients and bounces did.`
3476
+ }).input(SuppressionListIdInput.extend(AddSuppressionEntriesRequest.shape)).output(SuppressionEntriesAdded),
3477
+ remove: base.route({
3478
+ method: "DELETE",
3479
+ path: "/v1/suppressions/{listId}/entries/{address}",
3480
+ tags: ["Suppressions"],
3481
+ summary: "Release an address",
3482
+ description: "Later sends naming the list reach the address again (unless another list they consult holds it).",
3483
+ successStatus: 204
3484
+ }).errors(errors({ suppression_entry_not_found: "The address is not on the list." })).input(SuppressionEntryInput).output(z30.void())
3485
+ }
3486
+ };
3487
+ var suppressionReviews = {
3488
+ list: base.route({
3489
+ method: "GET",
3490
+ path: "/v1/suppression-reviews",
3491
+ tags: ["Suppressions"],
3492
+ summary: "List opt-outs to review",
3493
+ description: "Detected opt-outs waiting for a person, newest first; listId keeps one list's. Holds AI-classified opt-out replies while optOutHandling is review, and auto-replies saying the person is no longer at the address (source inactive, on the global list) while it is review or suppress. A detection of an address already on its list, or already waiting, is not queued again."
3494
+ }).errors(INVALID_CURSOR).input(ListSuppressionReviewsQuery).output(SuppressionReviewPage),
3495
+ approve: base.route({
3496
+ method: "POST",
3497
+ path: "/v1/suppression-reviews/{reviewId}/approve",
3498
+ tags: ["Suppressions"],
3499
+ summary: "Approve an opt-out",
3500
+ description: "Puts the address on the review's list with the review's source and removes the review. added is false when the address was on the list already. Emits suppression.created when it was not."
3501
+ }).errors(REVIEW_NOT_FOUND).input(ReviewIdInput).output(SuppressionAdded),
3502
+ dismiss: base.route({
3503
+ method: "POST",
3504
+ path: "/v1/suppression-reviews/{reviewId}/dismiss",
3505
+ tags: ["Suppressions"],
3506
+ summary: "Dismiss an opt-out",
3507
+ description: "Removes the review without suppressing anything; a later detection of the same address queues it again.",
3508
+ successStatus: 204
3509
+ }).errors(REVIEW_NOT_FOUND).input(ReviewIdInput).output(z30.void())
3510
+ };
3511
+
3512
+ // ../contract/src/templates/routes.ts
3513
+ import * as z31 from "zod";
3514
+ var NOT_FOUND3 = errors({ template_not_found: "The template does not exist." });
3515
+ var SYNTAX = errors({
3516
+ invalid_template: "A field is not valid Handlebars or uses partials or decorators (data names the field, the message is the parser's)."
3517
+ });
3518
+ var NAME_TAKEN = errors({
3519
+ template_name_taken: "The scope (the tenant, or the bound account) already has a template of that name."
3520
+ });
3521
+ var UNKNOWN_ACCOUNT = errors({
3522
+ unknown_account: "The accountId names an account the tenant does not have (data lists it)."
3523
+ });
3524
+ var templates = {
3525
+ list: base.route({
3526
+ method: "GET",
3527
+ path: "/v1/templates",
3528
+ tags: ["Templates"],
3529
+ summary: "List templates",
3530
+ description: "Newest first, without their content (GET /v1/templates/{templateId} has it). accountId lists those bound to that account; usableBy those the account may send with (the tenant's shared ones and its own); name the ones of that exact name, one per scope; query those whose name contains it, ignoring case."
3531
+ }).errors(INVALID_CURSOR).input(ListTemplatesQuery).output(TemplateSummaryPage),
3532
+ get: base.route({
3533
+ method: "GET",
3534
+ path: "/v1/templates/{templateId}",
3535
+ tags: ["Templates"],
3536
+ summary: "Get a template"
3537
+ }).errors(NOT_FOUND3).input(TemplateIdInput).output(Template),
3538
+ create: base.route({
3539
+ method: "POST",
3540
+ path: "/v1/templates",
3541
+ tags: ["Templates"],
3542
+ summary: "Create a template",
3543
+ description: "Every field is checked to compile as Handlebars.",
3544
+ successStatus: 201
3545
+ }).errors({ ...SYNTAX, ...UNKNOWN_ACCOUNT, ...NAME_TAKEN }).input(CreateTemplate).output(Template),
3546
+ update: base.route({
3547
+ method: "PATCH",
3548
+ path: "/v1/templates/{templateId}",
3549
+ tags: ["Templates"],
3550
+ summary: "Update a template",
3551
+ description: "Only the fields given change; queued sends keep what they were rendered with."
3552
+ }).errors({ ...NOT_FOUND3, ...SYNTAX, ...NAME_TAKEN }).input(TemplateIdInput.extend(UpdateTemplate.shape)).output(Template),
3553
+ delete: base.route({
3554
+ method: "DELETE",
3555
+ path: "/v1/templates/{templateId}",
3556
+ tags: ["Templates"],
3557
+ summary: "Delete a template",
3558
+ description: "Queued sends keep their rendered content.",
3559
+ successStatus: 204
3560
+ }).errors(NOT_FOUND3).input(TemplateIdInput).output(z31.void())
3561
+ };
3562
+ var templateRenders = {
3563
+ create: base.route({
3564
+ method: "POST",
3565
+ path: "/v1/template-renders",
3566
+ tags: ["Templates"],
3567
+ summary: "Render a template without sending",
3568
+ description: "Renders a stored template (templateId) or inline fields with params, as a send would. account.email and account.name come from accountId, else from the template's own account, else render empty."
3569
+ }).errors({
3570
+ ...SYNTAX,
3571
+ ...UNKNOWN_ACCOUNT,
3572
+ ...errors({
3573
+ template_and_inline_fields: "The templateId comes with inline fields or format.",
3574
+ unknown_template: "The templateId names no template of the tenant.",
3575
+ template_account_mismatch: "The template is bound to another account than accountId.",
3576
+ params_too_large: `The params exceed ${MAX_RENDER_PARAMS_BYTES} bytes as JSON.`,
3577
+ template_render_failed: `Rendering fails: a missing helper, more than ${MAX_RENDER_ITERATIONS} block iterations, a field rendered past its limit, or Markdown longer than ${MAX_MARKDOWN_LENGTH} characters (data names the field).`
3578
+ })
3579
+ }).input(RenderTemplateRequest).output(RenderedTemplate)
3580
+ };
3581
+
3582
+ // ../contract/src/threads/routes.ts
3583
+ import * as z33 from "zod";
3584
+
3585
+ // ../contract/src/threads/models.ts
3586
+ import * as z32 from "zod";
3587
+ var ThreadSummary = named("ThreadSummary", z32.object({
3588
+ id: ProviderId,
3589
+ subject: z32.string().nullable(),
3590
+ snippet: z32.string().nullable(),
3591
+ participants: z32.array(Address),
3592
+ messageCount: z32.number().int(),
3593
+ unreadCount: z32.number().int(),
3594
+ isFlagged: z32.boolean(),
3595
+ hasAttachments: z32.boolean(),
3596
+ hasDrafts: z32.boolean(),
3597
+ mailboxIds: z32.array(Id),
3598
+ latestMessageId: Id,
3599
+ lastMessageAt: Timestamp.nullable()
3600
+ }));
3601
+ var Thread = named("Thread", ThreadSummary.extend({
3602
+ messages: z32.array(MessageSummary)
3603
+ }));
3604
+ var ThreadSummaryPage = Page(ThreadSummary);
3605
+ var ThreadIdInput = named("ThreadIdInput", AccountIdInput.extend({ threadId: ProviderId }));
3606
+ var ListThreadsQuery = named("ListThreadsQuery", z32.object({
3607
+ mailboxId: Id.optional(),
3608
+ limit: pageLimit(MAX_THREAD_PAGE_SIZE, DEFAULT_THREAD_PAGE_SIZE),
3609
+ cursor: Cursor.optional()
3610
+ }));
3611
+ var UpdateThread = named("UpdateThread", z32.object({ isRead: z32.boolean().optional(), mailboxId: Id.optional() }));
3612
+
3613
+ // ../contract/src/threads/routes.ts
3614
+ var THREAD_NOT_FOUND = errors({
3615
+ thread_not_found: "The account has no message in the thread."
3616
+ });
3617
+ var ACTION_ERRORS = {
3618
+ ...ACCOUNT_NOT_FOUND,
3619
+ ...THREAD_NOT_FOUND,
3620
+ ...accountState(),
3621
+ ...PROVIDER_CHANGE
3622
+ };
3623
+ var PART_WAY = "A failure part way leaves the messages done so far changed; repeating the call finishes the rest.";
3624
+ var threads = {
3625
+ list: base.route({
3626
+ method: "GET",
3627
+ path: "/v1/accounts/{accountId}/threads",
3628
+ tags: ["Mail"],
3629
+ summary: "List threads, latest activity first",
3630
+ description: "Covers the account's sync window. With mailboxId, lists threads with a message in that mailbox, ordered by their latest message there; each summary still describes the whole thread."
3631
+ }).errors({ ...ACCOUNT_NOT_FOUND, ...INVALID_CURSOR, ...UNKNOWN_MAILBOX }).input(AccountIdInput.extend(ListThreadsQuery.shape)).output(ThreadSummaryPage),
3632
+ get: base.route({
3633
+ method: "GET",
3634
+ path: "/v1/accounts/{accountId}/threads/{threadId}",
3635
+ tags: ["Mail"],
3636
+ summary: "Get a thread with its messages, oldest first"
3637
+ }).errors({ ...ACCOUNT_NOT_FOUND, ...THREAD_NOT_FOUND }).input(ThreadIdInput).output(Thread),
3638
+ update: base.route({
3639
+ method: "PATCH",
3640
+ path: "/v1/accounts/{accountId}/threads/{threadId}",
3641
+ tags: ["Mail"],
3642
+ summary: "Mark a thread read or unread, or move it",
3643
+ description: `Applies the change to every message of the thread through the message operations; messages already in that state are skipped. mailboxId moves each message there (Gmail: replaces its folder-like labels). ${PART_WAY}`
3644
+ }).errors({ ...ACTION_ERRORS, ...UNKNOWN_MAILBOX }).input(ThreadIdInput.extend(UpdateThread.shape)).output(Thread),
3645
+ delete: base.route({
3646
+ method: "DELETE",
3647
+ path: "/v1/accounts/{accountId}/threads/{threadId}",
3648
+ tags: ["Mail"],
3649
+ summary: "Move a thread to Trash",
3650
+ description: `Moves every message of the thread that is not already there to Trash. ${PART_WAY}`,
3651
+ successStatus: 204
3652
+ }).errors(ACTION_ERRORS).input(ThreadIdInput).output(z33.void())
3653
+ };
3654
+
3655
+ // ../contract/src/usage/models.ts
3656
+ import * as z34 from "zod";
3657
+ var PlanModules = named("PlanModules", z34.object({
3658
+ calendar: z34.boolean(),
3659
+ contacts: z34.boolean(),
3660
+ tracking: z34.boolean(),
3661
+ ai: z34.boolean(),
3662
+ meetings: z34.boolean(),
3663
+ sharedMailboxes: z34.boolean()
3664
+ }));
3665
+ var PlanIncluded = named("PlanIncluded", z34.object({
3666
+ accounts: z34.number().int().min(0).nullable(),
3667
+ sendsPerMonth: z34.number().int().min(0).nullable(),
3668
+ aiCallsPerMonth: z34.number().int().min(0).nullable(),
3669
+ storageBytes: z34.number().int().min(0).nullable()
3670
+ }));
3671
+ var Amount = z34.number().int().min(0);
3672
+ var StripePrice = z34.string().regex(/^price_[A-Za-z0-9]+$/).max(MAX_STRIPE_ID_LENGTH).nullable();
3673
+ var PlanStripePrices = named("PlanStripePrices", z34.object({
3674
+ basePrice: StripePrice,
3675
+ accountPrice: StripePrice,
3676
+ sendsPrice: StripePrice,
3677
+ aiCallsPrice: StripePrice,
3678
+ storagePrice: StripePrice
3679
+ }));
3680
+ var PlanPricing = named("PlanPricing", z34.object({
3681
+ currency: z34.string().regex(/^[a-z]{3}$/),
3682
+ baseMonthly: Amount,
3683
+ perAccountMonthly: Amount,
3684
+ sendsPer1k: Amount,
3685
+ aiCallsPer1k: Amount,
3686
+ storagePerGbMonth: Amount,
3687
+ stripe: PlanStripePrices
3688
+ }));
3689
+ var PlanLimits = named("PlanLimits", z34.object({
3690
+ maxAccounts: z34.number().int().min(0).nullable(),
3691
+ sendsPerDay: z34.number().int().min(0).nullable(),
3692
+ apiCallsPerMinute: z34.number().int().min(1).nullable(),
3693
+ apiCallsPerMinutePerKey: z34.number().int().min(1).nullable(),
3694
+ webhookEndpoints: z34.number().int().min(0).nullable(),
3695
+ bodyStorageAllowed: z34.boolean(),
3696
+ storageBytes: z34.number().int().min(0).nullable(),
3697
+ modules: PlanModules,
3698
+ included: PlanIncluded,
3699
+ pricing: PlanPricing.nullable()
3700
+ }));
3701
+ var EffectivePlan = named("EffectivePlan", z34.object({ id: Id.nullable(), name: z34.string(), limits: PlanLimits }));
3702
+ var UsageDay = named("UsageDay", z34.object({
3703
+ day: DateOnly,
3704
+ liveAccounts: z34.number().int(),
3705
+ apiCalls: z34.record(z34.string(), z34.number().int()),
3706
+ sends: z34.number().int(),
3707
+ webhookDeliveries: z34.number().int(),
3708
+ aiCalls: z34.number().int(),
3709
+ storedBytes: z34.number().int().nullable()
3710
+ }));
3711
+ var CurrentUsage = named("CurrentUsage", z34.object({
3712
+ accounts: z34.number().int(),
3713
+ webhookEndpoints: z34.number().int(),
3714
+ sendsToday: z34.number().int(),
3715
+ storedBytes: z34.number().int()
3716
+ }));
3717
+ var Usage = named("Usage", z34.object({
3718
+ plan: EffectivePlan,
3719
+ current: CurrentUsage,
3720
+ days: z34.array(UsageDay)
3721
+ }));
3722
+ var DAY_MS2 = 86400000;
3723
+ var UsageQuery = named("UsageQuery", withDateRange(z34.object({ from: DateOnly.optional(), to: DateOnly.optional() }), {
3724
+ from: "from",
3725
+ to: "to",
3726
+ inclusive: true,
3727
+ maxSpanMs: (MAX_USAGE_RANGE_DAYS - 1) * DAY_MS2
3728
+ }).superRefine((q, ctx) => {
3729
+ if (q.from === undefined || q.to !== undefined)
3730
+ return;
3731
+ const span = Date.parse(new Date().toISOString().slice(0, 10)) - Date.parse(q.from);
3732
+ if (span < 0 || span > (MAX_USAGE_RANGE_DAYS - 1) * DAY_MS2)
3733
+ ctx.addIssue({
3734
+ code: "custom",
3735
+ path: ["from"],
3736
+ message: span < 0 ? "from must be on or before today when to is left out" : `The range may span at most ${MAX_USAGE_RANGE_DAYS} days`,
3737
+ params: { reason: "invalid_range" }
3738
+ });
3739
+ }));
3740
+
3741
+ // ../contract/src/usage/routes.ts
3742
+ var usage = {
3743
+ get: base.route({
3744
+ method: "GET",
3745
+ path: "/v1/usage",
3746
+ tags: ["Usage"],
3747
+ summary: "Read usage and plan limits",
3748
+ description: "Per UTC day: live accounts (sampled), API calls by scope, sends, webhook deliveries, AI calls and stored bytes (listed once a day). from and to default to the last 30 days; today's counters are live."
3749
+ }).errors(errors({
3750
+ invalid_range: `from is after to (or after today, with to left out), or the range spans more than ${MAX_USAGE_RANGE_DAYS} days.`
3751
+ })).input(UsageQuery).output(Usage)
3752
+ };
3753
+
3754
+ // ../contract/src/webhooks/routes.ts
3755
+ import * as z36 from "zod";
3756
+
3757
+ // ../contract/src/webhooks/models.ts
3758
+ import * as z35 from "zod";
3759
+ var ADDRESS_PATTERN = /^(?:[^\s@]+)?@[^\s@.]+(?:\.[^\s@.]+)+$/;
3760
+ var WEBHOOK_HEADERS = {
3761
+ id: "webhook-id",
3762
+ timestamp: "webhook-timestamp",
3763
+ signature: "webhook-signature"
3764
+ };
3765
+ var WEBHOOK_SECRET_PREFIX = "whsec_";
3766
+ var AddressPattern = z35.string().trim().toLowerCase().max(MAX_EMAIL_LENGTH).regex(ADDRESS_PATTERN, "Expected an address or @domain");
3767
+ var WebhookMessageFilters = named("WebhookMessageFilters", z35.object({
3768
+ mailboxIds: z35.array(Id),
3769
+ mailboxRoles: z35.array(MailboxRole),
3770
+ from: z35.array(z35.string()),
3771
+ to: z35.array(z35.string()),
3772
+ subjectContains: z35.array(z35.string()),
3773
+ hasAttachments: z35.boolean().nullable(),
3774
+ direction: MessageDirection.nullable()
3775
+ }));
3776
+ var WebhookFilters = named("WebhookFilters", z35.object({
3777
+ eventTypes: z35.array(EventType),
3778
+ accountIds: z35.array(Id),
3779
+ message: WebhookMessageFilters
3780
+ }));
3781
+ var WebhookMessageFiltersInput = named("WebhookMessageFiltersInput", z35.object({
3782
+ mailboxIds: z35.array(Id).max(MAX_WEBHOOK_FILTER_VALUES).optional(),
3783
+ mailboxRoles: z35.array(MailboxRole).max(MailboxRole.options.length).optional(),
3784
+ from: z35.array(AddressPattern).max(MAX_WEBHOOK_FILTER_VALUES).optional(),
3785
+ to: z35.array(AddressPattern).max(MAX_WEBHOOK_FILTER_VALUES).optional(),
3786
+ subjectContains: z35.array(z35.string().trim().min(1).max(MAX_SUBJECT_FILTER_LENGTH)).max(MAX_WEBHOOK_FILTER_VALUES).optional(),
3787
+ hasAttachments: z35.boolean().nullable().optional(),
3788
+ direction: MessageDirection.nullable().optional()
3789
+ }));
3790
+ var WebhookFiltersInput = named("WebhookFiltersInput", z35.object({
3791
+ eventTypes: z35.array(EventType).max(MAX_WEBHOOK_FILTER_VALUES).optional(),
3792
+ accountIds: z35.array(Id).max(MAX_WEBHOOK_FILTER_VALUES).optional(),
3793
+ message: WebhookMessageFiltersInput.optional()
3794
+ }));
3795
+ var WebhookEndpoint = named("WebhookEndpoint", z35.object({
3796
+ id: Id,
3797
+ url: z35.url(),
3798
+ description: z35.string().nullable(),
3799
+ filters: WebhookFilters,
3800
+ isEnabled: z35.boolean(),
3801
+ disabledReason: z35.string().nullable(),
3802
+ lastSuccessAt: Timestamp.nullable(),
3803
+ failingSince: Timestamp.nullable(),
3804
+ previousSecretExpiresAt: Timestamp.nullable(),
3805
+ createdAt: Timestamp
3806
+ }));
3807
+ var WebhookEndpointList = List(WebhookEndpoint);
3808
+ var WebhookEndpointWithSecret = named("WebhookEndpointWithSecret", z35.object({ endpoint: WebhookEndpoint, secret: z35.string() }));
3809
+ var WebhookSecret = named("WebhookSecret", z35.object({
3810
+ secret: z35.string(),
3811
+ previousSecretExpiresAt: Timestamp.nullable()
3812
+ }));
3813
+ var RotateWebhookSecretRequest = named("RotateWebhookSecretRequest", z35.object({
3814
+ graceSeconds: z35.number().int().min(0).max(MAX_WEBHOOK_SECRET_GRACE_SECONDS).default(DEFAULT_WEBHOOK_SECRET_GRACE_SECONDS)
3815
+ }));
3816
+ var WebhookEndpointIdInput = named("WebhookEndpointIdInput", z35.object({ endpointId: Id }));
3817
+ var CreateWebhookEndpoint = named("CreateWebhookEndpoint", z35.object({
3818
+ url: z35.url(),
3819
+ description: z35.string().max(MAX_NOTE_LENGTH).optional(),
3820
+ filters: WebhookFiltersInput.optional()
3821
+ }));
3822
+ var UpdateWebhookEndpoint = named("UpdateWebhookEndpoint", z35.object({
3823
+ url: z35.url().optional(),
3824
+ description: z35.string().max(MAX_NOTE_LENGTH).nullable().optional(),
3825
+ filters: WebhookFiltersInput.optional(),
3826
+ isEnabled: z35.boolean().optional()
3827
+ }));
3828
+ var SendTestEventRequest = named("SendTestEventRequest", z35.object({
3829
+ eventType: EventType.default(TEST_EVENT_TYPE),
3830
+ accountId: Id.optional(),
3831
+ data: z35.looseObject({}).optional()
3832
+ }).superRefine((input, ctx) => {
3833
+ if (input.data === undefined)
3834
+ return;
3835
+ const parsed = EVENTS[input.eventType].data.safeParse(input.data);
3836
+ if (parsed.success)
3837
+ return;
3838
+ for (const issue of parsed.error.issues)
3839
+ ctx.addIssue({ code: "custom", message: issue.message, path: ["data", ...issue.path] });
3840
+ }));
3841
+ var TestEventSent = named("TestEventSent", z35.object({ eventId: EventId }));
3842
+ var WebhookAttemptResult = named("WebhookAttemptResult", z35.enum(["succeeded", "failed"]));
3843
+ var WebhookAttempt = named("WebhookAttempt", z35.object({
3844
+ id: Id,
3845
+ eventId: EventId,
3846
+ eventType: EventType,
3847
+ accountId: Id.nullable(),
3848
+ attempt: z35.number().int(),
3849
+ statusCode: z35.number().int().nullable(),
3850
+ error: z35.string().nullable(),
3851
+ responseExcerpt: z35.string().nullable(),
3852
+ durationMs: z35.number().int(),
3853
+ isReplay: z35.boolean(),
3854
+ isTest: z35.boolean(),
3855
+ occurredAt: Timestamp
3856
+ }));
3857
+ var WebhookAttemptPage = Page(WebhookAttempt);
3858
+ var ListWebhookAttemptsQuery = named("ListWebhookAttemptsQuery", z35.object({
3859
+ limit: pageLimit(MAX_WEBHOOK_ATTEMPT_PAGE_SIZE, DEFAULT_WEBHOOK_ATTEMPT_PAGE_SIZE),
3860
+ cursor: Cursor.optional(),
3861
+ eventType: EventType.optional(),
3862
+ eventId: EventId.optional(),
3863
+ accountId: Id.optional(),
3864
+ result: WebhookAttemptResult.optional(),
3865
+ since: QueryTimestamp.optional(),
3866
+ until: QueryTimestamp.optional()
3867
+ }));
3868
+ var ListWebhookAttemptsInput = withDateRange(WebhookEndpointIdInput.extend(ListWebhookAttemptsQuery.shape), { from: "since", to: "until" });
3869
+ var ReplayEventInput = named("ReplayEventInput", WebhookEndpointIdInput.extend({ eventId: EventId }));
3870
+ var ReplayRangeRequest = named("ReplayRangeRequest", withDateRange(z35.object({ since: QueryTimestamp, until: QueryTimestamp }), {
3871
+ from: "since",
3872
+ to: "until"
3873
+ }));
3874
+ var ReplayResult = named("ReplayResult", z35.object({
3875
+ queued: z35.number().int(),
3876
+ alreadyQueued: z35.number().int()
3877
+ }));
3878
+ var FailedDeliveriesQuery = named("FailedDeliveriesQuery", withDateRange(z35.object({ since: QueryTimestamp.optional(), until: QueryTimestamp.optional() }), {
3879
+ from: "since",
3880
+ to: "until"
3881
+ }));
3882
+ var FailedDeliveryCount = named("FailedDeliveryCount", z35.object({
3883
+ count: z35.number().int()
3884
+ }));
3885
+
3886
+ // ../contract/src/webhooks/routes.ts
3887
+ var ENDPOINT_NOT_FOUND = errors({
3888
+ webhook_endpoint_not_found: "The endpoint does not exist."
3889
+ });
3890
+ var ENDPOINT_DISABLED = errors({ endpoint_disabled: "The endpoint is disabled." });
3891
+ var HTTPS_REQUIRED = errors({ https_required: "url is not https." });
3892
+ var WEBHOOK_FILTER_ERRORS = errors({
3893
+ unknown_account: "filters.accountIds names an account the tenant does not have; data.ids lists them.",
3894
+ unknown_mailbox: "filters.message.mailboxIds names a mailbox the tenant does not have; data.ids lists them.",
3895
+ mailbox_outside_accounts: "filters.message.mailboxIds names a mailbox of an account that filters.accountIds leaves out."
3896
+ });
3897
+ var webhooks = {
3898
+ list: base.route({
3899
+ method: "GET",
3900
+ path: "/v1/webhooks",
3901
+ tags: ["Webhooks"],
3902
+ summary: "List webhook endpoints"
3903
+ }).output(WebhookEndpointList),
3904
+ create: base.route({
3905
+ method: "POST",
3906
+ path: "/v1/webhooks",
3907
+ tags: ["Webhooks"],
3908
+ summary: "Create a webhook endpoint",
3909
+ description: "The signing secret is returned once.",
3910
+ successStatus: 201
3911
+ }).errors({
3912
+ ...HTTPS_REQUIRED,
3913
+ ...WEBHOOK_FILTER_ERRORS,
3914
+ ...errors({
3915
+ plan_limit_webhooks: "The tenant already has as many endpoints as its plan allows."
3916
+ })
3917
+ }).input(CreateWebhookEndpoint).output(WebhookEndpointWithSecret),
3918
+ update: base.route({
3919
+ method: "PATCH",
3920
+ path: "/v1/webhooks/{endpointId}",
3921
+ tags: ["Webhooks"],
3922
+ summary: "Update a webhook endpoint",
3923
+ description: "The mailbox check runs whenever filters.message.mailboxIds or filters.accountIds changes."
3924
+ }).errors({ ...ENDPOINT_NOT_FOUND, ...HTTPS_REQUIRED, ...WEBHOOK_FILTER_ERRORS }).input(WebhookEndpointIdInput.extend(UpdateWebhookEndpoint.shape)).output(WebhookEndpoint),
3925
+ delete: base.route({
3926
+ method: "DELETE",
3927
+ path: "/v1/webhooks/{endpointId}",
3928
+ tags: ["Webhooks"],
3929
+ summary: "Delete a webhook endpoint",
3930
+ successStatus: 204
3931
+ }).errors(ENDPOINT_NOT_FOUND).input(WebhookEndpointIdInput).output(z36.void()),
3932
+ rotateSecret: base.route({
3933
+ method: "POST",
3934
+ path: "/v1/webhooks/{endpointId}/rotate-secret",
3935
+ tags: ["Webhooks"],
3936
+ summary: "Rotate the signing secret",
3937
+ description: `Deliveries are signed with the new secret at once. For graceSeconds (default ${DEFAULT_WEBHOOK_SECRET_GRACE_SECONDS}, at most ${MAX_WEBHOOK_SECRET_GRACE_SECONDS}) they also carry a signature by the old one, in the same webhook-signature header, so receivers can switch secrets without refusing a delivery; 0 stops the old secret at once. A rotation during another's grace ends that grace: only the secret it replaces is kept.`
3938
+ }).errors(ENDPOINT_NOT_FOUND).input(RotateWebhookSecretRequest.safeExtend(WebhookEndpointIdInput.shape)).output(WebhookSecret),
3939
+ test: base.route({
3940
+ method: "POST",
3941
+ path: "/v1/webhooks/{endpointId}/test",
3942
+ tags: ["Webhooks"],
3943
+ summary: "Send a test event",
3944
+ description: `Queues one event of eventType for delivery to this endpoint only, whatever its filters, signed like any other, with "isTest": true in its body; its attempts show in the delivery log. It carries the sample data for its type unless data is given, which must match the type's data. Test events are never in the change feed.`,
3945
+ successStatus: 202
3946
+ }).errors({
3947
+ ...ENDPOINT_NOT_FOUND,
3948
+ ...ENDPOINT_DISABLED,
3949
+ ...errors({
3950
+ unknown_account: "accountId names an account the tenant does not have; data.ids lists it."
3951
+ })
3952
+ }).input(SendTestEventRequest.safeExtend(WebhookEndpointIdInput.shape)).output(TestEventSent),
3953
+ attempts: base.route({
3954
+ method: "GET",
3955
+ path: "/v1/webhooks/{endpointId}/attempts",
3956
+ tags: ["Webhooks"],
3957
+ summary: "Read the delivery log",
3958
+ description: "Delivery attempts to this endpoint, newest first: of the last 500 within 7 days, those matching the filters. A cursor stays valid while its attempt is in the log."
3959
+ }).errors({
3960
+ ...ENDPOINT_NOT_FOUND,
3961
+ ...INVALID_CURSOR,
3962
+ ...errors({ invalid_range: "until is not after since." })
3963
+ }).input(ListWebhookAttemptsInput).output(WebhookAttemptPage),
3964
+ replay: base.route({
3965
+ method: "POST",
3966
+ path: "/v1/webhooks/{endpointId}/replay",
3967
+ tags: ["Webhooks"],
3968
+ summary: "Replay a time range",
3969
+ description: `Queues every event in [since, until) that the endpoint would receive with its current filters again, with the same id and body as the first delivery and a fresh signature. Events the endpoint already has queued are not queued twice. Events past the tenant's eventRetentionDays cannot be replayed.`,
3970
+ successStatus: 202
3971
+ }).errors({
3972
+ ...ENDPOINT_NOT_FOUND,
3973
+ ...ENDPOINT_DISABLED,
3974
+ ...errors({
3975
+ invalid_range: "until is not after since.",
3976
+ replay_range_too_large: `The range holds more than ${MAX_REPLAY_EVENTS} events.`
3977
+ })
3978
+ }).input(ReplayRangeRequest.safeExtend(WebhookEndpointIdInput.shape)).output(ReplayResult),
3979
+ replayEvent: base.route({
3980
+ method: "POST",
3981
+ path: "/v1/webhooks/{endpointId}/events/{eventId}/replay",
3982
+ tags: ["Webhooks"],
3983
+ summary: "Replay one event",
3984
+ description: `Queues the event again for this endpoint, whatever its filters, with the same id and body as the first delivery and a fresh signature; not twice if the endpoint already has it queued. Events past the tenant's eventRetentionDays cannot be replayed.`,
3985
+ successStatus: 202
3986
+ }).errors({
3987
+ ...ENDPOINT_NOT_FOUND,
3988
+ ...ENDPOINT_DISABLED,
3989
+ ...errors({ event_not_found: "The event is unknown or no longer retained." })
3990
+ }).input(ReplayEventInput).output(ReplayResult),
3991
+ countFailed: base.route({
3992
+ method: "GET",
3993
+ path: "/v1/webhooks/{endpointId}/failed-deliveries/count",
3994
+ tags: ["Webhooks"],
3995
+ summary: "Count failed deliveries",
3996
+ description: "How many events replay-failed would queue now: those the endpoint gave up on (retries exhausted, or given up when it was disabled) that are still retained and not queued again, optionally only those created in [since, until)."
3997
+ }).errors({
3998
+ ...ENDPOINT_NOT_FOUND,
3999
+ ...errors({ invalid_range: "until is not after since." })
4000
+ }).input(FailedDeliveriesQuery.safeExtend(WebhookEndpointIdInput.shape)).output(FailedDeliveryCount),
4001
+ replayFailed: base.route({
4002
+ method: "POST",
4003
+ path: "/v1/webhooks/{endpointId}/replay-failed",
4004
+ tags: ["Webhooks"],
4005
+ summary: "Replay failed deliveries",
4006
+ description: `Queues every event the endpoint gave up on again, once each, optionally only those created in [since, until), with the same id and body as the first delivery and a fresh signature. The failed deliveries are replaced by the replays (their attempts stay in the delivery log), so calling again does not send them twice. Events past the tenant's eventRetentionDays cannot be replayed.`,
4007
+ successStatus: 202
4008
+ }).errors({
4009
+ ...ENDPOINT_NOT_FOUND,
4010
+ ...ENDPOINT_DISABLED,
4011
+ ...errors({
4012
+ invalid_range: "until is not after since.",
4013
+ replay_range_too_large: `More than ${MAX_REPLAY_EVENTS} events failed; replay a narrower range.`
4014
+ })
4015
+ }).input(FailedDeliveriesQuery.safeExtend(WebhookEndpointIdInput.shape)).output(ReplayResult)
4016
+ };
4017
+
4018
+ // ../contract/src/router.ts
4019
+ var v1Contract = {
4020
+ accounts,
4021
+ mailboxes,
4022
+ messages,
4023
+ search,
4024
+ threads,
4025
+ drafts,
4026
+ outbox,
4027
+ mailMerges,
4028
+ templates,
4029
+ templateRenders,
4030
+ suppressions,
4031
+ suppressionReviews,
4032
+ identities,
4033
+ mailSettings,
4034
+ calendars,
4035
+ calendarEvents,
4036
+ availability,
4037
+ meetingConnections,
4038
+ meetingConnectSessions,
4039
+ meetingApps,
4040
+ contacts,
4041
+ contactFolders,
4042
+ webhooks,
4043
+ changes,
4044
+ settings,
4045
+ oauthApps,
4046
+ connectSessions,
4047
+ oauthAuthorizations,
4048
+ orgConnections,
4049
+ sharedMailboxes,
4050
+ imapAccounts,
4051
+ imapSettings,
4052
+ autoconfig,
4053
+ hostedConnectSessions,
4054
+ hostedMeetingConnectSessions,
4055
+ exports,
4056
+ usage,
4057
+ ai,
4058
+ auditLog
4059
+ };
4060
+
4061
+ // ../contract/src/webhooks/signing.ts
4062
+ var WEBHOOK_TOLERANCE_SECONDS = 300;
4063
+ var WEBHOOK_VERIFICATION_MESSAGES = {
4064
+ missing_headers: "The webhook headers are missing",
4065
+ timestamp: "The webhook timestamp is outside the tolerance",
4066
+ signature: "No webhook signature matches the secret",
4067
+ payload: "The webhook body is not a workwae event"
4068
+ };
4069
+
4070
+ class WebhookVerificationError extends Error {
4071
+ failure;
4072
+ constructor(failure, message = WEBHOOK_VERIFICATION_MESSAGES[failure]) {
4073
+ super(message);
4074
+ this.failure = failure;
4075
+ this.name = "WebhookVerificationError";
4076
+ }
4077
+ }
4078
+ var encoder = new TextEncoder;
4079
+ function header(headers, name) {
4080
+ if (headers instanceof Headers)
4081
+ return headers.get(name);
4082
+ for (const [key, value] of Object.entries(headers))
4083
+ if (key.toLowerCase() === name)
4084
+ return Array.isArray(value) ? value.join(" ") : value ?? null;
4085
+ return null;
4086
+ }
4087
+ function fromBase64(text) {
4088
+ try {
4089
+ return Uint8Array.from(atob(text), (c) => c.charCodeAt(0));
4090
+ } catch {
4091
+ return null;
4092
+ }
4093
+ }
4094
+ async function mac(secret, id, timestamp, body) {
4095
+ const keyBytes = fromBase64(secret.startsWith(WEBHOOK_SECRET_PREFIX) ? secret.slice(WEBHOOK_SECRET_PREFIX.length) : secret);
4096
+ if (!keyBytes || keyBytes.length === 0)
4097
+ throw new TypeError("The webhook secret is not base64");
4098
+ const key = await crypto.subtle.importKey("raw", keyBytes, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
4099
+ const prefix = encoder.encode(`${id}.${timestamp}.`);
4100
+ const payload = typeof body === "string" ? encoder.encode(body) : new Uint8Array(body);
4101
+ const signed = new Uint8Array(prefix.length + payload.length);
4102
+ signed.set(prefix);
4103
+ signed.set(payload, prefix.length);
4104
+ return new Uint8Array(await crypto.subtle.sign("HMAC", key, signed));
4105
+ }
4106
+ function equalInConstantTime(a, b) {
4107
+ let difference = 0;
4108
+ for (let i = 0;i < a.length; i++)
4109
+ difference |= (a[i] ?? 0) ^ (b[i] ?? 0);
4110
+ return difference === 0;
4111
+ }
4112
+ async function checkWebhookSignature(secret, headers, body, options = {}) {
4113
+ const id = header(headers, WEBHOOK_HEADERS.id);
4114
+ const timestamp = header(headers, WEBHOOK_HEADERS.timestamp);
4115
+ const signatures = header(headers, WEBHOOK_HEADERS.signature);
4116
+ if (!id || !timestamp || !signatures)
4117
+ return "missing_headers";
4118
+ if (!/^[0-9]{1,15}$/.test(timestamp))
4119
+ return "timestamp";
4120
+ const nowSeconds = (options.now ?? new Date).getTime() / 1000;
4121
+ const tolerance = options.toleranceSeconds ?? WEBHOOK_TOLERANCE_SECONDS;
4122
+ if (Math.abs(nowSeconds - Number(timestamp)) > tolerance)
4123
+ return "timestamp";
4124
+ const expected = await mac(secret, id, timestamp, body);
4125
+ for (const candidate of signatures.split(" ", MAX_SIGNATURE_CANDIDATES)) {
4126
+ const [version, value] = candidate.split(",", 2);
4127
+ if (version !== "v1" || !value)
4128
+ continue;
4129
+ const given = fromBase64(value);
4130
+ if (given?.length === expected.length && equalInConstantTime(given, expected))
4131
+ return null;
4132
+ }
4133
+ return "signature";
4134
+ }
4135
+ async function verifyWebhook(secret, headers, body, options = {}) {
4136
+ return await checkWebhookSignature(secret, headers, body, options) === null;
4137
+ }
4138
+ // src/client.ts
4139
+ import { createORPCClient, ORPCError } from "@orpc/client";
4140
+ import { ResponseValidationPlugin } from "@orpc/contract/plugins";
4141
+ import { OpenAPILink } from "@orpc/openapi-client/fetch";
4142
+ var DEFAULT_BASE_URL = "https://api.workwae.com";
4143
+ function parseRetryAfter(value) {
4144
+ if (value === null)
4145
+ return null;
4146
+ const trimmed = value.trim();
4147
+ if (/^\d+(\.\d+)?$/.test(trimmed))
4148
+ return Math.round(Number(trimmed) * 1000);
4149
+ const at = Date.parse(trimmed);
4150
+ return Number.isNaN(at) ? null : Math.max(0, at - Date.now());
4151
+ }
4152
+ function sleep(ms, signal) {
4153
+ return new Promise((resolve, reject) => {
4154
+ if (signal?.aborted) {
4155
+ reject(signal.reason);
4156
+ return;
4157
+ }
4158
+ const onAbort = () => {
4159
+ clearTimeout(timer);
4160
+ reject(signal?.reason);
4161
+ };
4162
+ const timer = setTimeout(() => {
4163
+ signal?.removeEventListener("abort", onAbort);
4164
+ resolve();
4165
+ }, ms);
4166
+ signal?.addEventListener("abort", onAbort, { once: true });
4167
+ });
4168
+ }
4169
+ var BACKOFF_START_MS = 500;
4170
+ var SEEN = Symbol("workwae.seen");
4171
+ function retrying(send, options = {}) {
4172
+ const maxRetries = options.maxRetries ?? 2;
4173
+ const maxDelayMs = options.maxDelayMs ?? 60000;
4174
+ const wait = options.wait ?? sleep;
4175
+ return async (request, init, { signal, context }) => {
4176
+ if (request.method === "POST" && !request.headers.has("idempotency-key"))
4177
+ request.headers.set("idempotency-key", crypto.randomUUID());
4178
+ for (let attempt = 0;; attempt++) {
4179
+ const last = attempt >= maxRetries;
4180
+ const response = await send(last ? request : request.clone(), init);
4181
+ const seen = context[SEEN];
4182
+ if (seen)
4183
+ seen.requestId = response.headers.get("x-request-id") ?? undefined;
4184
+ if (last || response.status !== 429 && response.status !== 503)
4185
+ return response;
4186
+ const retryAfterMs = parseRetryAfter(response.headers.get("retry-after"));
4187
+ if (retryAfterMs !== null && retryAfterMs > maxDelayMs)
4188
+ return response;
4189
+ await response.body?.cancel();
4190
+ const backoff = BACKOFF_START_MS * 2 ** attempt * (0.5 + Math.random() / 2);
4191
+ await wait(retryAfterMs ?? Math.min(backoff, maxDelayMs), signal);
4192
+ signal?.throwIfAborted();
4193
+ }
4194
+ };
4195
+ }
4196
+ function createClient(options) {
4197
+ const { apiKey } = options;
4198
+ const link = new OpenAPILink(v1Contract, {
4199
+ url: options.baseUrl ?? DEFAULT_BASE_URL,
4200
+ headers: async ({ context }) => {
4201
+ const key = typeof apiKey === "function" ? await apiKey() : apiKey;
4202
+ return {
4203
+ ...options.headers,
4204
+ ...key && { authorization: `Bearer ${key}` },
4205
+ ...context.idempotencyKey && { "idempotency-key": context.idempotencyKey }
4206
+ };
4207
+ },
4208
+ fetch: retrying(options.fetch ?? ((request, init) => fetch(request, init)), options.retry),
4209
+ interceptors: [
4210
+ async ({ next, ...call }) => {
4211
+ const seen = { requestId: undefined };
4212
+ try {
4213
+ return await next({ ...call, context: { ...call.context, [SEEN]: seen } });
4214
+ } catch (error) {
4215
+ if (error instanceof ORPCError)
4216
+ error.requestId = seen.requestId;
4217
+ throw error;
4218
+ }
4219
+ }
4220
+ ],
4221
+ clientInterceptors: [
4222
+ async ({ next }) => {
4223
+ const response = await next();
4224
+ if (response.status >= 400) {
4225
+ const read = response.body;
4226
+ response.body = () => read().catch(() => {
4227
+ return;
4228
+ });
4229
+ }
4230
+ return response;
4231
+ }
4232
+ ],
4233
+ plugins: [new ResponseValidationPlugin(v1Contract)]
4234
+ });
4235
+ return createORPCClient(link);
4236
+ }
4237
+ // src/errors.ts
4238
+ import { ORPCError as ORPCError2 } from "@orpc/client";
4239
+ import { isDefinedError, ORPCError as ORPCError3, safe } from "@orpc/client";
4240
+ function isApiError(error, reason) {
4241
+ return error instanceof ORPCError2 && isErrorReason(error.code) && (reason === undefined || error.code === reason);
4242
+ }
4243
+ // src/pagination.ts
4244
+ function pages(list, input, options) {
4245
+ return followCursor((cursor) => list({ ...input, cursor }, options), input.cursor, (page) => page.nextCursor ?? undefined);
4246
+ }
4247
+ async function* paginate(list, input, options) {
4248
+ for await (const page of pages(list, input, options))
4249
+ yield* page.items;
4250
+ }
4251
+
4252
+ class ChangeFeed {
4253
+ list;
4254
+ input;
4255
+ options;
4256
+ #cursor;
4257
+ constructor(list, input, options) {
4258
+ this.list = list;
4259
+ this.input = input;
4260
+ this.options = options;
4261
+ this.#cursor = input.cursor;
4262
+ }
4263
+ get cursor() {
4264
+ return this.#cursor;
4265
+ }
4266
+ async* [Symbol.asyncIterator]() {
4267
+ const feed = followCursor((cursor) => this.list({ ...this.input, cursor }, this.options), this.#cursor, (page) => page.items.length === 0 ? undefined : page.nextCursor);
4268
+ for await (const page of feed) {
4269
+ yield* page.items;
4270
+ this.#cursor = page.nextCursor;
4271
+ }
4272
+ }
4273
+ }
4274
+ function changes2(list, input, options) {
4275
+ return new ChangeFeed(list, input, options);
4276
+ }
4277
+ // src/webhooks.ts
4278
+ import * as z37 from "zod";
4279
+ var LaterEvent = z37.looseObject({
4280
+ id: EventId,
4281
+ type: z37.string(),
4282
+ accountId: Id.nullable(),
4283
+ createdAt: Timestamp,
4284
+ data: z37.looseObject({}),
4285
+ isTest: z37.literal(true).exactOptional()
4286
+ });
4287
+ async function parseWebhook(secret, headers, body, options = {}) {
4288
+ const failure = await checkWebhookSignature(secret, headers, body, options);
4289
+ if (failure)
4290
+ throw new WebhookVerificationError(failure);
4291
+ let json;
4292
+ try {
4293
+ json = JSON.parse(typeof body === "string" ? body : new TextDecoder().decode(body));
4294
+ } catch {
4295
+ throw new WebhookVerificationError("payload", "The webhook body is not JSON");
4296
+ }
4297
+ const type = typeof json === "object" && json !== null && "type" in json ? json.type : undefined;
4298
+ if (EventType.safeParse(type).success) {
4299
+ const parsed = Event.safeParse(json);
4300
+ if (parsed.success)
4301
+ return { ...json, ...parsed.data, known: true };
4302
+ } else {
4303
+ const later = LaterEvent.safeParse(json);
4304
+ if (later.success)
4305
+ return { ...later.data, known: false };
4306
+ }
4307
+ throw new WebhookVerificationError("payload");
4308
+ }
4309
+ export {
4310
+ ChangeFeed,
4311
+ EVENT_TYPES,
4312
+ MAX_SIGNATURE_CANDIDATES,
4313
+ MAX_TEMPLATE_BODY_LENGTH,
4314
+ MAX_TEMPLATE_DESCRIPTION_LENGTH,
4315
+ MAX_TEMPLATE_NAME_LENGTH,
4316
+ MAX_TEMPLATE_PREVIEW_TEXT_LENGTH,
4317
+ MAX_TEMPLATE_SUBJECT_LENGTH,
4318
+ ORPCError3 as ORPCError,
4319
+ WEBHOOK_TOLERANCE_SECONDS,
4320
+ WebhookVerificationError,
4321
+ changes2 as changes,
4322
+ checkWebhookSignature,
4323
+ createClient,
4324
+ isApiError,
4325
+ isDefinedError,
4326
+ pages,
4327
+ paginate,
4328
+ parseWebhook,
4329
+ safe,
4330
+ verifyWebhook
4331
+ };
4332
+
4333
+ //# debugId=2876276784BC8B1464756E2164756E21
4334
+ //# sourceMappingURL=index.js.map