@dawnlit/outlook-bridge 1.1.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +150 -183
  2. package/dist/backend.d.ts +151 -0
  3. package/dist/backend.js +3 -0
  4. package/dist/backend.js.map +1 -0
  5. package/dist/bridge.d.ts +56 -0
  6. package/dist/bridge.js +507 -0
  7. package/dist/bridge.js.map +1 -0
  8. package/dist/errors.d.ts +81 -43
  9. package/dist/errors.js +98 -52
  10. package/dist/errors.js.map +1 -1
  11. package/dist/index.d.ts +10 -10
  12. package/dist/index.js +25 -52
  13. package/dist/index.js.map +1 -1
  14. package/dist/mac/accounts.d.ts +7 -2
  15. package/dist/mac/accounts.js +23 -6
  16. package/dist/mac/accounts.js.map +1 -1
  17. package/dist/mac/attachments.d.ts +5 -25
  18. package/dist/mac/attachments.js +40 -99
  19. package/dist/mac/attachments.js.map +1 -1
  20. package/dist/mac/bounces.d.ts +12 -27
  21. package/dist/mac/bounces.js +95 -112
  22. package/dist/mac/bounces.js.map +1 -1
  23. package/dist/mac/cleanup.d.ts +11 -25
  24. package/dist/mac/cleanup.js +50 -76
  25. package/dist/mac/cleanup.js.map +1 -1
  26. package/dist/mac/drafts.d.ts +13 -34
  27. package/dist/mac/drafts.js +95 -169
  28. package/dist/mac/drafts.js.map +1 -1
  29. package/dist/mac/folders.d.ts +10 -21
  30. package/dist/mac/folders.js +33 -47
  31. package/dist/mac/folders.js.map +1 -1
  32. package/dist/mac/index.d.ts +2 -22
  33. package/dist/mac/index.js +19 -81
  34. package/dist/mac/index.js.map +1 -1
  35. package/dist/mac/profile.d.ts +13 -0
  36. package/dist/mac/profile.js +149 -0
  37. package/dist/mac/profile.js.map +1 -0
  38. package/dist/mac/read.d.ts +18 -38
  39. package/dist/mac/read.js +154 -210
  40. package/dist/mac/read.js.map +1 -1
  41. package/dist/mac/run.d.ts +35 -39
  42. package/dist/mac/run.js +97 -102
  43. package/dist/mac/run.js.map +1 -1
  44. package/dist/mac/scripts.d.ts +78 -86
  45. package/dist/mac/scripts.js +125 -115
  46. package/dist/mac/scripts.js.map +1 -1
  47. package/dist/mac/send.d.ts +10 -20
  48. package/dist/mac/send.js +64 -86
  49. package/dist/mac/send.js.map +1 -1
  50. package/dist/mac/signatures.d.ts +2 -2
  51. package/dist/mac/signatures.js +9 -11
  52. package/dist/mac/signatures.js.map +1 -1
  53. package/dist/mac/templates.d.ts +14 -26
  54. package/dist/mac/templates.js +75 -113
  55. package/dist/mac/templates.js.map +1 -1
  56. package/dist/mail.d.ts +77 -45
  57. package/dist/mail.js +166 -73
  58. package/dist/mail.js.map +1 -1
  59. package/dist/runtime.d.ts +76 -43
  60. package/dist/runtime.js +103 -48
  61. package/dist/runtime.js.map +1 -1
  62. package/dist/shared/args.d.ts +48 -0
  63. package/dist/shared/args.js +216 -0
  64. package/dist/shared/args.js.map +1 -0
  65. package/dist/shared/attachmentMatch.d.ts +28 -16
  66. package/dist/shared/attachmentMatch.js +60 -22
  67. package/dist/shared/attachmentMatch.js.map +1 -1
  68. package/dist/shared/bounceReport.d.ts +11 -0
  69. package/dist/shared/bounceReport.js +45 -0
  70. package/dist/shared/bounceReport.js.map +1 -0
  71. package/dist/shared/bounceRules.d.ts +38 -10
  72. package/dist/shared/bounceRules.js +50 -32
  73. package/dist/shared/bounceRules.js.map +1 -1
  74. package/dist/shared/exec.d.ts +21 -0
  75. package/dist/shared/exec.js +47 -0
  76. package/dist/shared/exec.js.map +1 -0
  77. package/dist/shared/json.d.ts +12 -6
  78. package/dist/shared/json.js +29 -23
  79. package/dist/shared/json.js.map +1 -1
  80. package/dist/shared/locate.d.ts +39 -0
  81. package/dist/shared/locate.js +129 -0
  82. package/dist/shared/locate.js.map +1 -0
  83. package/dist/shared/replyBody.d.ts +42 -32
  84. package/dist/shared/replyBody.js +84 -71
  85. package/dist/shared/replyBody.js.map +1 -1
  86. package/dist/templateBody.d.ts +63 -0
  87. package/dist/{outlookTemplateSections.js → templateBody.js} +72 -72
  88. package/dist/templateBody.js.map +1 -0
  89. package/dist/types.d.ts +360 -167
  90. package/dist/types.js +8 -9
  91. package/dist/types.js.map +1 -1
  92. package/dist/windows/accounts.d.ts +7 -12
  93. package/dist/windows/accounts.js +19 -34
  94. package/dist/windows/accounts.js.map +1 -1
  95. package/dist/windows/attachments.d.ts +9 -24
  96. package/dist/windows/attachments.js +94 -119
  97. package/dist/windows/attachments.js.map +1 -1
  98. package/dist/windows/bounces.d.ts +13 -26
  99. package/dist/windows/bounces.js +168 -200
  100. package/dist/windows/bounces.js.map +1 -1
  101. package/dist/windows/cleanup.d.ts +19 -23
  102. package/dist/windows/cleanup.js +92 -111
  103. package/dist/windows/cleanup.js.map +1 -1
  104. package/dist/windows/drafts.d.ts +16 -35
  105. package/dist/windows/drafts.js +167 -253
  106. package/dist/windows/drafts.js.map +1 -1
  107. package/dist/windows/folders.d.ts +8 -13
  108. package/dist/windows/folders.js +44 -52
  109. package/dist/windows/folders.js.map +1 -1
  110. package/dist/windows/index.d.ts +2 -27
  111. package/dist/windows/index.js +11 -66
  112. package/dist/windows/index.js.map +1 -1
  113. package/dist/windows/read.d.ts +24 -60
  114. package/dist/windows/read.js +261 -306
  115. package/dist/windows/read.js.map +1 -1
  116. package/dist/windows/run.d.ts +39 -39
  117. package/dist/windows/run.js +151 -111
  118. package/dist/windows/run.js.map +1 -1
  119. package/dist/windows/scripts.d.ts +65 -55
  120. package/dist/windows/scripts.js +109 -85
  121. package/dist/windows/scripts.js.map +1 -1
  122. package/dist/windows/send.d.ts +6 -11
  123. package/dist/windows/send.js +68 -88
  124. package/dist/windows/send.js.map +1 -1
  125. package/dist/windows/signatures.d.ts +4 -10
  126. package/dist/windows/signatures.js +30 -37
  127. package/dist/windows/signatures.js.map +1 -1
  128. package/dist/windows/templates.d.ts +12 -18
  129. package/dist/windows/templates.js +122 -149
  130. package/dist/windows/templates.js.map +1 -1
  131. package/package.json +4 -3
  132. package/dist/OutlookService.d.ts +0 -43
  133. package/dist/OutlookService.js +0 -137
  134. package/dist/OutlookService.js.map +0 -1
  135. package/dist/outlookTemplateSections.d.ts +0 -62
  136. package/dist/outlookTemplateSections.js.map +0 -1
  137. package/dist/shared/capabilities.d.ts +0 -3
  138. package/dist/shared/capabilities.js +0 -9
  139. package/dist/shared/capabilities.js.map +0 -1
package/README.md CHANGED
@@ -1,253 +1,220 @@
1
1
  # @dawnlit/outlook-bridge
2
2
 
3
- Drive a real, locally installed Outlook client from Node — read the inbox, send and reply to mail, manage drafts, work
4
- with signatures and template emails, file and delete messages. No Graph API, no app registration, no cloud permissions:
5
- this automates the desktop client itself, the same way a person would.
3
+ Drive a real, locally installed Outlook client from Node — read and search mail, send and reply, manage drafts, save
4
+ attachments, work with signatures and template emails, file and delete messages. No Graph API, no app registration, no
5
+ cloud permissions: this automates the desktop client itself, the way a person would.
6
6
 
7
- - **Windows** — via PowerShell + Outlook's COM object model.
8
- - **macOS** — via AppleScript. The whole surface is ported, with two differences no capability flag can express (see
9
- [Platform differences](#platform-differences)). Requires **legacy** Outlook for Mac: New Outlook implements only a
10
- slice of the AppleScript dictionary — accounts don't enumerate and the inbox reports 0 messages — so calls fail
11
- loudly with `ACCOUNT_NOT_FOUND` there rather than quietly doing the wrong thing.
12
- - Any other platform — `capabilities()` reports everything false and every call rejects with `UNSUPPORTED_PLATFORM`.
7
+ - **Windows** — PowerShell and Outlook's COM object model.
8
+ - **macOS** — AppleScript. Requires **legacy** Outlook for Mac: New Outlook implements only a slice of the AppleScript
9
+ dictionary (accounts don't enumerate, the inbox reports no messages), so every call fails with `ACCOUNT_NOT_FOUND`
10
+ there rather than quietly doing the wrong thing.
11
+ - **Anywhere else** — `capabilities()` reports nothing supported and every call rejects with `UNSUPPORTED_PLATFORM`.
13
12
 
14
13
  ## Install
15
14
 
16
- Install straight off GitHub `main`, rather than a versioned release:
17
-
18
15
  ```bash
19
- npm install github:dawnlit-ai/outlook-bridge#main
16
+ npm install @dawnlit/outlook-bridge
20
17
  ```
21
18
 
22
- `prepare` runs `npm run build` automatically on install, so `dist/` is built from that checked-out
23
- source with no separate step. npm resolves the branch to a commit and pins it in `package-lock.json`,
24
- so a later plain `npm install` won't pick up new commits on its own — re-run the command above
25
- (naming the package and `#main` explicitly) whenever you want to advance to the current tip.
26
-
27
- Requires Node 24+ and a real Outlook installation on the machine running it — this is desktop automation, not a hosted
28
- API client.
19
+ Requires Node 24+ and a real Outlook on the machine running it — this is desktop automation, not a hosted API client.
29
20
 
30
21
  ## Usage
31
22
 
32
23
  ```ts
33
- import { getOutlookAccounts, readInboxEmails, sendOutlookEmail } from '@dawnlit/outlook-bridge';
24
+ import { getOutlookAccounts, readInboxEmails, readEmailBody, replyOutlookEmail } from '@dawnlit/outlook-bridge';
25
+
26
+ const [account] = await getOutlookAccounts();
34
27
 
35
- const accounts = await getOutlookAccounts();
28
+ const recent = await readInboxEmails(account, { daysBack: 7, limit: 20 });
36
29
 
37
- const emails = await readInboxEmails(accounts[0]);
30
+ // A listing row can be passed straight back in as the email to act on.
31
+ const email = recent[0];
32
+ const { body } = await readEmailBody(email);
38
33
 
39
- await sendOutlookEmail({
40
- emailAccount: accounts[0],
41
- to: 'someone@example.com', // comma- or semicolon-separated
42
- subject: 'Hello from @dawnlit/outlook-bridge',
43
- htmlBody: '<p>Sent by driving the Outlook client directly.</p>',
44
- sendImmediately: false, // false leaves it in Drafts
34
+ await replyOutlookEmail({
35
+ emailAccount: account,
36
+ entryId: email.entryId,
37
+ storeId: email.storeId,
38
+ htmlBody: '<p>Thanks — received.</p>',
39
+ openDraftWindow: false, // file the reply in Drafts without opening a window
45
40
  });
46
41
  ```
47
42
 
48
- Every function is exposed under one signature regardless of platform — the dispatcher pins both implementations to the
49
- same `OutlookBridge` interface, so nothing here returns a per-platform union you have to narrow.
50
-
51
- ## Platform differences
52
-
53
- Both platforms implement the whole contract, so `capabilities()` is all-true on Windows and on macOS. It still exists,
54
- and is still worth asking, because it answers for the machine you are actually on — on any other OS every entry is
55
- false and every call rejects:
43
+ Every operation takes its required arguments positionally and everything optional as one options object, with the
44
+ same defaults and the same validation on both platforms.
56
45
 
57
- ```ts
58
- import { createOutlookBridge } from '@dawnlit/outlook-bridge';
46
+ ### Operations
59
47
 
60
- const bridge = createOutlookBridge();
48
+ | Area | Operations |
49
+ |------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
50
+ | Accounts | `getOutlookAccounts` |
51
+ | Reading | `readInboxEmails` (the Inbox root, or any folder by name or path), `searchInboxByFilter` (walks the whole Inbox tree by subject glob or pattern, date window, attachments, folders), `readEmailBody` (the sender's own text, split from the quoted thread), `readSelectedEmail`, `openOutlookEmail`, `locateEmail` (finds an email again by subject, sender and time once its id has gone stale) |
52
+ | Folders | `listInboxFolders`, `moveOutlookEmails` (creates a missing destination chain on request) |
53
+ | Composing | `sendOutlookEmail` (to/cc/bcc, several attachments, a named signature, send or draft), `replyOutlookEmail` (reply or reply-all, from HTML or a saved template) |
54
+ | Drafts | `listOutlookDrafts`, `sendDrafts` (a chosen subset), `sendAllDrafts`, `deleteOutlookDrafts` |
55
+ | Attachments | `saveEmailAttachment`, `saveEmailAttachments` |
56
+ | Cleanup | `deleteOutlookEmails` (refuses received and sent mail unless allowed; dry-run first), `purgeDeletedItems` |
57
+ | Bounces | `cleanUndeliverableEmails`, `readBounceReport` (which sent messages bounced, and for which recipients), `collectBouncedRecipients`, `readSentRecipientGroups` |
58
+ | Signatures & templates | `listOutlookSignatures`, `readOutlookSignatureHtml`, `readTemplateEmails`, `saveTemplateEmail`, `editEmailTemplate` |
61
59
 
62
- if (bridge.supports('searchInboxByFilter')) { /* … */ }
60
+ Each is documented where it is declared, in `OutlookBridge` (see `dist/types.d.ts`), with every option and default.
63
61
 
64
- bridge.capabilities(); // { readInboxEmails: true, replyOutlookEmail: true, … }
65
- ```
62
+ A few pure helpers need no Outlook: `threadSubject` (a subject with every stacked reply and forward prefix stripped, in
63
+ any of the languages `RE:`, `AW:` or `回复:` come in — the subject a whole thread shares), `hasReplyPrefix`,
64
+ `splitQuotedOriginal` (the sender's new text apart from the quoted thread) and `mailFolderRef`.
66
65
 
67
- The map is derived from `keyof OutlookBridge`, so a function added to the contract fails both platform maps at
68
- compile time rather than quietly defaulting to supported.
66
+ ### Identifying an email
69
67
 
70
- Two things differ between the platforms that no flag can express, and code moving between them has to know both:
68
+ Listings return an `entryId` and a `storeId`. Operations on one email accept an `EmailRef`: the entry id alone, or any
69
+ object carrying `{ entryId, storeId? }` — so the row itself can be passed back. Passing the row is also the reliable way
70
+ to keep a `storeId` with its own `entryId`; the store id is what lets an email resolve in a mailbox other than the
71
+ default one.
71
72
 
72
- **Ids are not portable.** Windows returns MAPI `EntryID` strings; macOS returns Outlook for Mac's small integer message
73
- ids (`"1263"`). Each platform rejects the other's with `INVALID_REQUEST` rather than failing later as a confusing "not
74
- found". `storeId` has no macOS equivalent: it is returned as `''` and accepted-and-ignored wherever it is a parameter.
75
- Don't persist an id from one platform and look it up on the other.
73
+ **Ids are not portable between platforms.** Windows returns MAPI EntryIDs; macOS returns Outlook for macOS small integer
74
+ message ids, and has no store id (it reports `''` and ignores one passed in). Each platform rejects the other's ids with
75
+ `INVALID_REQUEST`. **Moving an email rewrites its id** on both, so ids saved before a move are stale afterward. Keep the
76
+ subject, sender and received time beside a saved id, and `locateEmail` finds the email again from them wherever it has
77
+ moved under the Inbox, on either platform.
76
78
 
77
- **`searchInboxByFilter` filters in-process on macOS.** Windows hands `subjectLike` and the date window to Outlook's
78
- server-side `Items.Restrict`; AppleScript has no counterpart, so the macOS walk indexes each folder with bulk property
79
- reads and applies the same tests here. Same results, different cost — a `daysBack` of 0 over a large mailbox tree is
80
- considerably heavier on macOS.
79
+ Timestamps are local time as `yyyy-MM-dd HH:mm`, which sorts correctly as a string.
81
80
 
82
- Two smaller notes: `replyOutlookEmail` reports `to` as the reply's resolved recipient addresses on macOS rather than the
83
- display-name string Windows reports; and `editEmailTemplate` on macOS files the edited draft when you answer Outlook's
84
- own "save this message?" prompt on close, where Windows asks for Ctrl+S.
85
-
86
- ## Configuration
81
+ ### Templates
87
82
 
88
- Each call generates a script and shells out to an interpreter. That leaves four things otherwise out of your reach:
89
- how long a run may take, how much it may print, how to call it off, and what the script actually said.
90
-
91
- `createOutlookBridge()` gives you those on an instance no other caller in the process can disturb:
83
+ A template email is an ordinary email saved in a mailbox folder (default `Templates`) and edited in Outlook. One template
84
+ can hold several variants of a reply between `[[SECTION]] ... [[/SECTION]]` markers around a shared greeting and
85
+ closing, with `{{PLACEHOLDER}}` tokens:
92
86
 
93
87
  ```ts
94
- import { createOutlookBridge } from '@dawnlit/outlook-bridge';
95
-
96
- const bridge = createOutlookBridge({
97
- timeoutMs: 120_000, // default per-run ceiling; 0 disables
98
- maxBufferBytes: 32 * 1024 * 1024, // raise before a scan that returns thousands of bodies
99
- tempDir: '/var/tmp/outlook', // scratch scripts and default attachment destination
100
- debug: (e) => log(e.script), // every generated script, its duration, and any error
88
+ await replyOutlookEmail({
89
+ emailAccount: account,
90
+ entryId: email.entryId,
91
+ storeId: email.storeId,
92
+ templateSubject: 'Standard reply',
93
+ templateSection: 'ACCEPT',
94
+ templatePlaceholders: { NOTE: 'We will confirm by Friday.' },
95
+ signatureName: 'Work', // fills {{SIGNATURE}}
101
96
  });
102
-
103
- await bridge.readInboxEmails(account);
104
97
  ```
105
98
 
106
- `bridge.withOptions({ … })` derives a bridge with some settings changed — how one call gets its own budget, or its
107
- own cancellation:
99
+ The template, the section, the placeholders and the signature are all resolved inside the package, so a large
100
+ Word-generated template never has to pass through the caller. Composition refuses rather than guesses: a missing section,
101
+ an unknown placeholder, or a marker left unresolved fails with `INVALID_REQUEST` before anything is created.
108
102
 
109
- ```ts
110
- const controller = new AbortController();
111
- cancelButton.onclick = () => controller.abort();
103
+ The same toolkit works on any HTML string: `composeTemplateBody`, `findTemplateMarkers`, and for `{Token}`-style fills,
104
+ `replaceToken`, `removeTokenLine` (removes a token together with its table row or paragraph), `findTokens` and
105
+ `findUnfilledTokens`.
112
106
 
113
- await bridge.withOptions({ signal: controller.signal }).searchInboxByFilter(account, filter);
114
- // → rejects with an AbortedError (code 'ABORTED'), interpreter killed
115
- ```
107
+ ## Configuration
116
108
 
117
- The same functions are also exported directly, running against a process-wide config that `configure()` sets. That is
118
- the simpler thing for a program that owns its process; prefer `createOutlookBridge` in anything sharing a process
119
- with code you do not own.
109
+ Every call generates a script and runs it under an interpreter. The settings are how long a run may take, how much it may
110
+ print, how to call it off, and what the script said:
120
111
 
121
112
  ```ts
122
- import { configure, readInboxEmails } from '@dawnlit/outlook-bridge';
113
+ import { createOutlookBridge } from '@dawnlit/outlook-bridge';
123
114
 
124
- configure({ timeoutMs: 60_000 });
125
- await readInboxEmails(account);
115
+ const outlook = createOutlookBridge({
116
+ timeoutMs: 120_000, // ceiling on every run (default: a budget sized for each operation)
117
+ maxBufferBytes: 32 * 1024 * 1024, // output cap; searches returning bodies push against it (default 8 MiB)
118
+ tempDir: '/var/tmp/outlook', // generated scripts, and attachments saved without a destination
119
+ debug: event => log(event.script),// every generated script, its duration, and any error
120
+ });
126
121
  ```
127
122
 
128
- `OUTLOOK_BRIDGE_DEBUG=1` in the environment turns on script logging to stderr without a code change.
123
+ - **Timeouts.** Unless `timeoutMs` is set, each run gets a budget sized for its work — a minute for a lookup, two for
124
+ composing or reading a folder, five for a mailbox walk or a batch, ten for purging. `timeoutMs` replaces all of them;
125
+ `0` disables timeouts; `null` goes back to the budgets. `editEmailTemplate` waits on a person and is never timed —
126
+ cancel it with a signal.
127
+ - **Instances.** A bridge's settings apply to its own calls only, even when several bridges run at once.
128
+ `outlook.withOptions({ ... })` derives a bridge with some settings changed — which is how a single call gets its own
129
+ budget or cancellation:
129
130
 
130
- Calls that carry a genuinely different budget (a full-mailbox walk, a purge) keep their own timeout and ignore
131
- `timeoutMs`. `searchInboxByFilter` is the one most likely to push against `maxBufferBytes`, since it returns whole
132
- message bodies.
131
+ ```ts
132
+ const controller = new AbortController();
133
+ cancelButton.onclick = () => controller.abort();
134
+ await outlook.withOptions({ signal: controller.signal }).searchInboxByFilter(account, { daysBack: 365 });
135
+ // → rejects with ABORTED, interpreter killed
136
+ ```
137
+
138
+ - **Process-wide.** The exported functions run under process-wide settings that `configure()` changes. That is the
139
+ simpler choice for a program that owns its process; prefer `createOutlookBridge` in anything sharing one.
140
+ - **Debugging.** `OUTLOOK_BRIDGE_DEBUG=1` logs every script to stderr without a code change.
141
+
142
+ Capability discovery answers for the machine you are on: `capabilities()` returns a map of every operation to
143
+ `true`/`false`, and `supports('searchInboxByFilter')` asks about one.
133
144
 
134
145
  ## Errors
135
146
 
136
- Every deliberate failure is an `OutlookError` carrying a stable `code`. **The codes are the API; the messages are
137
- not** — branch on `code`, never on message text.
147
+ Every deliberate failure is an `OutlookError` with a stable `code`. **The codes are the API; the messages are not** —
148
+ branch on `code`, never on message text.
138
149
 
139
150
  ```ts
140
151
  import { OutlookError } from '@dawnlit/outlook-bridge';
141
152
 
142
153
  try {
143
- await bridge.replyOutlookEmail(params);
154
+ await outlook.readInboxEmails(account, { folder: 'Invoices\\2026' });
144
155
  } catch (error) {
145
- if (!(error instanceof OutlookError)) throw error; // a real bug
156
+ if (!(error instanceof OutlookError)) throw error; // a bug
146
157
  switch (error.code) {
147
- case 'ACCOUNT_NOT_FOUND': return promptForAccount(error.account);
148
- case 'NOT_IMPLEMENTED': return degrade(error.operation);
149
- case 'ABORTED': return;
150
- case 'SCRIPT_FAILED': return report(error.script, error.stderr);
158
+ case 'ACCOUNT_NOT_FOUND': return askForAccount(error.account);
159
+ case 'NOT_FOUND': return report(`${error.kind} missing: ${error.message}`);
160
+ case 'OUTPUT_TOO_LARGE': return retryWithNarrowerWindow();
161
+ case 'SCRIPT_FAILED': return logFailure(error.script, error.line, error.stderr);
151
162
  }
152
163
  }
153
164
  ```
154
165
 
155
- | code | class | meaning |
156
- | --- | --- | --- |
157
- | `UNSUPPORTED_PLATFORM` | `UnsupportedPlatformError` | No Outlook automation exists on this OS. |
158
- | `NOT_IMPLEMENTED` | `NotImplementedError` | The platform could, but this corner isn't written. Carries `operation`. Now only reachable for a folder root macOS has no term for (Journal and the like). |
159
- | `ACCOUNT_NOT_FOUND` | `AccountNotFoundError` | No configured account matches. Carries `account`. |
160
- | `NOT_FOUND` | `NotFoundError` | A folder, template, signature or item did not resolve. Carries `kind`. |
161
- | `INVALID_REQUEST` | `InvalidRequestError` | The arguments cannot produce a call; nothing was attempted. |
162
- | `SCRIPT_FAILED` | `ScriptError` | The interpreter ran and failed. Carries `script`, `stderr`, `runner`, `durationMs`. |
163
- | `TIMEOUT` | `TimeoutError` | The run exceeded its budget and was killed. Carries `timeoutMs`. |
164
- | `ABORTED` | `AbortedError` | The caller's `AbortSignal` fired. |
165
-
166
- `ScriptError` carries the generated script verbatim — the thing you actually want when one fires, and otherwise
167
- reachable only if you wired up a `debug` hook in advance.
166
+ | code | class | meaning |
167
+ |------------------------|----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
168
+ | `UNSUPPORTED_PLATFORM` | `UnsupportedPlatformError` | No Outlook automation exists on this OS. |
169
+ | `NOT_IMPLEMENTED` | `NotImplementedError` | The platform could do this, but this corner isn't written (a folder root macOS has no term for). |
170
+ | `ACCOUNT_NOT_FOUND` | `AccountNotFoundError` | No mailbox in the profile matches the address. Carries `account`. |
171
+ | `NOT_FOUND` | `NotFoundError` | A `folder`, `email`, `attachment`, `template`, `signature` or `file` didn't resolve. Carries `kind`. |
172
+ | `INVALID_REQUEST` | `InvalidRequestError` | The arguments can't produce a call — a wrong type, a missing value, a template edited into an unusable state, or a mailbox that can be read but not sent from. |
173
+ | `SCRIPT_FAILED` | `ScriptError` | The script ran and failed. Carries `script`, `line`, `stderr`, `runner`, `durationMs`. |
174
+ | `OUTPUT_TOO_LARGE` | `OutputTooLargeError` | The script printed more than `maxBufferBytes`. |
175
+ | `TIMEOUT` | `TimeoutError` | The run exceeded its time budget and was killed. Carries `timeoutMs`. |
176
+ | `ABORTED` | `AbortedError` | The caller's `AbortSignal` fired. |
177
+
178
+ Batch operations (moving, deleting, sending drafts, cleaning bounces) don't throw for one bad item: they report each
179
+ failure in `failed` beside the items that worked.
180
+
181
+ ## Security
182
+
183
+ Caller text ends up inside generated scripts, so the package treats every argument as untrusted:
184
+
185
+ - Arguments are checked for type at run time — a count must be a finite number, a flag a boolean — before any script
186
+ is generated, whatever TypeScript believed at compile time.
187
+ - On Windows, caller text only ever appears in single-quoted PowerShell literals, with every quote character PowerShell
188
+ accepts (including the typographic `‘ ’ ‚ ‛`) escaped. Text that has to appear in a message is referenced through a
189
+ variable, never interpolated into a double-quoted string where `$(...)` would run. Scripts run from UTF-8 files with a
190
+ byte-order mark, so non-ASCII text arrives intact.
191
+ - Attachment names come from the sender, so a saved file's name is sanitized — no path separators, reserved device
192
+ names or characters the file system rejects — and never overwrites an existing file.
168
193
 
169
194
  ## Module format
170
195
 
171
- Published as CommonJS. ESM consumers can use named imports normally (`import { sendOutlookEmail } from …`); Node
172
- resolves them off the CJS build.
173
-
174
- There is deliberately no dual ESM/CJS build. This package identifies its errors with `instanceof`, and a dual-format
175
- package can load two copies of itself into one process — at which point `error instanceof OutlookError` returns
176
- `false` for errors the package itself threw. One format avoids that failure entirely.
177
-
178
- ## API
179
-
180
- **Accounts & inbox** — `getOutlookAccounts`, `readInboxEmails`, `readEmailBody`,
181
- `listInboxFolders`, `moveOutlookEmails`, `openOutlookEmail`.
182
-
183
- **Search & selection** — `searchInboxByFilter` (walks every folder under the Inbox for one
184
- account, filtering by subject pattern/date window/reply-exclusion/attachment presence — the
185
- building block for "find the emails matching X" without knowing which subfolder holds them),
186
- `readSelectedEmail` (the email currently selected or open in Outlook).
187
-
188
- **Sending & replying** — `sendOutlookEmail`, `replyOutlookEmail` (answers the sender; pass `replyAll: true` to answer
189
- every recipient), `sendAllDrafts`.
190
-
191
- **Drafts** — `listOutlookDrafts`, `deleteOutlookDrafts`, `sendDrafts` (sends a chosen subset by
192
- EntryID — the review-and-select alternative to `sendAllDrafts`'s account-wide sweep).
193
-
194
- **Attachments** — `saveEmailAttachment`, `saveEmailAttachmentDetailed`, `saveEmailAttachments`
195
- (the same, batched into one COM round trip for several attachments off one email). All take an optional `destDir`,
196
- created if absent. Without one, each call saves into a fresh private directory — attachments keep the name the sender
197
- gave them, so a shared folder means two emails carrying `invoice.pdf` overwrite each other.
198
-
199
- **Signatures & templates** — `listOutlookSignatures`, `readOutlookSignatureHtml`,
200
- `readTemplateEmails`, `saveTemplateEmail`, `editEmailTemplate` (opens the template in a real Outlook compose window,
201
- waits for the user to close it, and returns the saved HTML — so it has no timeout by default; give it an
202
- `AbortSignal` if you need a way to give up).
203
-
204
- **Bounce handling** — `cleanUndeliverableEmails`, `collectBouncedRecipients`,
205
- `readSentRecipientGroups`.
206
-
207
- **Mailbox cleanup** — `deleteOutlookEmails`, `purgeDeletedItems`.
208
-
209
- **Template-section parsing** — a small pure-string toolkit for templates that carry several reply variants between
210
- `[[SECTION]] ... [[/SECTION]]` markers around one shared greeting/signature, with `{{PLACEHOLDER}}` tokens filled in by
211
- the caller: `findTemplateMarkers`, `composeTemplateBody`, `findTokens`,
212
- `findUnfilledTokens`, `replaceToken`, `removeTokenLine`.
213
-
214
- **Platform-neutral helpers** — usable with no Outlook session at all: `mailFolderRef` (parse any folder string a user
215
- might type into a well-known root plus segments), `splitQuotedOriginal` (separate a reply's own text from the thread
216
- quoted below it), `WELL_KNOWN_FOLDERS`.
217
-
218
- **Instances, capabilities and errors** — `createOutlookBridge` (a bridge with its own settings; see
219
- [Configuration](#configuration)), `capabilities` / `supports`, and the `OutlookError` family
220
- (see [Errors](#errors)).
221
-
222
- Every exported type (`InboxEmail`, `ReplyEmailParams`, `MailFolderRef`, `OutlookBridge`, `CapabilityMap`,
223
- `OutlookErrorCode`, etc.) is exported alongside its function.
196
+ Published as CommonJS; ESM consumers can use named imports normally. There is deliberately no dual build: the package
197
+ identifies its errors with `instanceof`, and a dual-format package can load two copies of itself into one process.
224
198
 
225
199
  ## Development
226
200
 
227
201
  ```bash
228
202
  npm run build # tsc → dist/
229
- npm test # build, then node:test over the platform-independent parts
203
+ npm test # build, then node:test
230
204
  ```
231
205
 
232
- The tests cover the parts that need no Outlook session — folder-string parsing, quote splitting, template
233
- composition, scratch-file handling, the error taxonomy and its run classifier, capability maps, config scoping, and
234
- the shape of the PowerShell the folder walk emits — so they run anywhere, with or without Outlook installed.
235
-
236
- On a **Mac with Outlook installed** they additionally compile every AppleScript the macOS implementation generates.
237
- The runner is stubbed, each operation is driven with plausible arguments, and the captured scripts go through
238
- `osacompile` — which resolves terminology against the real dictionary without opening a session, sending anything or
239
- touching a message. This is worth the trouble because one bad dictionary term fails the WHOLE script at compile time
240
- rather than at the offending line, so a live failure never names the thing that caused it. Those tests skip
241
- automatically elsewhere.
242
-
243
- Two guards over the same captured scripts run **everywhere**, with no Outlook needed, because they cover the
244
- failures compiling cannot catch — both of which shipped once and only surfaced against a live mailbox: assigning to a
245
- reserved AppleScript word (`set rest to …` parses, then fails at run time), and reading a record field through a
246
- nested accessor (`address of (sender of m)` does not coerce, and inside a `try` that means a silently empty sender).
247
-
248
- What no test can cover is the automation itself: driving a real Outlook client is the whole point of the package, and
249
- behaviour against a live mailbox — that a `move` really filed the mail, that a reply threaded correctly — is verified
250
- by running it, not in CI.
206
+ The tests need no Outlook. They cover every operation's validation and defaults against a fake backend, the pure
207
+ helpers, the error taxonomy, and the generated scripts themselves:
208
+
209
+ - **On Windows**, every PowerShell script every operation generates is put through PowerShell's own parser, with
210
+ arguments built to break out of a badly escaped literal. The test fails on a syntax error, and on any caller text
211
+ inside a double-quoted string.
212
+ - **On a Mac with Outlook installed**, every AppleScript is compiled with `osacompile`, which resolves dictionary terms
213
+ without opening a session. Elsewhere, those tests skip; two guards that need no Outlook — no assignment to a reserved
214
+ AppleScript word, no record field read through a nested accessor — run everywhere.
215
+
216
+ What no test covers is the automation itself: that a move really filed the mail, or a reply threaded correctly, is
217
+ verified against a live mailbox.
251
218
 
252
219
  ## License
253
220
 
@@ -0,0 +1,151 @@
1
+ import type { CleanUndeliverableResult, DeleteDraftsResult, DeleteMailResult, EmailLocator, InboxEmail, InboxFolderInfo, InboxSearchMatch, ListDraftsResult, MailFolderRef, MoveEmailsResult, PurgeDeletedItemsResult, ReplyEmailResult, SavedAttachment, SaveTemplateResult, SelectedEmail, SendDraftsResult, SentRecipientGroup, TemplateFolderResult } from './types';
2
+ /** What to do with a composed email. */
3
+ export type Disposition =
4
+ /** Send it now. */
5
+ 'send'
6
+ /** Open it in a compose window for review. */
7
+ | 'display'
8
+ /** File it into Drafts without a window. */
9
+ | 'save';
10
+ export interface AccountRequest {
11
+ /** The mailbox's SMTP address, trimmed. */
12
+ readonly account: string;
13
+ }
14
+ export interface SendRequest extends AccountRequest {
15
+ /** One entry per recipient, each an address or a display-name form. */
16
+ readonly to: readonly string[];
17
+ readonly cc: readonly string[];
18
+ readonly bcc: readonly string[];
19
+ readonly subject: string;
20
+ readonly htmlBody: string;
21
+ /** Absolute paths, each verified to exist. */
22
+ readonly attachments: readonly string[];
23
+ readonly disposition: Disposition;
24
+ }
25
+ export interface ReplyRequest extends AccountRequest {
26
+ readonly email: EmailLocator;
27
+ /** The HTML to insert above the quoted original, fully composed. */
28
+ readonly html: string;
29
+ readonly replyAll: boolean;
30
+ readonly disposition: Disposition;
31
+ }
32
+ export interface ReadInboxRequest extends AccountRequest {
33
+ readonly folder: MailFolderRef;
34
+ /** The folder as the caller wrote it, for messages. */
35
+ readonly folderLabel: string;
36
+ readonly daysBack: number;
37
+ readonly limit: number;
38
+ /** 0 means no body is read at all. */
39
+ readonly previewChars: number;
40
+ }
41
+ export interface SearchRequest extends AccountRequest {
42
+ readonly daysBack: number;
43
+ readonly subjectLike?: string;
44
+ readonly subjectPattern?: RegExp;
45
+ readonly excludeReplies: boolean;
46
+ readonly requireAttachment: boolean;
47
+ readonly includeFolders: readonly string[];
48
+ readonly excludeFolders: readonly string[];
49
+ readonly includeBody: boolean;
50
+ }
51
+ /** One email as a backend reads it, before any splitting or capping. */
52
+ export interface RawEmail {
53
+ readonly entryId: string;
54
+ readonly subject: string;
55
+ readonly senderName: string;
56
+ readonly senderEmail: string;
57
+ readonly receivedTime: string;
58
+ /** The full plain-text body. */
59
+ readonly body: string;
60
+ readonly attachmentNames: readonly string[];
61
+ }
62
+ export interface ListFoldersRequest extends AccountRequest {
63
+ readonly maxDepth: number;
64
+ }
65
+ export interface MoveRequest extends AccountRequest {
66
+ /** Non-empty, deduplicated. */
67
+ readonly entryIds: readonly string[];
68
+ readonly folder: MailFolderRef;
69
+ readonly folderLabel: string;
70
+ readonly createIfMissing: boolean;
71
+ }
72
+ export interface ListDraftsRequest extends AccountRequest {
73
+ readonly limit: number;
74
+ readonly previewChars: number;
75
+ }
76
+ export interface EntryIdsRequest extends AccountRequest {
77
+ /** Non-empty, deduplicated. */
78
+ readonly entryIds: readonly string[];
79
+ }
80
+ export interface DeleteEmailsRequest extends EntryIdsRequest {
81
+ readonly allowProtected: boolean;
82
+ readonly dryRun: boolean;
83
+ }
84
+ export interface PurgeRequest extends AccountRequest {
85
+ readonly olderThanDays: number;
86
+ readonly dryRun: boolean;
87
+ }
88
+ export interface SaveAttachmentsRequest {
89
+ readonly email: EmailLocator;
90
+ /** Non-empty; matched exactly first, then ignoring case and whitespace. */
91
+ readonly fileNames: readonly string[];
92
+ /** An existing directory to save into. */
93
+ readonly destDir: string;
94
+ }
95
+ export interface CleanUndeliverableRequest extends AccountRequest {
96
+ readonly daysBack: number;
97
+ readonly dryRun: boolean;
98
+ }
99
+ export interface CollectBouncesRequest extends AccountRequest {
100
+ readonly daysBack: number;
101
+ readonly includeDeletedItems: boolean;
102
+ }
103
+ export interface SentGroupsRequest extends AccountRequest {
104
+ readonly daysBack: number;
105
+ readonly limit: number;
106
+ }
107
+ export interface ReadTemplatesRequest extends AccountRequest {
108
+ readonly folder: string;
109
+ readonly limit: number;
110
+ readonly includeBody: boolean;
111
+ /** Only the template with this subject, case-insensitively. */
112
+ readonly subject?: string;
113
+ }
114
+ export interface SaveTemplateRequest extends AccountRequest {
115
+ readonly folder: string;
116
+ readonly subject: string;
117
+ readonly htmlBody: string;
118
+ }
119
+ export interface EditTemplateRequest {
120
+ readonly label: string;
121
+ readonly html: string;
122
+ }
123
+ /** One platform's Outlook automation. */
124
+ export interface Backend {
125
+ getOutlookAccounts(): Promise<string[]>;
126
+ sendOutlookEmail(request: SendRequest): Promise<void>;
127
+ replyOutlookEmail(request: ReplyRequest): Promise<ReplyEmailResult>;
128
+ readInboxEmails(request: ReadInboxRequest): Promise<InboxEmail[]>;
129
+ searchInboxByFilter(request: SearchRequest): Promise<InboxSearchMatch[]>;
130
+ readSelectedEmail(): Promise<SelectedEmail>;
131
+ readEmailBody(email: EmailLocator): Promise<RawEmail>;
132
+ openOutlookEmail(email: EmailLocator): Promise<void>;
133
+ listInboxFolders(request: ListFoldersRequest): Promise<InboxFolderInfo[]>;
134
+ moveOutlookEmails(request: MoveRequest): Promise<MoveEmailsResult>;
135
+ listOutlookDrafts(request: ListDraftsRequest): Promise<ListDraftsResult>;
136
+ sendDrafts(request: EntryIdsRequest): Promise<SendDraftsResult>;
137
+ sendAllDrafts(request: AccountRequest): Promise<SendDraftsResult>;
138
+ deleteOutlookDrafts(request: EntryIdsRequest): Promise<DeleteDraftsResult>;
139
+ deleteOutlookEmails(request: DeleteEmailsRequest): Promise<DeleteMailResult>;
140
+ purgeDeletedItems(request: PurgeRequest): Promise<PurgeDeletedItemsResult>;
141
+ saveEmailAttachments(request: SaveAttachmentsRequest): Promise<SavedAttachment[]>;
142
+ cleanUndeliverableEmails(request: CleanUndeliverableRequest): Promise<CleanUndeliverableResult>;
143
+ collectBouncedRecipients(request: CollectBouncesRequest): Promise<string[]>;
144
+ readSentRecipientGroups(request: SentGroupsRequest): Promise<SentRecipientGroup[]>;
145
+ listOutlookSignatures(): Promise<string[]>;
146
+ /** '' when there is no signature by that name. */
147
+ readOutlookSignatureHtml(name: string): Promise<string>;
148
+ readTemplateEmails(request: ReadTemplatesRequest): Promise<TemplateFolderResult>;
149
+ saveTemplateEmail(request: SaveTemplateRequest): Promise<SaveTemplateResult>;
150
+ editEmailTemplate(request: EditTemplateRequest): Promise<string>;
151
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"","sourcesContent":["// What a platform implements.\n//\n// The public operations (bridge.ts) check every argument, apply every default,\n// and do all the work that is the same on both platforms — composing a reply\n// from a template, splitting a body from its quoted thread, choosing where\n// attachments go. What reaches a backend is a request in its final shape: counts\n// are whole numbers in range, strings are trimmed, folders are parsed, and each\n// field the platform's script needs is present. A backend's job is only to turn\n// that request into Outlook automation and its output into the result.\nimport type {\n CleanUndeliverableResult,\n DeleteDraftsResult,\n DeleteMailResult,\n EmailLocator,\n InboxEmail,\n InboxFolderInfo,\n InboxSearchMatch,\n ListDraftsResult,\n MailFolderRef,\n MoveEmailsResult,\n PurgeDeletedItemsResult,\n ReplyEmailResult,\n SavedAttachment,\n SaveTemplateResult,\n SelectedEmail,\n SendDraftsResult,\n SentRecipientGroup,\n TemplateFolderResult,\n} from './types';\n\n/** What to do with a composed email. */\nexport type Disposition =\n/** Send it now. */\n | 'send'\n /** Open it in a compose window for review. */\n | 'display'\n /** File it into Drafts without a window. */\n | 'save';\n\nexport interface AccountRequest {\n /** The mailbox's SMTP address, trimmed. */\n readonly account: string;\n}\n\nexport interface SendRequest extends AccountRequest {\n /** One entry per recipient, each an address or a display-name form. */\n readonly to: readonly string[];\n readonly cc: readonly string[];\n readonly bcc: readonly string[];\n readonly subject: string;\n readonly htmlBody: string;\n /** Absolute paths, each verified to exist. */\n readonly attachments: readonly string[];\n readonly disposition: Disposition;\n}\n\nexport interface ReplyRequest extends AccountRequest {\n readonly email: EmailLocator;\n /** The HTML to insert above the quoted original, fully composed. */\n readonly html: string;\n readonly replyAll: boolean;\n readonly disposition: Disposition;\n}\n\nexport interface ReadInboxRequest extends AccountRequest {\n readonly folder: MailFolderRef;\n /** The folder as the caller wrote it, for messages. */\n readonly folderLabel: string;\n readonly daysBack: number;\n readonly limit: number;\n /** 0 means no body is read at all. */\n readonly previewChars: number;\n}\n\nexport interface SearchRequest extends AccountRequest {\n readonly daysBack: number;\n readonly subjectLike?: string;\n readonly subjectPattern?: RegExp;\n readonly excludeReplies: boolean;\n readonly requireAttachment: boolean;\n readonly includeFolders: readonly string[];\n readonly excludeFolders: readonly string[];\n readonly includeBody: boolean;\n}\n\n/** One email as a backend reads it, before any splitting or capping. */\nexport interface RawEmail {\n readonly entryId: string;\n readonly subject: string;\n readonly senderName: string;\n readonly senderEmail: string;\n readonly receivedTime: string;\n /** The full plain-text body. */\n readonly body: string;\n readonly attachmentNames: readonly string[];\n}\n\nexport interface ListFoldersRequest extends AccountRequest {\n readonly maxDepth: number;\n}\n\nexport interface MoveRequest extends AccountRequest {\n /** Non-empty, deduplicated. */\n readonly entryIds: readonly string[];\n readonly folder: MailFolderRef;\n readonly folderLabel: string;\n readonly createIfMissing: boolean;\n}\n\nexport interface ListDraftsRequest extends AccountRequest {\n readonly limit: number;\n readonly previewChars: number;\n}\n\nexport interface EntryIdsRequest extends AccountRequest {\n /** Non-empty, deduplicated. */\n readonly entryIds: readonly string[];\n}\n\nexport interface DeleteEmailsRequest extends EntryIdsRequest {\n readonly allowProtected: boolean;\n readonly dryRun: boolean;\n}\n\nexport interface PurgeRequest extends AccountRequest {\n readonly olderThanDays: number;\n readonly dryRun: boolean;\n}\n\nexport interface SaveAttachmentsRequest {\n readonly email: EmailLocator;\n /** Non-empty; matched exactly first, then ignoring case and whitespace. */\n readonly fileNames: readonly string[];\n /** An existing directory to save into. */\n readonly destDir: string;\n}\n\nexport interface CleanUndeliverableRequest extends AccountRequest {\n readonly daysBack: number;\n readonly dryRun: boolean;\n}\n\nexport interface CollectBouncesRequest extends AccountRequest {\n readonly daysBack: number;\n readonly includeDeletedItems: boolean;\n}\n\nexport interface SentGroupsRequest extends AccountRequest {\n readonly daysBack: number;\n readonly limit: number;\n}\n\nexport interface ReadTemplatesRequest extends AccountRequest {\n readonly folder: string;\n readonly limit: number;\n readonly includeBody: boolean;\n /** Only the template with this subject, case-insensitively. */\n readonly subject?: string;\n}\n\nexport interface SaveTemplateRequest extends AccountRequest {\n readonly folder: string;\n readonly subject: string;\n readonly htmlBody: string;\n}\n\nexport interface EditTemplateRequest {\n readonly label: string;\n readonly html: string;\n}\n\n/** One platform's Outlook automation. */\nexport interface Backend {\n getOutlookAccounts(): Promise<string[]>;\n\n sendOutlookEmail(request: SendRequest): Promise<void>;\n\n replyOutlookEmail(request: ReplyRequest): Promise<ReplyEmailResult>;\n\n readInboxEmails(request: ReadInboxRequest): Promise<InboxEmail[]>;\n\n searchInboxByFilter(request: SearchRequest): Promise<InboxSearchMatch[]>;\n\n readSelectedEmail(): Promise<SelectedEmail>;\n\n readEmailBody(email: EmailLocator): Promise<RawEmail>;\n\n openOutlookEmail(email: EmailLocator): Promise<void>;\n\n listInboxFolders(request: ListFoldersRequest): Promise<InboxFolderInfo[]>;\n\n moveOutlookEmails(request: MoveRequest): Promise<MoveEmailsResult>;\n\n listOutlookDrafts(request: ListDraftsRequest): Promise<ListDraftsResult>;\n\n sendDrafts(request: EntryIdsRequest): Promise<SendDraftsResult>;\n\n sendAllDrafts(request: AccountRequest): Promise<SendDraftsResult>;\n\n deleteOutlookDrafts(request: EntryIdsRequest): Promise<DeleteDraftsResult>;\n\n deleteOutlookEmails(request: DeleteEmailsRequest): Promise<DeleteMailResult>;\n\n purgeDeletedItems(request: PurgeRequest): Promise<PurgeDeletedItemsResult>;\n\n saveEmailAttachments(request: SaveAttachmentsRequest): Promise<SavedAttachment[]>;\n\n cleanUndeliverableEmails(request: CleanUndeliverableRequest): Promise<CleanUndeliverableResult>;\n\n collectBouncedRecipients(request: CollectBouncesRequest): Promise<string[]>;\n\n readSentRecipientGroups(request: SentGroupsRequest): Promise<SentRecipientGroup[]>;\n\n listOutlookSignatures(): Promise<string[]>;\n\n /** '' when there is no signature by that name. */\n readOutlookSignatureHtml(name: string): Promise<string>;\n\n readTemplateEmails(request: ReadTemplatesRequest): Promise<TemplateFolderResult>;\n\n saveTemplateEmail(request: SaveTemplateRequest): Promise<SaveTemplateResult>;\n\n editEmailTemplate(request: EditTemplateRequest): Promise<string>;\n}\n"]}