@dawnlit/outlook-bridge 1.1.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -183
- package/dist/backend.d.ts +151 -0
- package/dist/backend.js +3 -0
- package/dist/backend.js.map +1 -0
- package/dist/bridge.d.ts +56 -0
- package/dist/bridge.js +507 -0
- package/dist/bridge.js.map +1 -0
- package/dist/errors.d.ts +79 -43
- package/dist/errors.js +93 -52
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.js +25 -52
- package/dist/index.js.map +1 -1
- package/dist/mac/accounts.d.ts +7 -2
- package/dist/mac/accounts.js +23 -6
- package/dist/mac/accounts.js.map +1 -1
- package/dist/mac/attachments.d.ts +5 -25
- package/dist/mac/attachments.js +40 -99
- package/dist/mac/attachments.js.map +1 -1
- package/dist/mac/bounces.d.ts +12 -27
- package/dist/mac/bounces.js +96 -117
- package/dist/mac/bounces.js.map +1 -1
- package/dist/mac/cleanup.d.ts +11 -25
- package/dist/mac/cleanup.js +44 -107
- package/dist/mac/cleanup.js.map +1 -1
- package/dist/mac/drafts.d.ts +15 -34
- package/dist/mac/drafts.js +113 -186
- package/dist/mac/drafts.js.map +1 -1
- package/dist/mac/folders.d.ts +12 -21
- package/dist/mac/folders.js +44 -59
- package/dist/mac/folders.js.map +1 -1
- package/dist/mac/index.d.ts +2 -22
- package/dist/mac/index.js +19 -81
- package/dist/mac/index.js.map +1 -1
- package/dist/mac/profile.d.ts +13 -0
- package/dist/mac/profile.js +149 -0
- package/dist/mac/profile.js.map +1 -0
- package/dist/mac/read.d.ts +21 -37
- package/dist/mac/read.js +198 -216
- package/dist/mac/read.js.map +1 -1
- package/dist/mac/run.d.ts +51 -38
- package/dist/mac/run.js +138 -103
- package/dist/mac/run.js.map +1 -1
- package/dist/mac/scripts.d.ts +90 -91
- package/dist/mac/scripts.js +167 -126
- package/dist/mac/scripts.js.map +1 -1
- package/dist/mac/send.d.ts +10 -20
- package/dist/mac/send.js +99 -89
- package/dist/mac/send.js.map +1 -1
- package/dist/mac/signatures.d.ts +2 -2
- package/dist/mac/signatures.js +9 -11
- package/dist/mac/signatures.js.map +1 -1
- package/dist/mac/templates.d.ts +14 -26
- package/dist/mac/templates.js +79 -120
- package/dist/mac/templates.js.map +1 -1
- package/dist/mail.d.ts +80 -46
- package/dist/mail.js +179 -73
- package/dist/mail.js.map +1 -1
- package/dist/runtime.d.ts +76 -43
- package/dist/runtime.js +103 -48
- package/dist/runtime.js.map +1 -1
- package/dist/shared/args.d.ts +48 -0
- package/dist/shared/args.js +216 -0
- package/dist/shared/args.js.map +1 -0
- package/dist/shared/attachmentMatch.d.ts +26 -20
- package/dist/shared/attachmentMatch.js +60 -23
- package/dist/shared/attachmentMatch.js.map +1 -1
- package/dist/shared/bounceReport.d.ts +11 -0
- package/dist/shared/bounceReport.js +45 -0
- package/dist/shared/bounceReport.js.map +1 -0
- package/dist/shared/bounceRules.d.ts +38 -10
- package/dist/shared/bounceRules.js +50 -32
- package/dist/shared/bounceRules.js.map +1 -1
- package/dist/shared/exec.d.ts +21 -0
- package/dist/shared/exec.js +47 -0
- package/dist/shared/exec.js.map +1 -0
- package/dist/shared/json.d.ts +17 -6
- package/dist/shared/json.js +35 -24
- package/dist/shared/json.js.map +1 -1
- package/dist/shared/locate.d.ts +31 -0
- package/dist/shared/locate.js +128 -0
- package/dist/shared/locate.js.map +1 -0
- package/dist/shared/replyBody.d.ts +31 -36
- package/dist/shared/replyBody.js +83 -72
- package/dist/shared/replyBody.js.map +1 -1
- package/dist/shared/templateLimits.d.ts +14 -0
- package/dist/shared/templateLimits.js +21 -0
- package/dist/shared/templateLimits.js.map +1 -0
- package/dist/templateBody.d.ts +69 -0
- package/dist/{outlookTemplateSections.js → templateBody.js} +80 -73
- package/dist/templateBody.js.map +1 -0
- package/dist/types.d.ts +381 -168
- package/dist/types.js +8 -9
- package/dist/types.js.map +1 -1
- package/dist/windows/accounts.d.ts +7 -12
- package/dist/windows/accounts.js +19 -34
- package/dist/windows/accounts.js.map +1 -1
- package/dist/windows/attachments.d.ts +9 -24
- package/dist/windows/attachments.js +94 -119
- package/dist/windows/attachments.js.map +1 -1
- package/dist/windows/bounces.d.ts +13 -26
- package/dist/windows/bounces.js +168 -200
- package/dist/windows/bounces.js.map +1 -1
- package/dist/windows/cleanup.d.ts +23 -23
- package/dist/windows/cleanup.js +99 -113
- package/dist/windows/cleanup.js.map +1 -1
- package/dist/windows/drafts.d.ts +16 -35
- package/dist/windows/drafts.js +156 -253
- package/dist/windows/drafts.js.map +1 -1
- package/dist/windows/folders.d.ts +8 -13
- package/dist/windows/folders.js +44 -52
- package/dist/windows/folders.js.map +1 -1
- package/dist/windows/index.d.ts +2 -27
- package/dist/windows/index.js +11 -66
- package/dist/windows/index.js.map +1 -1
- package/dist/windows/read.d.ts +24 -60
- package/dist/windows/read.js +265 -306
- package/dist/windows/read.js.map +1 -1
- package/dist/windows/run.d.ts +38 -40
- package/dist/windows/run.js +151 -112
- package/dist/windows/run.js.map +1 -1
- package/dist/windows/scripts.d.ts +65 -55
- package/dist/windows/scripts.js +109 -85
- package/dist/windows/scripts.js.map +1 -1
- package/dist/windows/send.d.ts +9 -9
- package/dist/windows/send.js +76 -88
- package/dist/windows/send.js.map +1 -1
- package/dist/windows/signatures.d.ts +4 -10
- package/dist/windows/signatures.js +30 -37
- package/dist/windows/signatures.js.map +1 -1
- package/dist/windows/templates.d.ts +12 -18
- package/dist/windows/templates.js +117 -150
- package/dist/windows/templates.js.map +1 -1
- package/package.json +5 -4
- package/dist/OutlookService.d.ts +0 -43
- package/dist/OutlookService.js +0 -137
- package/dist/OutlookService.js.map +0 -1
- package/dist/outlookTemplateSections.d.ts +0 -62
- package/dist/outlookTemplateSections.js.map +0 -1
- package/dist/shared/capabilities.d.ts +0 -3
- package/dist/shared/capabilities.js +0 -9
- package/dist/shared/capabilities.js.map +0 -1
package/dist/windows/bounces.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.cleanUndeliverableEmails = cleanUndeliverableEmails;
|
|
4
4
|
exports.collectBouncedRecipients = collectBouncedRecipients;
|
|
5
5
|
exports.readSentRecipientGroups = readSentRecipientGroups;
|
|
6
|
-
// Bounce-backs: finding them,
|
|
6
|
+
// Bounce-backs: finding them, clearing them away, and mining them for the
|
|
7
7
|
// addresses that failed.
|
|
8
8
|
const run_1 = require("./run");
|
|
9
9
|
const scripts_1 = require("./scripts");
|
|
@@ -11,257 +11,225 @@ const json_1 = require("../shared/json");
|
|
|
11
11
|
const bounceRules_1 = require("../shared/bounceRules");
|
|
12
12
|
const mail_1 = require("../mail");
|
|
13
13
|
/**
|
|
14
|
-
* The classifier
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*/
|
|
18
|
-
const BOUNCE_LISTS_PS = `
|
|
19
|
-
$phrases = @(${(0, run_1.psList)(bounceRules_1.BOUNCE_SUBJECT_PHRASES)})
|
|
20
|
-
$daemonAddr = @(${(0, run_1.psList)(bounceRules_1.BOUNCE_DAEMON_ADDRESSES)})
|
|
21
|
-
$daemonName = @(${(0, run_1.psList)(bounceRules_1.BOUNCE_DAEMON_NAMES)})
|
|
22
|
-
`;
|
|
23
|
-
/**
|
|
24
|
-
* Per-item classifier. Given an Outlook item in `$item` and the account address
|
|
25
|
-
* (lowercased) in `$targetLc`, sets `$reason` to why it's a bounce ('' if not)
|
|
26
|
-
* and fills `$failedRcpts` with the recipient addresses parsed from the bounce
|
|
27
|
-
* body. Also leaves `$subj`/`$sName`/`$sEmail` populated for the caller's report.
|
|
14
|
+
* The classifier, generated from the shared rules so Windows and macOS can't
|
|
15
|
+
* disagree about what a bounce is. Defines `Get-BounceReport`, which returns
|
|
16
|
+
* null for an item that isn't a bounce and a report row for one that is.
|
|
28
17
|
*
|
|
29
|
-
* The body is only
|
|
18
|
+
* The body is read only after a cheaper signal (class, sender, subject) has
|
|
30
19
|
* matched — that ordering is what keeps a large-inbox scan affordable.
|
|
31
20
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
$
|
|
35
|
-
$
|
|
36
|
-
$
|
|
37
|
-
$
|
|
38
|
-
$
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
$
|
|
45
|
-
|
|
46
|
-
$
|
|
47
|
-
$
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
21
|
+
function bounceClassifierScript() {
|
|
22
|
+
return `
|
|
23
|
+
$bouncePhrases = ${(0, run_1.psArray)(bounceRules_1.BOUNCE_SUBJECT_PHRASES)}
|
|
24
|
+
$daemonAddresses = ${(0, run_1.psArray)(bounceRules_1.BOUNCE_DAEMON_ADDRESSES)}
|
|
25
|
+
$daemonNames = ${(0, run_1.psArray)(bounceRules_1.BOUNCE_DAEMON_NAMES)}
|
|
26
|
+
$daemonSelf = ${(0, run_1.psArray)(bounceRules_1.DAEMON_SELF_ADDRESSES)}
|
|
27
|
+
$ndrPrefix = ${(0, run_1.psString)(bounceRules_1.NDR_MESSAGE_CLASS_PREFIX)}
|
|
28
|
+
$addressPattern = ${(0, run_1.psString)(bounceRules_1.BODY_ADDRESS_PATTERN)}
|
|
29
|
+
$maxFailed = ${(0, run_1.psInt)(bounceRules_1.MAX_FAILED_RECIPIENTS)}
|
|
30
|
+
$accountLower = $target.ToLower()
|
|
31
|
+
function Get-BounceReport($item) {
|
|
32
|
+
$class = 0
|
|
33
|
+
try { $class = [int]$item.Class } catch {}
|
|
34
|
+
if ($class -ne 43 -and $class -ne 46) { return $null }
|
|
35
|
+
$subject = ''
|
|
36
|
+
try { $subject = [string]$item.Subject } catch {}
|
|
37
|
+
$senderName = ''
|
|
38
|
+
try { $senderName = [string]$item.SenderName } catch {}
|
|
39
|
+
$senderEmail = ''
|
|
40
|
+
try { $senderEmail = [string]$item.SenderEmailAddress } catch {}
|
|
41
|
+
$messageClass = ''
|
|
42
|
+
try { $messageClass = [string]$item.MessageClass } catch {}
|
|
43
|
+
$reason = ''
|
|
44
|
+
if ($messageClass.StartsWith($ndrPrefix, [StringComparison]::OrdinalIgnoreCase)) { $reason = ${(0, run_1.psString)(bounceRules_1.BOUNCE_REASON.ndr)} }
|
|
45
|
+
if (-not $reason) { foreach ($d in $daemonAddresses) { if ($senderEmail.ToLower().Contains($d)) { $reason = ${(0, run_1.psString)(bounceRules_1.BOUNCE_REASON.daemon)}; break } } }
|
|
46
|
+
if (-not $reason) { foreach ($d in $daemonNames) { if ($senderName.ToLower().Contains($d)) { $reason = ${(0, run_1.psString)(bounceRules_1.BOUNCE_REASON.daemon)}; break } } }
|
|
47
|
+
if (-not $reason) { foreach ($p in $bouncePhrases) { if ($subject.ToLower().Contains($p)) { $reason = "${bounceRules_1.BOUNCE_REASON.subjectPhrase('$p')}"; break } } }
|
|
48
|
+
if (-not $reason) { return $null }
|
|
49
|
+
$failedRecipients = @()
|
|
50
|
+
$body = ''
|
|
51
|
+
try { $body = [string]$item.Body } catch {}
|
|
52
|
+
foreach ($m in [regex]::Matches($body, $addressPattern)) {
|
|
53
|
+
$address = $m.Value.ToLower().TrimEnd('.')
|
|
54
|
+
if ($address -eq $accountLower) { continue }
|
|
55
|
+
$isDaemon = $false
|
|
56
|
+
foreach ($d in $daemonSelf) { if ($address.Contains($d)) { $isDaemon = $true; break } }
|
|
57
|
+
if ($isDaemon -or $failedRecipients -contains $address) { continue }
|
|
58
|
+
$failedRecipients += $address
|
|
59
|
+
if ($failedRecipients.Count -ge $maxFailed) { break }
|
|
60
|
+
}
|
|
61
|
+
$received = ''
|
|
62
|
+
try { $received = $item.ReceivedTime.ToString('yyyy-MM-dd HH:mm') } catch {}
|
|
63
|
+
return [PSCustomObject]@{
|
|
64
|
+
entryId = [string]$item.EntryID
|
|
65
|
+
subject = $subject.Trim()
|
|
66
|
+
senderName = $senderName
|
|
67
|
+
senderEmail = $senderEmail
|
|
68
|
+
receivedTime = $received
|
|
69
|
+
matchedReason = $reason
|
|
70
|
+
failedRecipients = @($failedRecipients)
|
|
66
71
|
}
|
|
67
72
|
}
|
|
68
73
|
`;
|
|
74
|
+
}
|
|
69
75
|
/**
|
|
70
|
-
* Scan an account's
|
|
71
|
-
*
|
|
72
|
-
* failure notices — and, unless previewing, move each to Deleted Items (recoverable).
|
|
76
|
+
* Scan an account's Inbox for bounce-backs and, unless dry-running, move each to
|
|
77
|
+
* Deleted Items (recoverable).
|
|
73
78
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* MessageClass is an NDR report, its sender fingerprints as a mail-delivery
|
|
77
|
-
* daemon/postmaster, or its subject contains a specific bounce phrase.
|
|
78
|
-
*
|
|
79
|
-
* Matched item references are snapshotted before any Delete() call — deleting
|
|
80
|
-
* mutates the folder collection, so deleting mid-enumeration would skip items
|
|
81
|
-
* (same pattern as sendAllDrafts).
|
|
79
|
+
* Matched items are collected before the first Delete(): deleting shifts the
|
|
80
|
+
* collection, and deleting mid-enumeration would skip items.
|
|
82
81
|
*/
|
|
83
|
-
async function cleanUndeliverableEmails(
|
|
84
|
-
(0, run_1.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
$dryRun = ${(0, run_1.psBool)(dryRun)}
|
|
88
|
-
${(0,
|
|
89
|
-
$
|
|
90
|
-
$
|
|
91
|
-
$
|
|
92
|
-
$
|
|
93
|
-
$targetLc = $target.ToLower()
|
|
94
|
-
$matched = @() # snapshot of COM item refs to (optionally) delete afterward
|
|
95
|
-
$report = @()
|
|
82
|
+
async function cleanUndeliverableEmails(request) {
|
|
83
|
+
const output = await (0, run_1.runPowerShellJson)(`${(0, scripts_1.accountScript)(request.account)}
|
|
84
|
+
${scripts_1.NAMED_STORE_PS}
|
|
85
|
+
${bounceClassifierScript()}
|
|
86
|
+
$dryRun = ${(0, run_1.psBool)(request.dryRun)}
|
|
87
|
+
$inbox = $store.GetDefaultFolder(${(0, run_1.psInt)(mail_1.FolderId.Inbox)})
|
|
88
|
+
${(0, scripts_1.cutoffScript)(request.daysBack)}
|
|
89
|
+
${(0, scripts_1.itemsSinceScript)('inbox', 'ReceivedTime')}
|
|
90
|
+
$reports = @()
|
|
91
|
+
$matchedItems = @()
|
|
96
92
|
$count = $filtered.Count
|
|
97
93
|
for ($i = 1; $i -le $count; $i++) {
|
|
98
94
|
$item = $null
|
|
99
95
|
try { $item = $filtered.Item($i) } catch { continue }
|
|
100
96
|
if ($item -eq $null) { continue }
|
|
101
|
-
$
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
$
|
|
105
|
-
|
|
106
|
-
$
|
|
107
|
-
|
|
108
|
-
subject = $subj.Trim()
|
|
109
|
-
senderName = $sName
|
|
110
|
-
senderEmail = $sEmail
|
|
111
|
-
receivedTime = $rt
|
|
112
|
-
matchedReason = $reason
|
|
113
|
-
failedRecipients = @($failedRcpts)
|
|
114
|
-
}
|
|
115
|
-
$matched += $item
|
|
97
|
+
$received = $null
|
|
98
|
+
try { $received = $item.ReceivedTime } catch {}
|
|
99
|
+
if ($received -ne $null -and $received -lt $cutoff) { continue }
|
|
100
|
+
$report = Get-BounceReport $item
|
|
101
|
+
if ($report -eq $null) { continue }
|
|
102
|
+
$reports += $report
|
|
103
|
+
$matchedItems += $item
|
|
116
104
|
}
|
|
117
|
-
|
|
118
105
|
$deleted = 0
|
|
119
106
|
$failed = @()
|
|
120
107
|
if (-not $dryRun) {
|
|
121
|
-
foreach ($m in $
|
|
122
|
-
$
|
|
123
|
-
|
|
108
|
+
foreach ($m in $matchedItems) {
|
|
109
|
+
$entryId = ''
|
|
110
|
+
$subject = ''
|
|
111
|
+
try { $entryId = [string]$m.EntryID } catch {}
|
|
112
|
+
try { $subject = [string]$m.Subject } catch {}
|
|
124
113
|
try { $m.Delete(); $deleted++ }
|
|
125
|
-
catch { $
|
|
114
|
+
catch { ${(0, json_1.itemFailurePs)('$entryId')} }
|
|
126
115
|
}
|
|
127
116
|
}
|
|
128
|
-
|
|
129
|
-
[PSCustomObject]@{
|
|
130
|
-
account = $target
|
|
131
|
-
scannedDays = ${days}
|
|
132
|
-
dryRun = $dryRun
|
|
133
|
-
matchedCount = $report.Count
|
|
117
|
+
ConvertTo-Json -Depth 4 -InputObject ([PSCustomObject]@{
|
|
134
118
|
deletedCount = $deleted
|
|
135
|
-
matched
|
|
136
|
-
failed
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const
|
|
119
|
+
matched = @($reports)
|
|
120
|
+
failed = @($failed)
|
|
121
|
+
})
|
|
122
|
+
`, 'scan');
|
|
123
|
+
const e = (0, json_1.record)(output);
|
|
124
|
+
const matched = (0, json_1.toArray)(e.matched).map(row => {
|
|
125
|
+
const m = (0, json_1.record)(row);
|
|
126
|
+
return {
|
|
127
|
+
entryId: (0, json_1.str)(m.entryId),
|
|
128
|
+
subject: (0, json_1.str)(m.subject),
|
|
129
|
+
senderName: (0, json_1.str)(m.senderName),
|
|
130
|
+
senderEmail: (0, json_1.str)(m.senderEmail),
|
|
131
|
+
receivedTime: (0, json_1.str)(m.receivedTime),
|
|
132
|
+
matchedReason: (0, json_1.str)(m.matchedReason),
|
|
133
|
+
failedRecipients: (0, json_1.strList)(m.failedRecipients),
|
|
134
|
+
};
|
|
135
|
+
});
|
|
140
136
|
return {
|
|
141
|
-
account:
|
|
142
|
-
scannedDays:
|
|
143
|
-
dryRun:
|
|
144
|
-
matchedCount:
|
|
145
|
-
deletedCount: (0, json_1.num)(
|
|
146
|
-
matched
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
entryId: (0, json_1.str)(e.entryId),
|
|
150
|
-
subject: (0, json_1.str)(e.subject),
|
|
151
|
-
senderName: (0, json_1.str)(e.senderName),
|
|
152
|
-
senderEmail: (0, json_1.str)(e.senderEmail),
|
|
153
|
-
receivedTime: (0, json_1.str)(e.receivedTime),
|
|
154
|
-
matchedReason: (0, json_1.str)(e.matchedReason),
|
|
155
|
-
failedRecipients: (0, json_1.toArray)(e.failedRecipients).map(json_1.str),
|
|
156
|
-
};
|
|
157
|
-
}),
|
|
158
|
-
failed: (0, json_1.toArray)(parsed.failed).map(f => {
|
|
159
|
-
const e = (0, json_1.record)(f);
|
|
160
|
-
return { subject: (0, json_1.str)(e.subject), error: (0, json_1.str)(e.error) };
|
|
161
|
-
}),
|
|
137
|
+
account: request.account,
|
|
138
|
+
scannedDays: request.daysBack,
|
|
139
|
+
dryRun: request.dryRun,
|
|
140
|
+
matchedCount: matched.length,
|
|
141
|
+
deletedCount: (0, json_1.num)(e.deletedCount),
|
|
142
|
+
matched,
|
|
143
|
+
failed: (0, json_1.itemFailures)(e.failed),
|
|
162
144
|
};
|
|
163
145
|
}
|
|
164
146
|
/**
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* Deleted Items is included so this still works after cleanUndeliverableEmails has
|
|
170
|
-
* already filed the bounces there — the blacklist step doesn't depend on running
|
|
171
|
-
* before the cleanup. This never deletes anything.
|
|
147
|
+
* The deduplicated addresses bounce-backs report as failed, from the Inbox and,
|
|
148
|
+
* optionally, Deleted Items — so it still works after the bounces were cleaned
|
|
149
|
+
* away. Never deletes anything.
|
|
172
150
|
*/
|
|
173
|
-
async function collectBouncedRecipients(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const days = (0, mail_1.clamp)(daysBack, 1, 365);
|
|
177
|
-
// 6 = Inbox, 3 = Deleted Items.
|
|
178
|
-
const folderIds = scanDeleted ? '@(6, 3)' : '@(6)';
|
|
179
|
-
const script = `${(0, scripts_1.accountScript)(emailAccount)}
|
|
151
|
+
async function collectBouncedRecipients(request) {
|
|
152
|
+
const roles = request.includeDeletedItems ? [mail_1.FolderId.Inbox, mail_1.FolderId.DeletedItems] : [mail_1.FolderId.Inbox];
|
|
153
|
+
const output = await (0, run_1.runPowerShellJson)(`${(0, scripts_1.accountScript)(request.account)}
|
|
180
154
|
${scripts_1.DELIVERY_STORE_PS}
|
|
181
|
-
$
|
|
182
|
-
${
|
|
183
|
-
$
|
|
184
|
-
$
|
|
185
|
-
|
|
186
|
-
$
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
$filtered = $folder.Items.Restrict("[ReceivedTime] >= '$cutoff'")
|
|
155
|
+
${bounceClassifierScript()}
|
|
156
|
+
${(0, scripts_1.cutoffScript)(request.daysBack)}
|
|
157
|
+
$found = [ordered]@{}
|
|
158
|
+
foreach ($role in @(${roles.map(run_1.psInt).join(', ')})) {
|
|
159
|
+
$scanFolder = $null
|
|
160
|
+
try { $scanFolder = $store.GetDefaultFolder($role) } catch {}
|
|
161
|
+
if ($scanFolder -eq $null) { continue }
|
|
162
|
+
${(0, scripts_1.itemsSinceScript)('scanFolder', 'ReceivedTime').trim()}
|
|
190
163
|
$count = $filtered.Count
|
|
191
164
|
for ($i = 1; $i -le $count; $i++) {
|
|
192
165
|
$item = $null
|
|
193
166
|
try { $item = $filtered.Item($i) } catch { continue }
|
|
194
167
|
if ($item -eq $null) { continue }
|
|
195
|
-
$
|
|
196
|
-
|
|
197
|
-
|
|
168
|
+
$received = $null
|
|
169
|
+
try { $received = $item.ReceivedTime } catch {}
|
|
170
|
+
if ($received -ne $null -and $received -lt $cutoff) { continue }
|
|
171
|
+
$report = Get-BounceReport $item
|
|
172
|
+
if ($report -eq $null) { continue }
|
|
173
|
+
foreach ($address in $report.failedRecipients) { $found[$address] = $true }
|
|
198
174
|
}
|
|
199
175
|
}
|
|
200
|
-
ConvertTo-Json @($found.Keys)
|
|
201
|
-
|
|
202
|
-
return (0, json_1.
|
|
176
|
+
ConvertTo-Json -Compress -InputObject @($found.Keys)
|
|
177
|
+
`, 'scan');
|
|
178
|
+
return (0, json_1.strList)(output);
|
|
203
179
|
}
|
|
204
180
|
/**
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
* the "was every address for this contact tried?" question gets answered: where a
|
|
208
|
-
* blast sends one email per organization addressed to all of its addresses, a sent
|
|
209
|
-
* message's recipient set IS that organization's full address set.
|
|
181
|
+
* Sent Items within the window, each message with the full SMTP address set it
|
|
182
|
+
* went to (To + CC + BCC), newest first.
|
|
210
183
|
*/
|
|
211
|
-
async function readSentRecipientGroups(
|
|
212
|
-
|
|
213
|
-
return [];
|
|
214
|
-
const days = (0, mail_1.clamp)(daysBack, 1, 365);
|
|
215
|
-
const cap = (0, mail_1.clamp)(limit, 1, 10000);
|
|
216
|
-
const script = `${(0, scripts_1.accountScript)(emailAccount)}
|
|
184
|
+
async function readSentRecipientGroups(request) {
|
|
185
|
+
const output = await (0, run_1.runPowerShellJson)(`${(0, scripts_1.accountScript)(request.account)}
|
|
217
186
|
${scripts_1.DELIVERY_STORE_PS}
|
|
218
|
-
$
|
|
219
|
-
$
|
|
220
|
-
$
|
|
221
|
-
$
|
|
222
|
-
$
|
|
223
|
-
$
|
|
224
|
-
|
|
187
|
+
$sentFolder = $store.GetDefaultFolder(${(0, run_1.psInt)(mail_1.FolderId.SentMail)})
|
|
188
|
+
${(0, scripts_1.cutoffScript)(request.daysBack)}
|
|
189
|
+
${(0, scripts_1.itemsSinceScript)('sentFolder', 'SentOn')}
|
|
190
|
+
$filtered.Sort('[SentOn]', $true)
|
|
191
|
+
$limit = ${(0, run_1.psInt)(request.limit)}
|
|
192
|
+
$rows = @()
|
|
193
|
+
$count = $filtered.Count
|
|
194
|
+
for ($i = 1; $i -le $count -and $rows.Count -lt $limit; $i++) {
|
|
225
195
|
$m = $null
|
|
226
|
-
try { $m = $
|
|
196
|
+
try { $m = $filtered.Item($i) } catch { continue }
|
|
227
197
|
if ($m -eq $null) { continue }
|
|
228
|
-
$
|
|
229
|
-
try { $
|
|
230
|
-
if ($
|
|
231
|
-
$
|
|
198
|
+
$sentOn = $null
|
|
199
|
+
try { $sentOn = $m.SentOn } catch {}
|
|
200
|
+
if ($sentOn -ne $null -and $sentOn -lt $cutoff) { break }
|
|
201
|
+
$class = 0
|
|
202
|
+
try { $class = [int]$m.Class } catch {}
|
|
203
|
+
if ($class -ne 43) { continue }
|
|
204
|
+
$addresses = @()
|
|
232
205
|
try {
|
|
233
206
|
foreach ($r in $m.Recipients) {
|
|
234
|
-
$
|
|
235
|
-
try { $
|
|
236
|
-
#
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
try { $addr = [string]$r.AddressEntry.GetExchangeUser().PrimarySmtpAddress } catch {}
|
|
207
|
+
$address = ''
|
|
208
|
+
try { $address = [string]$r.Address } catch {}
|
|
209
|
+
# An internal recipient resolves to an Exchange DN; recover its SMTP address.
|
|
210
|
+
if ($address -notlike '*@*') {
|
|
211
|
+
try { $address = [string]$r.AddressEntry.GetExchangeUser().PrimarySmtpAddress } catch {}
|
|
240
212
|
}
|
|
241
|
-
if ($
|
|
213
|
+
if ($address -like '*@*') { $addresses += $address.ToLower() }
|
|
242
214
|
}
|
|
243
215
|
} catch {}
|
|
244
|
-
if ($
|
|
245
|
-
$
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
entryId = $m.EntryID
|
|
251
|
-
subject = $subj.Trim()
|
|
252
|
-
sentOn = $sentOn
|
|
253
|
-
recipients = @($addrs)
|
|
216
|
+
if ($addresses.Count -eq 0) { continue }
|
|
217
|
+
$rows += [PSCustomObject]@{
|
|
218
|
+
entryId = [string]$m.EntryID
|
|
219
|
+
subject = ([string]$m.Subject).Trim()
|
|
220
|
+
sentOn = if ($sentOn -ne $null) { $sentOn.ToString('yyyy-MM-dd HH:mm') } else { '' }
|
|
221
|
+
recipients = @($addresses)
|
|
254
222
|
}
|
|
255
223
|
}
|
|
256
|
-
ConvertTo-Json @($
|
|
257
|
-
|
|
258
|
-
return (0, json_1.
|
|
259
|
-
const e = (0, json_1.record)(
|
|
224
|
+
ConvertTo-Json -Depth 4 -InputObject @($rows)
|
|
225
|
+
`, 'scan');
|
|
226
|
+
return (0, json_1.toArray)(output).map(row => {
|
|
227
|
+
const e = (0, json_1.record)(row);
|
|
260
228
|
return {
|
|
261
229
|
entryId: (0, json_1.str)(e.entryId),
|
|
262
230
|
subject: (0, json_1.str)(e.subject),
|
|
263
231
|
sentOn: (0, json_1.str)(e.sentOn),
|
|
264
|
-
recipients: (0, json_1.
|
|
232
|
+
recipients: (0, json_1.strList)(e.recipients),
|
|
265
233
|
};
|
|
266
234
|
});
|
|
267
235
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bounces.js","sourceRoot":"","sources":["../../src/windows/bounces.ts"],"names":[],"mappings":";;AAuFA,4DAoFC;AAWD,4DAiCC;AASD,0DA2DC;AA3RD,wEAAwE;AACxE,yBAAyB;AACzB,+BAAsE;AACtE,uCAA+E;AAC/E,yCAAoF;AACpF,uDAM+B;AAC/B,kCAAgC;AAGhC;;;;GAIG;AACH,MAAM,eAAe,GAAG;eACT,IAAA,YAAM,EAAC,oCAAsB,CAAC;kBAC3B,IAAA,YAAM,EAAC,qCAAuB,CAAC;kBAC/B,IAAA,YAAM,EAAC,iCAAmB,CAAC;CAC5C,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;2BAiBA,sCAAwB;;;;;;;;gDAQH,kCAAoB;;;;;;;;;;;CAWnE,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACI,KAAK,UAAU,wBAAwB,CAC1C,YAAoB,EACpB,QAAQ,GAAG,EAAE,EACb,MAAM,GAAG,IAAI;IAEb,IAAA,oBAAc,GAAE,CAAC;IACjB,MAAM,IAAI,GAAG,IAAA,YAAK,EAAC,QAAQ,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,GAAG,IAAA,uBAAa,EAAC,YAAY,CAAC;YACrC,IAAA,YAAM,EAAC,MAAM,CAAC;EACxB,IAAA,0BAAgB,EAAC,YAAY,CAAC;;gCAEA,IAAI;;EAElC,eAAe;;;;;;;;;EASf,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBA8BA,IAAI;;;;;;;CAOvB,CAAC;IACE,MAAM,MAAM,GAAG,IAAA,kBAAW,EAAC,MAAM,IAAA,mBAAa,EAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChE,OAAO;QACH,OAAO,EAAE,IAAA,UAAG,EAAC,MAAM,CAAC,OAAO,CAAC,IAAI,YAAY;QAC5C,WAAW,EAAE,IAAA,UAAG,EAAC,MAAM,CAAC,WAAW,CAAC,IAAI,IAAI;QAC5C,MAAM,EAAE,MAAM,CAAC,MAAM,KAAK,KAAK;QAC/B,YAAY,EAAE,IAAA,UAAG,EAAC,MAAM,CAAC,YAAY,CAAC;QACtC,YAAY,EAAE,IAAA,UAAG,EAAC,MAAM,CAAC,YAAY,CAAC;QACtC,OAAO,EAAE,IAAA,cAAO,EAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;YACrC,MAAM,CAAC,GAAG,IAAA,aAAM,EAAC,CAAC,CAAC,CAAC;YACpB,OAAO;gBACH,OAAO,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,OAAO,CAAC;gBACvB,OAAO,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,OAAO,CAAC;gBACvB,UAAU,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,UAAU,CAAC;gBAC7B,WAAW,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,WAAW,CAAC;gBAC/B,YAAY,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,YAAY,CAAC;gBACjC,aAAa,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,aAAa,CAAC;gBACnC,gBAAgB,EAAE,IAAA,cAAO,EAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,UAAG,CAAC;aACzD,CAAC;QACN,CAAC,CAAC;QACF,MAAM,EAAE,IAAA,cAAO,EAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;YACnC,MAAM,CAAC,GAAG,IAAA,aAAM,EAAC,CAAC,CAAC,CAAC;YACpB,OAAO,EAAE,OAAO,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5D,CAAC,CAAC;KACL,CAAC;AACN,CAAC;AAED;;;;;;;;GAQG;AACI,KAAK,UAAU,wBAAwB,CAC1C,YAAoB,EACpB,QAAQ,GAAG,EAAE,EACb,WAAW,GAAG,IAAI;IAElB,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,EAAE,CAAC;IAC5C,MAAM,IAAI,GAAG,IAAA,YAAK,EAAC,QAAQ,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;IACrC,gCAAgC;IAChC,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;IACnD,MAAM,MAAM,GAAG,GAAG,IAAA,uBAAa,EAAC,YAAY,CAAC;EAC/C,2BAAiB;gCACa,IAAI;EAClC,eAAe;;;mBAGE,SAAS;;;;;;;;;;EAU1B,kBAAkB;;;;;;CAMnB,CAAC;IACE,OAAO,IAAA,iBAAU,EAAC,MAAM,IAAA,mBAAa,EAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,UAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACpF,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,uBAAuB,CACzC,YAAoB,EACpB,QAAQ,GAAG,EAAE,EACb,KAAK,GAAG,IAAI;IAEZ,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,EAAE,CAAC;IAC5C,MAAM,IAAI,GAAG,IAAA,YAAK,EAAC,QAAQ,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;IACrC,MAAM,GAAG,GAAG,IAAA,YAAK,EAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,GAAG,IAAA,uBAAa,EAAC,YAAY,CAAC;EAC/C,2BAAiB;;gCAEa,IAAI;;;;qCAIC,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCvC,CAAC;IACE,OAAO,IAAA,iBAAU,EAAC,MAAM,IAAA,mBAAa,EAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;QAC5D,MAAM,CAAC,GAAG,IAAA,aAAM,EAAC,EAAE,CAAC,CAAC;QACrB,OAAO;YACH,OAAO,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,OAAO,CAAC;YACvB,OAAO,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,OAAO,CAAC;YACvB,MAAM,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,MAAM,CAAC;YACrB,UAAU,EAAE,IAAA,cAAO,EAAC,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,UAAG,CAAC;SAC7C,CAAC;IACN,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
1
|
+
{"version":3,"file":"bounces.js","sourceRoot":"","sources":["../../src/windows/bounces.ts"],"names":[],"mappings":"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","sourcesContent":["// Bounce-backs: finding them, clearing them away, and mining them for the\n// addresses that failed.\nimport { psArray, psBool, psInt, psString, runPowerShellJson } from './run';\nimport { accountScript, cutoffScript, DELIVERY_STORE_PS, itemsSinceScript, NAMED_STORE_PS } from './scripts';\nimport { itemFailurePs, itemFailures, num, record, str, strList, toArray } from '../shared/json';\nimport {\n BODY_ADDRESS_PATTERN,\n BOUNCE_DAEMON_ADDRESSES,\n BOUNCE_DAEMON_NAMES,\n BOUNCE_REASON,\n BOUNCE_SUBJECT_PHRASES,\n DAEMON_SELF_ADDRESSES,\n MAX_FAILED_RECIPIENTS,\n NDR_MESSAGE_CLASS_PREFIX,\n} from '../shared/bounceRules';\nimport { FolderId } from '../mail';\nimport type { CleanUndeliverableRequest, CollectBouncesRequest, SentGroupsRequest } from '../backend';\nimport type { CleanUndeliverableResult, SentRecipientGroup } from '../types';\n\n/**\n * The classifier, generated from the shared rules so Windows and macOS can't\n * disagree about what a bounce is. Defines `Get-BounceReport`, which returns\n * null for an item that isn't a bounce and a report row for one that is.\n *\n * The body is read only after a cheaper signal (class, sender, subject) has\n * matched — that ordering is what keeps a large-inbox scan affordable.\n */\nfunction bounceClassifierScript(): string {\n return `\n$bouncePhrases = ${psArray(BOUNCE_SUBJECT_PHRASES)}\n$daemonAddresses = ${psArray(BOUNCE_DAEMON_ADDRESSES)}\n$daemonNames = ${psArray(BOUNCE_DAEMON_NAMES)}\n$daemonSelf = ${psArray(DAEMON_SELF_ADDRESSES)}\n$ndrPrefix = ${psString(NDR_MESSAGE_CLASS_PREFIX)}\n$addressPattern = ${psString(BODY_ADDRESS_PATTERN)}\n$maxFailed = ${psInt(MAX_FAILED_RECIPIENTS)}\n$accountLower = $target.ToLower()\nfunction Get-BounceReport($item) {\n $class = 0\n try { $class = [int]$item.Class } catch {}\n if ($class -ne 43 -and $class -ne 46) { return $null }\n $subject = ''\n try { $subject = [string]$item.Subject } catch {}\n $senderName = ''\n try { $senderName = [string]$item.SenderName } catch {}\n $senderEmail = ''\n try { $senderEmail = [string]$item.SenderEmailAddress } catch {}\n $messageClass = ''\n try { $messageClass = [string]$item.MessageClass } catch {}\n $reason = ''\n if ($messageClass.StartsWith($ndrPrefix, [StringComparison]::OrdinalIgnoreCase)) { $reason = ${psString(BOUNCE_REASON.ndr)} }\n if (-not $reason) { foreach ($d in $daemonAddresses) { if ($senderEmail.ToLower().Contains($d)) { $reason = ${psString(BOUNCE_REASON.daemon)}; break } } }\n if (-not $reason) { foreach ($d in $daemonNames) { if ($senderName.ToLower().Contains($d)) { $reason = ${psString(BOUNCE_REASON.daemon)}; break } } }\n if (-not $reason) { foreach ($p in $bouncePhrases) { if ($subject.ToLower().Contains($p)) { $reason = \"${BOUNCE_REASON.subjectPhrase('$p')}\"; break } } }\n if (-not $reason) { return $null }\n $failedRecipients = @()\n $body = ''\n try { $body = [string]$item.Body } catch {}\n foreach ($m in [regex]::Matches($body, $addressPattern)) {\n $address = $m.Value.ToLower().TrimEnd('.')\n if ($address -eq $accountLower) { continue }\n $isDaemon = $false\n foreach ($d in $daemonSelf) { if ($address.Contains($d)) { $isDaemon = $true; break } }\n if ($isDaemon -or $failedRecipients -contains $address) { continue }\n $failedRecipients += $address\n if ($failedRecipients.Count -ge $maxFailed) { break }\n }\n $received = ''\n try { $received = $item.ReceivedTime.ToString('yyyy-MM-dd HH:mm') } catch {}\n return [PSCustomObject]@{\n entryId = [string]$item.EntryID\n subject = $subject.Trim()\n senderName = $senderName\n senderEmail = $senderEmail\n receivedTime = $received\n matchedReason = $reason\n failedRecipients = @($failedRecipients)\n }\n}\n`;\n}\n\n/**\n * Scan an account's Inbox for bounce-backs and, unless dry-running, move each to\n * Deleted Items (recoverable).\n *\n * Matched items are collected before the first Delete(): deleting shifts the\n * collection, and deleting mid-enumeration would skip items.\n */\nexport async function cleanUndeliverableEmails(request: CleanUndeliverableRequest): Promise<CleanUndeliverableResult> {\n const output = await runPowerShellJson(`${accountScript(request.account)}\n${NAMED_STORE_PS}\n${bounceClassifierScript()}\n$dryRun = ${psBool(request.dryRun)}\n$inbox = $store.GetDefaultFolder(${psInt(FolderId.Inbox)})\n${cutoffScript(request.daysBack)}\n${itemsSinceScript('inbox', 'ReceivedTime')}\n$reports = @()\n$matchedItems = @()\n$count = $filtered.Count\nfor ($i = 1; $i -le $count; $i++) {\n $item = $null\n try { $item = $filtered.Item($i) } catch { continue }\n if ($item -eq $null) { continue }\n $received = $null\n try { $received = $item.ReceivedTime } catch {}\n if ($received -ne $null -and $received -lt $cutoff) { continue }\n $report = Get-BounceReport $item\n if ($report -eq $null) { continue }\n $reports += $report\n $matchedItems += $item\n}\n$deleted = 0\n$failed = @()\nif (-not $dryRun) {\n foreach ($m in $matchedItems) {\n $entryId = ''\n $subject = ''\n try { $entryId = [string]$m.EntryID } catch {}\n try { $subject = [string]$m.Subject } catch {}\n try { $m.Delete(); $deleted++ }\n catch { ${itemFailurePs('$entryId')} }\n }\n}\nConvertTo-Json -Depth 4 -InputObject ([PSCustomObject]@{\n deletedCount = $deleted\n matched = @($reports)\n failed = @($failed)\n})\n`, 'scan');\n const e = record(output);\n const matched = toArray(e.matched).map(row => {\n const m = record(row);\n return {\n entryId: str(m.entryId),\n subject: str(m.subject),\n senderName: str(m.senderName),\n senderEmail: str(m.senderEmail),\n receivedTime: str(m.receivedTime),\n matchedReason: str(m.matchedReason),\n failedRecipients: strList(m.failedRecipients),\n };\n });\n return {\n account: request.account,\n scannedDays: request.daysBack,\n dryRun: request.dryRun,\n matchedCount: matched.length,\n deletedCount: num(e.deletedCount),\n matched,\n failed: itemFailures(e.failed),\n };\n}\n\n/**\n * The deduplicated addresses bounce-backs report as failed, from the Inbox and,\n * optionally, Deleted Items — so it still works after the bounces were cleaned\n * away. Never deletes anything.\n */\nexport async function collectBouncedRecipients(request: CollectBouncesRequest): Promise<string[]> {\n const roles = request.includeDeletedItems ? [FolderId.Inbox, FolderId.DeletedItems] : [FolderId.Inbox];\n const output = await runPowerShellJson(`${accountScript(request.account)}\n${DELIVERY_STORE_PS}\n${bounceClassifierScript()}\n${cutoffScript(request.daysBack)}\n$found = [ordered]@{}\nforeach ($role in @(${roles.map(psInt).join(', ')})) {\n $scanFolder = $null\n try { $scanFolder = $store.GetDefaultFolder($role) } catch {}\n if ($scanFolder -eq $null) { continue }\n ${itemsSinceScript('scanFolder', 'ReceivedTime').trim()}\n $count = $filtered.Count\n for ($i = 1; $i -le $count; $i++) {\n $item = $null\n try { $item = $filtered.Item($i) } catch { continue }\n if ($item -eq $null) { continue }\n $received = $null\n try { $received = $item.ReceivedTime } catch {}\n if ($received -ne $null -and $received -lt $cutoff) { continue }\n $report = Get-BounceReport $item\n if ($report -eq $null) { continue }\n foreach ($address in $report.failedRecipients) { $found[$address] = $true }\n }\n}\nConvertTo-Json -Compress -InputObject @($found.Keys)\n`, 'scan');\n return strList(output);\n}\n\n/**\n * Sent Items within the window, each message with the full SMTP address set it\n * went to (To + CC + BCC), newest first.\n */\nexport async function readSentRecipientGroups(request: SentGroupsRequest): Promise<SentRecipientGroup[]> {\n const output = await runPowerShellJson(`${accountScript(request.account)}\n${DELIVERY_STORE_PS}\n$sentFolder = $store.GetDefaultFolder(${psInt(FolderId.SentMail)})\n${cutoffScript(request.daysBack)}\n${itemsSinceScript('sentFolder', 'SentOn')}\n$filtered.Sort('[SentOn]', $true)\n$limit = ${psInt(request.limit)}\n$rows = @()\n$count = $filtered.Count\nfor ($i = 1; $i -le $count -and $rows.Count -lt $limit; $i++) {\n $m = $null\n try { $m = $filtered.Item($i) } catch { continue }\n if ($m -eq $null) { continue }\n $sentOn = $null\n try { $sentOn = $m.SentOn } catch {}\n if ($sentOn -ne $null -and $sentOn -lt $cutoff) { break }\n $class = 0\n try { $class = [int]$m.Class } catch {}\n if ($class -ne 43) { continue }\n $addresses = @()\n try {\n foreach ($r in $m.Recipients) {\n $address = ''\n try { $address = [string]$r.Address } catch {}\n # An internal recipient resolves to an Exchange DN; recover its SMTP address.\n if ($address -notlike '*@*') {\n try { $address = [string]$r.AddressEntry.GetExchangeUser().PrimarySmtpAddress } catch {}\n }\n if ($address -like '*@*') { $addresses += $address.ToLower() }\n }\n } catch {}\n if ($addresses.Count -eq 0) { continue }\n $rows += [PSCustomObject]@{\n entryId = [string]$m.EntryID\n subject = ([string]$m.Subject).Trim()\n sentOn = if ($sentOn -ne $null) { $sentOn.ToString('yyyy-MM-dd HH:mm') } else { '' }\n recipients = @($addresses)\n }\n}\nConvertTo-Json -Depth 4 -InputObject @($rows)\n`, 'scan');\n return toArray(output).map(row => {\n const e = record(row);\n return {\n entryId: str(e.entryId),\n subject: str(e.subject),\n sentOn: str(e.sentOn),\n recipients: strList(e.recipients),\n };\n });\n}\n"]}
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DeleteEmailsRequest, PurgeRequest } from '../backend';
|
|
2
|
+
import type { DeleteMailResult, PurgeDeletedItemsResult } from '../types';
|
|
2
3
|
/**
|
|
3
|
-
* Delete mail by
|
|
4
|
-
* Deleted Items, so this is recoverable — purgeDeletedItems is what destroys.
|
|
4
|
+
* Delete mail by entry id from anywhere in the account. Delete() moves each item
|
|
5
|
+
* to Deleted Items, so this is recoverable — purgeDeletedItems is what destroys.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
7
|
+
* This is the one operation that can reach received mail, and an entry id is an
|
|
8
|
+
* unsafe key for it: a stale or wrong id can resolve to a DIFFERENT message.
|
|
9
|
+
* Four things hold the line:
|
|
10
|
+
* - An id that resolves outside this account's store is refused. The bare
|
|
11
|
+
* lookup falls back to the default store, and the Inbox/Sent check below
|
|
12
|
+
* only knows this account's folders, so another mailbox's received mail
|
|
13
|
+
* would otherwise pass it.
|
|
14
|
+
* - Inbox and Sent Items are refused, INCLUDING their subfolders (a filed
|
|
15
|
+
* folder is still received mail), unless `allowProtected`.
|
|
16
|
+
* - `dryRun` resolves and reports without deleting, so the exact subjects and
|
|
17
|
+
* folders can be checked before anything happens.
|
|
18
|
+
* - Every outcome echoes the subject and folder of the item actually resolved.
|
|
18
19
|
*/
|
|
19
|
-
export declare function deleteOutlookEmails(
|
|
20
|
+
export declare function deleteOutlookEmails(request: DeleteEmailsRequest): Promise<DeleteMailResult>;
|
|
20
21
|
/**
|
|
21
|
-
* Permanently remove items from the account's Deleted Items
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* user already threw away.
|
|
22
|
+
* Permanently remove items from the account's Deleted Items. The ONE irreversible
|
|
23
|
+
* operation here, which is why it is folder-scoped rather than keyed on ids: it
|
|
24
|
+
* can only ever destroy what was already thrown away.
|
|
25
25
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
26
|
+
* Walks backwards, since deleting shifts the collection and a forward walk would
|
|
27
|
+
* skip every other item.
|
|
28
28
|
*/
|
|
29
|
-
export declare function purgeDeletedItems(
|
|
29
|
+
export declare function purgeDeletedItems(request: PurgeRequest): Promise<PurgeDeletedItemsResult>;
|