@serve.zone/dcrouter 18.3.0 → 18.4.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 (33) hide show
  1. package/deno.json +1 -1
  2. package/dist_serve/bundle.js +1770 -1677
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/classes.dcrouter.js +4 -1
  5. package/dist_ts/db/documents/classes.cached.email.d.ts +44 -0
  6. package/dist_ts/db/documents/classes.cached.email.js +24 -4
  7. package/dist_ts/email/classes.accepted-email-spool.js +16 -13
  8. package/dist_ts/email/classes.mail-egress-coordinator.js +10 -1
  9. package/dist_ts/email/helpers.inbound-security.js +7 -8
  10. package/dist_ts/opsserver/handlers/email-ops.handler.d.ts +3 -0
  11. package/dist_ts/opsserver/handlers/email-ops.handler.js +89 -22
  12. package/dist_ts/remoteingress/classes.tunnel-manager.d.ts +4 -0
  13. package/dist_ts/remoteingress/classes.tunnel-manager.js +10 -1
  14. package/dist_ts_interfaces/requests/email-ops.d.ts +44 -0
  15. package/dist_ts_migrations/index.js +111 -11
  16. package/dist_ts_web/00_commitinfo_data.js +1 -1
  17. package/dist_ts_web/appstate/email-ops.d.ts +15 -0
  18. package/dist_ts_web/appstate/email-ops.js +24 -1
  19. package/dist_ts_web/elements/email/ops-view-emails.d.ts +7 -0
  20. package/dist_ts_web/elements/email/ops-view-emails.js +63 -13
  21. package/package.json +5 -5
  22. package/readme.md +19 -0
  23. package/ts/00_commitinfo_data.ts +1 -1
  24. package/ts/classes.dcrouter.ts +7 -0
  25. package/ts/db/documents/classes.cached.email.ts +60 -2
  26. package/ts/email/classes.accepted-email-spool.ts +20 -14
  27. package/ts/email/classes.mail-egress-coordinator.ts +8 -0
  28. package/ts/email/helpers.inbound-security.ts +6 -7
  29. package/ts/opsserver/handlers/email-ops.handler.ts +105 -18
  30. package/ts/remoteingress/classes.tunnel-manager.ts +14 -0
  31. package/ts_web/00_commitinfo_data.ts +1 -1
  32. package/ts_web/appstate/email-ops.ts +30 -0
  33. package/ts_web/elements/email/ops-view-emails.ts +67 -11
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '18.3.0',
6
+ version: '18.4.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfd2ViLzAwX2NvbW1pdGluZm9fZGF0YS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUNILE1BQU0sQ0FBQyxNQUFNLFVBQVUsR0FBRztJQUN4QixJQUFJLEVBQUUsc0JBQXNCO0lBQzVCLE9BQU8sRUFBRSxRQUFRO0lBQ2pCLFdBQVcsRUFBRSwwRUFBMEU7Q0FDeEYsQ0FBQSJ9
@@ -4,6 +4,10 @@ export interface IEmailOpsState {
4
4
  emails: interfaces.requests.IEmail[];
5
5
  traffic: interfaces.requests.IEmailLogTraffic | null;
6
6
  searchQuery: string;
7
+ selectedRange: {
8
+ from: number;
9
+ to: number;
10
+ } | null;
7
11
  securityFindings: interfaces.requests.IEmailSecurityFinding[];
8
12
  securityFindingsLoading: boolean;
9
13
  securityFindingsError: string | null;
@@ -15,8 +19,19 @@ export interface IEmailOpsState {
15
19
  export declare const emailOpsStatePart: plugins.deesElement.domtools.plugins.smartstate.StatePart<string, IEmailOpsState>;
16
20
  export interface IEmailOpsFetchOptions {
17
21
  searchQuery?: string;
22
+ selectedRange?: {
23
+ from: number;
24
+ to: number;
25
+ } | null;
18
26
  }
19
27
  export declare function setActiveEmailSearchQuery(searchQueryArg: string): string;
28
+ export declare function setActiveEmailSelectedRange(rangeArg: {
29
+ from: number;
30
+ to: number;
31
+ } | null): {
32
+ from: number;
33
+ to: number;
34
+ } | null;
20
35
  export declare const fetchAllEmailsAction: plugins.deesElement.domtools.plugins.smartstate.StateAction<IEmailOpsState, IEmailOpsFetchOptions | null>;
21
36
  export declare const fetchEmailSecurityFindingsAction: plugins.deesElement.domtools.plugins.smartstate.StateAction<IEmailOpsState, void>;
22
37
  export declare function fetchEmailDetailSnapshot(emailIdArg: string): Promise<interfaces.requests.IEmailDetail | null>;
@@ -7,6 +7,7 @@ export const emailOpsStatePart = await appState.getStatePart('emailOps', {
7
7
  emails: [],
8
8
  traffic: null,
9
9
  searchQuery: '',
10
+ selectedRange: null,
10
11
  securityFindings: [],
11
12
  securityFindingsLoading: false,
12
13
  securityFindingsError: null,
@@ -18,6 +19,7 @@ export const emailOpsStatePart = await appState.getStatePart('emailOps', {
18
19
  // Email Operations Actions
19
20
  // ============================================================================
20
21
  let activeSearchQuery = '';
22
+ let activeSelectedRange = null;
21
23
  let emailListRequestGeneration = 0;
22
24
  let securityFindingsRequestGeneration = 0;
23
25
  export function setActiveEmailSearchQuery(searchQueryArg) {
@@ -33,6 +35,22 @@ export function setActiveEmailSearchQuery(searchQueryArg) {
33
35
  });
34
36
  return activeSearchQuery;
35
37
  }
38
+ export function setActiveEmailSelectedRange(rangeArg) {
39
+ if (rangeArg && (!Number.isSafeInteger(rangeArg.from)
40
+ || !Number.isSafeInteger(rangeArg.to)
41
+ || rangeArg.from < 0
42
+ || rangeArg.to <= rangeArg.from)) {
43
+ throw new Error('Invalid email time range');
44
+ }
45
+ activeSelectedRange = rangeArg ? { ...rangeArg } : null;
46
+ emailListRequestGeneration++;
47
+ const currentState = emailOpsStatePart.getState();
48
+ emailOpsStatePart.setState({
49
+ ...currentState,
50
+ selectedRange: activeSelectedRange,
51
+ });
52
+ return activeSelectedRange;
53
+ }
36
54
  // Fetch All Emails Action
37
55
  export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (statePartArg, dataArg) => {
38
56
  const context = getActionContext();
@@ -42,6 +60,9 @@ export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (stateP
42
60
  const searchQuery = dataArg?.searchQuery !== undefined
43
61
  ? setActiveEmailSearchQuery(dataArg.searchQuery)
44
62
  : activeSearchQuery;
63
+ const selectedRange = dataArg?.selectedRange !== undefined
64
+ ? setActiveEmailSelectedRange(dataArg.selectedRange)
65
+ : activeSelectedRange;
45
66
  const requestGeneration = ++emailListRequestGeneration;
46
67
  const ticket = captureRealtimeTicket(['emailQueue', 'smtpAttempts']);
47
68
  try {
@@ -49,6 +70,7 @@ export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (stateP
49
70
  const response = await request.fire({
50
71
  identity: context.identity,
51
72
  ...(searchQuery ? { search: searchQuery } : {}),
73
+ ...(selectedRange ? selectedRange : {}),
52
74
  });
53
75
  if (requestGeneration !== emailListRequestGeneration
54
76
  || !isRealtimeTicketCurrent(ticket))
@@ -59,6 +81,7 @@ export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (stateP
59
81
  traffic: response.traffic,
60
82
  searchQuery,
61
83
  isLoading: false,
84
+ selectedRange,
62
85
  error: null,
63
86
  lastUpdated: Date.now(),
64
87
  };
@@ -128,4 +151,4 @@ export async function fetchEmailDetailSnapshot(emailIdArg) {
128
151
  });
129
152
  return response.email;
130
153
  }
131
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW1haWwtb3BzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHNfd2ViL2FwcHN0YXRlL2VtYWlsLW9wcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQUN6QyxPQUFPLEtBQUssVUFBVSxNQUFNLDhCQUE4QixDQUFDO0FBQzNELE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDdkMsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQzlDLE9BQU8sRUFBRSxxQkFBcUIsRUFBRSx1QkFBdUIsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQWUvRSxNQUFNLENBQUMsTUFBTSxpQkFBaUIsR0FBRyxNQUFNLFFBQVEsQ0FBQyxZQUFZLENBQzFELFVBQVUsRUFDVjtJQUNFLE1BQU0sRUFBRSxFQUFFO0lBQ1YsT0FBTyxFQUFFLElBQUk7SUFDYixXQUFXLEVBQUUsRUFBRTtJQUNmLGdCQUFnQixFQUFFLEVBQUU7SUFDcEIsdUJBQXVCLEVBQUUsS0FBSztJQUM5QixxQkFBcUIsRUFBRSxJQUFJO0lBQzNCLFNBQVMsRUFBRSxLQUFLO0lBQ2hCLEtBQUssRUFBRSxJQUFJO0lBQ1gsV0FBVyxFQUFFLENBQUM7SUFDZCxtQkFBbUIsRUFBRSxDQUFDO0NBQ3ZCLEVBQ0QsTUFBTSxDQUNQLENBQUM7QUFFRiwyQkFBMkI7QUFDM0IsK0VBQStFO0FBRS9FLElBQUksaUJBQWlCLEdBQUcsRUFBRSxDQUFDO0FBQzNCLElBQUksMEJBQTBCLEdBQUcsQ0FBQyxDQUFDO0FBQ25DLElBQUksaUNBQWlDLEdBQUcsQ0FBQyxDQUFDO0FBTTFDLE1BQU0sVUFBVSx5QkFBeUIsQ0FBQyxjQUFzQjtJQUM5RCxNQUFNLGVBQWUsR0FBRyxjQUFjLENBQUMsSUFBSSxFQUFFLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxHQUFHLENBQUMsQ0FBQztJQUM1RCxJQUFJLGVBQWUs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
154
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW1haWwtb3BzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHNfd2ViL2FwcHN0YXRlL2VtYWlsLW9wcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQUN6QyxPQUFPLEtBQUssVUFBVSxNQUFNLDhCQUE4QixDQUFDO0FBQzNELE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDdkMsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQzlDLE9BQU8sRUFBRSxxQkFBcUIsRUFBRSx1QkFBdUIsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQWdCL0UsTUFBTSxDQUFDLE1BQU0saUJBQWlCLEdBQUcsTUFBTSxRQUFRLENBQUMsWUFBWSxDQUMxRCxVQUFVLEVBQ1Y7SUFDRSxNQUFNLEVBQUUsRUFBRTtJQUNWLE9BQU8sRUFBRSxJQUFJO0lBQ2IsV0FBVyxFQUFFLEVBQUU7SUFDZixhQUFhLEVBQUUsSUFBSTtJQUNuQixnQkFBZ0IsRUFBRSxFQUFFO0lBQ3BCLHVCQUF1QixFQUFFLEtBQUs7SUFDOUIscUJBQXFCLEVBQUUsSUFBSTtJQUMzQixTQUFTLEVBQUUsS0FBSztJQUNoQixLQUFLLEVBQUUsSUFBSTtJQUNYLFdBQVcsRUFBRSxDQUFDO0lBQ2QsbUJBQW1CLEVBQUUsQ0FBQztDQUN2QixFQUNELE1BQU0sQ0FDUCxDQUFDO0FBRUYsMkJBQTJCO0FBQzNCLCtFQUErRTtBQUUvRSxJQUFJLGlCQUFpQixHQUFHLEVBQUUsQ0FBQztBQUMzQixJQUFJLG1CQUFtQixHQUF3QyxJQUFJLENBQUM7QUFDcEUsSUFBSSwwQkFBMEIsR0FBRyxDQUFDLENBQUM7QUFDbkMsSUFBSSxpQ0FBaUMsR0FBRyxDQUFDLENBQUM7QUFPMUMsTUFBTSxVQUFVLHlCQUF5QixDQUFDLGNBQXNCO0lBQzlELE1BQU0sZUFBZSxHQUFHLGNBQWMsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFDO0lBQzVELElBQUksZUFBZSxLQUFLLGlCQUFpQjtRQUFFLE9BQU8saUJBQWlCLENBQUM7SUFDcEUsaUJBQWlCLEdBQUcsZUFBZSxDQUFDO0lBQ3BDLDBCQUEwQixFQUFFLENBQUM7SUFDN0IsTUFBTSxZQUFZLEdBQUcsaUJBQWlCLENBQUMsUUFBUSxFQUFHLENBQUM7SUFDbkQsaUJBQWlCLENBQUMsUUFBUSxDQUFDO1FBQ3pCLEdBQUcsWUFBWTtRQUNmLFdBQVcsRUFBRSxpQkFBaUI7S0FDL0IsQ0FBQyxDQUFDO0lBQ0gsT0FBTyxpQkFBaUIsQ0FBQztBQUMzQixDQUFDO0FBRUQsTUFBTSxVQUFVLDJCQUEyQixDQUN6QyxRQUE2QztJQUU3QyxJQUFJLFFBQVEsSUFBSSxDQUNkLENBQUMsTUFBTSxDQUFDLGFBQWEsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDO1dBQ2pDLENBQUMsTUFBTSxDQUFDLGFBQWEsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO1dBQ2xDLFFBQVEsQ0FBQyxJQUFJLEdBQUcsQ0FBQztXQUNqQixRQUFRLENBQUMsRUFBRSxJQUFJLFFBQVEsQ0FBQyxJQUFJLENBQ2hDLEVBQUUsQ0FBQztRQUNGLE1BQU0sSUFBSSxLQUFLLENBQUMsMEJBQTBCLENBQUMsQ0FBQztJQUM5QyxDQUFDO0lBQ0QsbUJBQW1CLEdBQUcsUUFBUSxDQUFDLENBQUMsQ0FBQyxFQUFFLEdBQUcsUUFBUSxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztJQUN4RCwwQkFBMEIsRUFBRSxDQUFDO0lBQzdCLE1BQU0sWUFBWSxHQUFHLGlCQUFpQixDQUFDLFFBQVEsRUFBRyxDQUFDO0lBQ25ELGlCQUFpQixDQUFDLFFBQVEsQ0FBQztRQUN6QixHQUFHLFlBQVk7UUFDZixhQUFhLEVBQUUsbUJBQW1CO0tBQ25DLENBQUMsQ0FBQztJQUNILE9BQU8sbUJBQW1CLENBQUM7QUFDN0IsQ0FBQztBQUVELDBCQUEwQjtBQUMxQixNQUFNLENBQUMsTUFBTSxvQkFBb0IsR0FBRyxpQkFBaUIsQ0FBQyxZQUFZLENBQStCLEtBQUssRUFDcEcsWUFBWSxFQUNaLE9BQU8sRUFDa0IsRUFBRTtJQUMzQixNQUFNLE9BQU8sR0FBRyxnQkFBZ0IsRUFBRSxDQUFDO0lBQ25DLE1BQU0sWUFBWSxHQUFHLFlBQVksQ0FBQyxRQUFRLEVBQUcsQ0FBQztJQUM5QyxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVE7UUFBRSxPQUFPLFlBQVksQ0FBQztJQUMzQyxNQUFNLFdBQVcsR0FBRyxPQUFPLEVBQUUsV0FBVyxLQUFLLFNBQVM7UUFDcEQsQ0FBQyxDQUFDLHlCQUF5QixDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUM7UUFDaEQsQ0FBQyxDQUFDLGlCQUFpQixDQUFDO0lBQ3RCLE1BQU0sYUFBYSxHQUFHLE9BQU8sRUFBRSxhQUFhLEtBQUssU0FBUztRQUN4RCxDQUFDLENBQUMsMkJBQTJCLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQztRQUNwRCxDQUFDLENBQUMsbUJBQW1CLENBQUM7SUFDeEIsTUFBTSxpQkFBaUIsR0FBRyxFQUFFLDBCQUEwQixDQUFDO0lBQ3ZELE1BQU0sTUFBTSxHQUFHLHFCQUFxQixDQUFDLENBQUMsWUFBWSxFQUFFLGNBQWMsQ0FBQyxDQUFDLENBQUM7SUFFckUsSUFBSSxDQUFDO1FBQ0gsTUFBTSxPQUFPLEdBQUcsSUFBSSxPQUFPLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxZQUFZLENBQUMsWUFBWSxDQUVwRSxlQUFlLEVBQUUsY0FBYyxDQUFDLENBQUM7UUFFbkMsTUFBTSxRQUFRLEdBQUcsTUFBTSxPQUFPLENBQUMsSUFBSSxDQUFDO1lBQ2xDLFFBQVEsRUFBRSxPQUFPLENBQUMsUUFBUTtZQUMxQixHQUFHLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxXQUFXLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1lBQy9DLEdBQUcsQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1NBQ3hDLENBQUMsQ0FBQztRQUNILElBQ0UsaUJBQWlCLEtBQUssMEJBQTBCO2VBQzdDLENBQUMsdUJBQXVCLENBQUMsTUFBTSxDQUFDO1lBQ25DLE9BQU8sWUFBWSxDQUFDLFFBQVEsRUFBRyxDQUFDO1FBRWxDLE9BQU87WUFDTCxHQUFHLFlBQVksQ0FBQyxRQUFRLEVBQUc7WUFDM0IsTUFBTSxFQUFFLFFBQVEsQ0FBQyxNQUFNO1lBQ3ZCLE9BQU8sRUFBRSxRQUFRLENBQUMsT0FBTztZQUN6QixXQUFXO1lBQ1gsU0FBUyxFQUFFLEtBQUs7WUFDaEIsYUFBYTtZQUNiLEtBQUssRUFBRSxJQUFJO1lBQ1gsV0FBVyxFQUFFLElBQUksQ0FBQyxHQUFHLEVBQUU7U0FDeEIsQ0FBQztJQUNKLENBQUM7SUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1FBQ2YsSUFDRSxpQkFBaUIsS0FBSywwQkFBMEI7ZUFDN0MsQ0FBQyx1QkFBdUIsQ0FBQyxNQUFNLENBQUM7WUFDbkMsT0FBTyxZQUFZLENBQUMsUUFBUSxFQUFHLENBQUM7UUFDbEMsT0FBTztZQUNMLEdBQUcsWUFBWSxDQUFDLFFBQVEsRUFBRztZQUMzQixTQUFTLEVBQUUsS0FBSztZQUNoQixLQUFLLEVBQUUsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsd0JBQXdCO1NBQ3pFLENBQUM7SUFDSixDQUFDO0FBQ0gsQ0FBQyxDQUFDLENBQUM7QUFFSCxNQUFNLENBQUMsTUFBTSxnQ0FBZ0MsR0FBRyxpQkFBaUIsQ0FBQyxZQUFZLENBQU8sS0FBSyxFQUN4RixZQUFZLEVBQ2EsRUFBRTtJQUMzQixNQUFNLE9BQU8sR0FBRyxnQkFBZ0IsRUFBRSxDQUFDO0lBQ25DLE1BQU0sWUFBWSxHQUFHLFlBQVksQ0FBQyxRQUFRLEVBQUcsQ0FBQztJQUM5QyxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVE7UUFBRSxPQUFPLFlBQVksQ0FBQztJQUMzQyxNQUFNLGlCQUFpQixHQUFHLEVBQUUsaUNBQWlDLENBQUM7SUFDOUQsTUFBTSxNQUFNLEdBQUcscUJBQXFCLENBQUMsQ0FBQyxZQUFZLEVBQUUsY0FBYyxDQUFDLENBQUMsQ0FBQztJQUNyRSxZQUFZLENBQUMsUUFBUSxDQUFDO1FBQ3BCLEdBQUcsWUFBWTtRQUNmLHVCQUF1QixFQUFFLElBQUk7UUFDN0IscUJBQXFCLEVBQUUsSUFBSTtLQUM1QixDQUFDLENBQUM7SUFDSCxJQUFJLENBQUM7UUFDSCxNQUFNLE9BQU8sR0FBRyxJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLFlBQVksQ0FBQyxZQUFZLENBRXBFLGVBQWUsRUFBRSwwQkFBMEIsQ0FBQyxDQUFDO1FBQy9DLE1BQU0sUUFBUSxHQUFHLE1BQU0sT0FBTyxDQUFDLElBQUksQ0FBQztZQUNsQyxRQUFRLEVBQUUsT0FBTyxDQUFDLFFBQVE7WUFDMUIsS0FBSyxFQUFFLEdBQUc7U0FDWCxDQUFDLENBQUM7UUFDSCxJQUNFLGlCQUFpQixLQUFLLGlDQUFpQztlQUNwRCxDQUFDLHVCQUF1QixDQUFDLE1BQU0sQ0FBQztZQUNuQyxPQUFPLFlBQVksQ0FBQyxRQUFRLEVBQUcsQ0FBQztRQUNsQyxPQUFPO1lBQ0wsR0FBRyxZQUFZLENBQUMsUUFBUSxFQUFHO1lBQzNCLGdCQUFnQixFQUFFLFFBQVEsQ0FBQyxRQUFRO1lBQ25DLHVCQUF1QixFQUFFLEtBQUs7WUFDOUIscUJBQXFCLEVBQUUsSUFBSTtZQUMzQixtQkFBbUIsRUFBRSxJQUFJLENBQUMsR0FBRyxFQUFFO1NBQ2hDLENBQUM7SUFDSixDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNmLElBQ0UsaUJBQWlCLEtBQUssaUNBQWlDO2VBQ3BELENBQUMsdUJBQXVCLENBQUMsTUFBTSxDQUFDO1lBQ25DLE9BQU8sWUFBWSxDQUFDLFFBQVEsRUFBRyxDQUFDO1FBQ2xDLE9BQU87WUFDTCxHQUFHLFlBQVksQ0FBQyxRQUFRLEVBQUc7WUFDM0IsdUJBQXVCLEVBQUUsS0FBSztZQUM5QixxQkFBcUIsRUFBRSxLQUFLLFlBQVksS0FBSztnQkFDM0MsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPO2dCQUNmLENBQUMsQ0FBQyx5Q0FBeUM7U0FDOUMsQ0FBQztJQUNKLENBQUM7QUFDSCxDQUFDLENBQUMsQ0FBQztBQUVILCtFQUErRTtBQUMvRSxNQUFNLENBQUMsS0FBSyxVQUFVLHdCQUF3QixDQUM1QyxVQUFrQjtJQUVsQixNQUFNLE9BQU8sR0FBRyxnQkFBZ0IsRUFBRSxDQUFDO0lBQ25DLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUTtRQUFFLE9BQU8sSUFBSSxDQUFDO0lBQ25DLE1BQU0sT0FBTyxHQUFHLElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLFlBQVksQ0FFcEUsZUFBZSxFQUFFLGdCQUFnQixDQUFDLENBQUM7SUFDckMsTUFBTSxRQUFRLEdBQUcsTUFBTSxPQUFPLENBQUMsSUFBSSxDQUFDO1FBQ2xDLFFBQVEsRUFBRSxPQUFPLENBQUMsUUFBUTtRQUMxQixPQUFPLEVBQUUsVUFBVTtLQUNwQixDQUFDLENBQUM7SUFDSCxPQUFPLFFBQVEsQ0FBQyxLQUFLLENBQUM7QUFDeEIsQ0FBQyJ9
@@ -12,6 +12,10 @@ export declare class OpsViewEmails extends DeesElement {
12
12
  accessor isLoading: boolean;
13
13
  accessor traffic: interfaces.requests.IEmailLogTraffic | null;
14
14
  accessor searchQuery: string;
15
+ accessor selectedRange: {
16
+ from: number;
17
+ to: number;
18
+ } | null;
15
19
  private stateSubscription;
16
20
  private lastEmailOpsUpdate;
17
21
  private detailRequestGeneration;
@@ -23,6 +27,9 @@ export declare class OpsViewEmails extends DeesElement {
23
27
  private refreshEmailDetail;
24
28
  private handleEmailClick;
25
29
  private handleSearchChange;
30
+ private handleRangeChange;
31
+ private clearSelectedRange;
32
+ private formatSelectedRange;
26
33
  private getEmailTrafficSeries;
27
34
  private handleBack;
28
35
  }
@@ -60,6 +60,9 @@ let OpsViewEmails = (() => {
60
60
  let _searchQuery_decorators;
61
61
  let _searchQuery_initializers = [];
62
62
  let _searchQuery_extraInitializers = [];
63
+ let _selectedRange_decorators;
64
+ let _selectedRange_initializers = [];
65
+ let _selectedRange_extraInitializers = [];
63
66
  var OpsViewEmails = class extends _classSuper {
64
67
  static { _classThis = this; }
65
68
  static {
@@ -70,12 +73,14 @@ let OpsViewEmails = (() => {
70
73
  _isLoading_decorators = [state()];
71
74
  _traffic_decorators = [state()];
72
75
  _searchQuery_decorators = [state()];
76
+ _selectedRange_decorators = [state()];
73
77
  __esDecorate(this, null, _emails_decorators, { kind: "accessor", name: "emails", static: false, private: false, access: { has: obj => "emails" in obj, get: obj => obj.emails, set: (obj, value) => { obj.emails = value; } }, metadata: _metadata }, _emails_initializers, _emails_extraInitializers);
74
78
  __esDecorate(this, null, _selectedEmail_decorators, { kind: "accessor", name: "selectedEmail", static: false, private: false, access: { has: obj => "selectedEmail" in obj, get: obj => obj.selectedEmail, set: (obj, value) => { obj.selectedEmail = value; } }, metadata: _metadata }, _selectedEmail_initializers, _selectedEmail_extraInitializers);
75
79
  __esDecorate(this, null, _currentView_decorators, { kind: "accessor", name: "currentView", static: false, private: false, access: { has: obj => "currentView" in obj, get: obj => obj.currentView, set: (obj, value) => { obj.currentView = value; } }, metadata: _metadata }, _currentView_initializers, _currentView_extraInitializers);
76
80
  __esDecorate(this, null, _isLoading_decorators, { kind: "accessor", name: "isLoading", static: false, private: false, access: { has: obj => "isLoading" in obj, get: obj => obj.isLoading, set: (obj, value) => { obj.isLoading = value; } }, metadata: _metadata }, _isLoading_initializers, _isLoading_extraInitializers);
77
81
  __esDecorate(this, null, _traffic_decorators, { kind: "accessor", name: "traffic", static: false, private: false, access: { has: obj => "traffic" in obj, get: obj => obj.traffic, set: (obj, value) => { obj.traffic = value; } }, metadata: _metadata }, _traffic_initializers, _traffic_extraInitializers);
78
82
  __esDecorate(this, null, _searchQuery_decorators, { kind: "accessor", name: "searchQuery", static: false, private: false, access: { has: obj => "searchQuery" in obj, get: obj => obj.searchQuery, set: (obj, value) => { obj.searchQuery = value; } }, metadata: _metadata }, _searchQuery_initializers, _searchQuery_extraInitializers);
83
+ __esDecorate(this, null, _selectedRange_decorators, { kind: "accessor", name: "selectedRange", static: false, private: false, access: { has: obj => "selectedRange" in obj, get: obj => obj.selectedRange, set: (obj, value) => { obj.selectedRange = value; } }, metadata: _metadata }, _selectedRange_initializers, _selectedRange_extraInitializers);
79
84
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
80
85
  OpsViewEmails = _classThis = _classDescriptor.value;
81
86
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
@@ -98,7 +103,10 @@ let OpsViewEmails = (() => {
98
103
  #searchQuery_accessor_storage = (__runInitializers(this, _traffic_extraInitializers), __runInitializers(this, _searchQuery_initializers, ''));
99
104
  get searchQuery() { return this.#searchQuery_accessor_storage; }
100
105
  set searchQuery(value) { this.#searchQuery_accessor_storage = value; }
101
- stateSubscription = __runInitializers(this, _searchQuery_extraInitializers);
106
+ #selectedRange_accessor_storage = (__runInitializers(this, _searchQuery_extraInitializers), __runInitializers(this, _selectedRange_initializers, null));
107
+ get selectedRange() { return this.#selectedRange_accessor_storage; }
108
+ set selectedRange(value) { this.#selectedRange_accessor_storage = value; }
109
+ stateSubscription = __runInitializers(this, _selectedRange_extraInitializers);
102
110
  lastEmailOpsUpdate = 0;
103
111
  detailRequestGeneration = 0;
104
112
  searchDebounceTimer;
@@ -108,6 +116,7 @@ let OpsViewEmails = (() => {
108
116
  this.emails = state.emails;
109
117
  this.traffic = state.traffic;
110
118
  this.searchQuery = state.searchQuery;
119
+ this.selectedRange = state.selectedRange;
111
120
  const hasNewSnapshot = state.lastUpdated > this.lastEmailOpsUpdate;
112
121
  this.lastEmailOpsUpdate = Math.max(this.lastEmailOpsUpdate, state.lastUpdated);
113
122
  if (hasNewSnapshot && this.selectedEmail) {
@@ -141,7 +150,23 @@ let OpsViewEmails = (() => {
141
150
  height: 100%;
142
151
  }
143
152
 
144
- dees-chart-area[slot='above-table'] {
153
+ .chartStack {
154
+ display: flex;
155
+ flex-direction: column;
156
+ gap: 8px;
157
+ }
158
+
159
+ .rangeSummary {
160
+ display: flex;
161
+ align-items: center;
162
+ justify-content: space-between;
163
+ min-height: 32px;
164
+ padding: 0 4px;
165
+ color: ${cssManager.bdTheme('#374151', '#d1d5db')};
166
+ font-size: 13px;
167
+ }
168
+
169
+ .chartStack dees-chart-area {
145
170
  min-height: 360px;
146
171
  }
147
172
  `,
@@ -166,16 +191,26 @@ let OpsViewEmails = (() => {
166
191
  @email-click=${this.handleEmailClick}
167
192
  >
168
193
  ${this.traffic ? html `
169
- <dees-chart-area
170
- slot="above-table"
171
- .label=${this.traffic.label}
172
- .series=${this.getEmailTrafficSeries()}
173
- .realtimeMode=${true}
174
- .rollingWindow=${this.traffic.durationMs}
175
- .legendStats=${['latest']}
176
- .chartLines=${[]}
177
- .yAxisFormatter=${(valueArg) => `${valueArg}`}
178
- ></dees-chart-area>
194
+ <div slot="above-table" class="chartStack">
195
+ ${this.selectedRange ? html `
196
+ <div class="rangeSummary">
197
+ <span>${this.formatSelectedRange()}</span>
198
+ <dees-button @click=${this.clearSelectedRange}>Clear time range</dees-button>
199
+ </div>
200
+ ` : html ``}
201
+ <dees-chart-area
202
+ .label=${this.traffic.label}
203
+ .series=${this.getEmailTrafficSeries()}
204
+ .realtimeMode=${true}
205
+ .rollingWindow=${this.traffic.durationMs}
206
+ .legendStats=${['latest']}
207
+ .chartLines=${[]}
208
+ .rangeSelectionEnabled=${true}
209
+ .selectedRange=${this.selectedRange}
210
+ .yAxisFormatter=${(valueArg) => `${valueArg}`}
211
+ @range-change=${this.handleRangeChange}
212
+ ></dees-chart-area>
213
+ </div>
179
214
  ` : html ``}
180
215
  </sz-mta-list-view>
181
216
  `}
@@ -215,6 +250,21 @@ let OpsViewEmails = (() => {
215
250
  void appstate.emailOpsStatePart.dispatchAction(appstate.fetchAllEmailsAction, { searchQuery });
216
251
  }, 300);
217
252
  }
253
+ handleRangeChange = (eventArg) => {
254
+ void appstate.emailOpsStatePart.dispatchAction(appstate.fetchAllEmailsAction, { selectedRange: eventArg.detail });
255
+ };
256
+ clearSelectedRange = () => {
257
+ void appstate.emailOpsStatePart.dispatchAction(appstate.fetchAllEmailsAction, { selectedRange: null });
258
+ };
259
+ formatSelectedRange() {
260
+ if (!this.selectedRange)
261
+ return '';
262
+ const formatter = new Intl.DateTimeFormat(undefined, {
263
+ dateStyle: 'medium',
264
+ timeStyle: 'short',
265
+ });
266
+ return `Showing emails from ${formatter.format(this.selectedRange.from)} to ${formatter.format(this.selectedRange.to)}`;
267
+ }
218
268
  getEmailTrafficSeries() {
219
269
  if (!this.traffic)
220
270
  return [];
@@ -248,4 +298,4 @@ let OpsViewEmails = (() => {
248
298
  return OpsViewEmails = _classThis;
249
299
  })();
250
300
  export { OpsViewEmails };
251
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BzLXZpZXctZW1haWxzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vdHNfd2ViL2VsZW1lbnRzL2VtYWlsL29wcy12aWV3LWVtYWlscy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsT0FBTyxFQUFFLFdBQVcsRUFBRSxJQUFJLEVBQUUsYUFBYSxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFDdkcsT0FBTyxLQUFLLFFBQVEsTUFBTSxtQkFBbUIsQ0FBQztBQUM5QyxPQUFPLEtBQUssTUFBTSxNQUFNLG9CQUFvQixDQUFDO0FBQzdDLE9BQU8sS0FBSyxVQUFVLE1BQU0sc0NBQXNDLENBQUM7SUFTdEQsYUFBYTs0QkFEekIsYUFBYSxDQUFDLGlCQUFpQixDQUFDOzs7O3NCQUNFLFdBQVc7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7NkJBQW5CLFNBQVEsV0FBVzs7OztrQ0FDM0MsS0FBSyxFQUFFO3lDQUdQLEtBQUssRUFBRTt1Q0FHUCxLQUFLLEVBQUU7cUNBR1AsS0FBSyxFQUFFO21DQUdQLEtBQUssRUFBRTt1Q0FHUCxLQUFLLEVBQUU7WUFkUix1S0FBUyxNQUFNLDZCQUFOLE1BQU0sdUZBQW9DO1lBR25ELDRMQUFTLGFBQWEsNkJBQWIsYUFBYSxxR0FBaUQ7WUFHdkUsc0xBQVMsV0FBVyw2QkFBWCxXQUFXLGlHQUE2QjtZQUdqRCxnTEFBUyxTQUFTLDZCQUFULFNBQVMsNkZBQVM7WUFHM0IsMEtBQVMsT0FBTyw2QkFBUCxPQUFPLHlGQUFxRDtZQUdyRSxzTEFBUyxXQUFXLDZCQUFYLFdBQVcsaUdBQU07WUFqQjVCLDZLQStLQzs7OztRQTdLQyx5RUFBZ0QsRUFBRSxFQUFDO1FBQW5ELElBQVMsTUFBTSw0Q0FBb0M7UUFBbkQsSUFBUyxNQUFNLGtEQUFvQztRQUduRCw0SUFBa0UsSUFBSSxHQUFDO1FBQXZFLElB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
301
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BzLXZpZXctZW1haWxzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vdHNfd2ViL2VsZW1lbnRzL2VtYWlsL29wcy12aWV3LWVtYWlscy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsT0FBTyxFQUFFLFdBQVcsRUFBRSxJQUFJLEVBQUUsYUFBYSxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsVUFBVSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFDdkcsT0FBTyxLQUFLLFFBQVEsTUFBTSxtQkFBbUIsQ0FBQztBQUM5QyxPQUFPLEtBQUssTUFBTSxNQUFNLG9CQUFvQixDQUFDO0FBQzdDLE9BQU8sS0FBSyxVQUFVLE1BQU0sc0NBQXNDLENBQUM7SUFTdEQsYUFBYTs0QkFEekIsYUFBYSxDQUFDLGlCQUFpQixDQUFDOzs7O3NCQUNFLFdBQVc7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7NkJBQW5CLFNBQVEsV0FBVzs7OztrQ0FDM0MsS0FBSyxFQUFFO3lDQUdQLEtBQUssRUFBRTt1Q0FHUCxLQUFLLEVBQUU7cUNBR1AsS0FBSyxFQUFFO21DQUdQLEtBQUssRUFBRTt1Q0FHUCxLQUFLLEVBQUU7eUNBR1AsS0FBSyxFQUFFO1lBakJSLHVLQUFTLE1BQU0sNkJBQU4sTUFBTSx1RkFBb0M7WUFHbkQsNExBQVMsYUFBYSw2QkFBYixhQUFhLHFHQUFpRDtZQUd2RSxzTEFBUyxXQUFXLDZCQUFYLFdBQVcsaUdBQTZCO1lBR2pELGdMQUFTLFNBQVMsNkJBQVQsU0FBUyw2RkFBUztZQUczQiwwS0FBUyxPQUFPLDZCQUFQLE9BQU8seUZBQXFEO1lBR3JFLHNMQUFTLFdBQVcsNkJBQVgsV0FBVyxpR0FBTTtZQUcxQiw0TEFBUyxhQUFhLDZCQUFiLGFBQWEscUdBQTZDO1lBcEJyRSw2S0F1T0M7Ozs7UUFyT0MseUVBQWdELEVBQUUsRUFBQztRQUFuRCxJQUFTLE1BQU0sNENBQW9D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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@serve.zone/dcrouter",
3
3
  "private": false,
4
- "version": "18.3.0",
4
+ "version": "18.4.0",
5
5
  "description": "A multifaceted routing service handling mail and SMS delivery functions.",
6
6
  "type": "module",
7
7
  "bin": {
@@ -31,7 +31,7 @@
31
31
  "@api.global/typedserver": "^8.6.4",
32
32
  "@api.global/typedsocket": "^5.0.0",
33
33
  "@apiclient.xyz/cloudflare": "^7.1.0",
34
- "@design.estate/dees-catalog": "^3.98.1",
34
+ "@design.estate/dees-catalog": "^3.99.0",
35
35
  "@design.estate/dees-element": "^2.4.0",
36
36
  "@idp.global/sdk": "^14.3.0",
37
37
  "@push.rocks/lik": "^6.4.1",
@@ -50,7 +50,7 @@
50
50
  "@push.rocks/smartmcp": "^0.3.0",
51
51
  "@push.rocks/smartmetrics": "^3.0.3",
52
52
  "@push.rocks/smartmigration": "1.4.1",
53
- "@push.rocks/smartmta": "^9.3.0",
53
+ "@push.rocks/smartmta": "^9.4.0",
54
54
  "@push.rocks/smartnetwork": "^4.8.0",
55
55
  "@push.rocks/smartpath": "^6.0.0",
56
56
  "@push.rocks/smartpromise": "^4.2.4",
@@ -63,9 +63,9 @@
63
63
  "@push.rocks/smartunique": "^3.0.10",
64
64
  "@push.rocks/smartvpn": "1.22.0",
65
65
  "@push.rocks/taskbuffer": "^8.0.2",
66
- "@serve.zone/catalog": "^2.20.0",
66
+ "@serve.zone/catalog": "^2.21.0",
67
67
  "@serve.zone/interfaces": "^19.2.0",
68
- "@serve.zone/remoteingress": "^5.0.3",
68
+ "@serve.zone/remoteingress": "^5.1.0",
69
69
  "@tsclass/tsclass": "^9.5.1",
70
70
  "@types/qrcode": "^1.5.6",
71
71
  "ipaddr.js": "^2.4.0",
package/readme.md CHANGED
@@ -474,6 +474,25 @@ Use `@serve.zone/dcrouter/interfaces` or `@serve.zone/dcrouter-interfaces` when
474
474
 
475
475
  Gateway-client mail contracts let Cloudly or Onebox claim exact app addresses, attach inbound `smtpForward` targets, enable managed outbound SMTP credentials, rotate those credentials, enqueue service mail, and query delivery status through TypedRequest. Managed SMTP users can only send as their exact claimed address; mismatched envelope or header `From` values are rejected before relay. Delivery status queries return the dcrouter spool item state, including deferred SmartMTA errors and next retry timestamps when outbound delivery is temporarily delayed.
476
476
 
477
+ ### Email Operations Views
478
+
479
+ The Email Log keeps its search controls sticky and renders the traffic chart
480
+ above the message table. With no query, the chart covers the latest 24 hours.
481
+ Searches expand the chart window to the smallest supported period that contains
482
+ matching retained messages (7, 14, or 30 days). Dragging across the chart
483
+ selects an epoch-millisecond time range for the table while the chart continues
484
+ to show the complete search context; clearing the selection restores the full
485
+ result list.
486
+
487
+ Inbound messages persist a separate security disposition:
488
+ `accepted`, `flagged`, or `rejected`. SPF, DKIM, or DMARC findings therefore
489
+ remain visible in Email Security without overwriting the transport/delivery
490
+ status. The security table shows time, sender, recipients, subject, and the
491
+ specific authentication failure. Sent-message detail exposes the asynchronous
492
+ outbound authentication self-check based on the exact post-DKIM bytes and the
493
+ actual RemoteIngress source address. It is diagnostic evidence, not a receipt
494
+ from the destination server.
495
+
477
496
  ### RemoteIngress SMTP Egress
478
497
 
479
498
  The email settings API and Ops UI expose `outboundMode`. Keep it at `direct` to let SmartMTA dial destination MX hosts from the dcrouter hub. Set it to `remoteIngress` to make SmartMTA request a one-shot RemoteIngress TCP egress proxy for each outbound SMTP connection while keeping the logical MX host and port for SMTP/TLS identity.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '18.3.0',
6
+ version: '18.4.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -2142,6 +2142,13 @@ export class DcRouter {
2142
2142
  await this.acceptedEmailSpool.trackSmtpTransaction(transaction, emailServer);
2143
2143
  },
2144
2144
  );
2145
+ this.addEmailEventSubscription(
2146
+ emailServer.deliverySystem,
2147
+ 'smtpTransactionUpdated',
2148
+ async (transaction: import('./db/documents/classes.cached.email.js').ICachedEmailSmtpTransaction) => {
2149
+ await this.acceptedEmailSpool.trackSmtpTransaction(transaction, emailServer);
2150
+ },
2151
+ );
2145
2152
 
2146
2153
  // Wire delivery events to MetricsManager and logger using smartmta's public queue APIs.
2147
2154
  if (this.metricsManager) {
@@ -14,11 +14,14 @@ export type TCachedEmailStatus = 'pending' | 'processing' | 'queued' | 'delivere
14
14
  * 'outbound' = locally submitted for delivery.
15
15
  */
16
16
  export type TCachedEmailDirection = 'inbound' | 'outbound';
17
+ export type TCachedEmailSecurityDisposition = 'accepted' | 'flagged' | 'rejected';
17
18
 
18
19
  export interface ICachedEmailListFilter {
19
20
  direction?: TCachedEmailDirection;
20
21
  recipientDomain?: string;
21
22
  search?: string;
23
+ from?: number;
24
+ to?: number;
22
25
  }
23
26
 
24
27
  export interface ICachedEmailTrafficBucket {
@@ -67,6 +70,7 @@ export interface ICachedEmailSmtpTransaction {
67
70
  }>;
68
71
  failurePhase?: TCachedEmailSmtpDeliveryPhase;
69
72
  error?: string;
73
+ outboundAuthentication?: ICachedEmailOutboundAuthenticationCheck;
70
74
  transcript: Array<{
71
75
  timestampMs: number;
72
76
  phase: TCachedEmailSmtpDeliveryPhase;
@@ -76,6 +80,40 @@ export interface ICachedEmailSmtpTransaction {
76
80
  }>;
77
81
  }
78
82
 
83
+ export interface ICachedEmailOutboundAuthenticationCheck {
84
+ state: 'pending' | 'completed' | 'indeterminate' | 'not-evaluated';
85
+ overall: 'pass' | 'fail' | 'indeterminate';
86
+ checkedAt: string;
87
+ signedMessageSha256?: string;
88
+ error?: string;
89
+ identity?: {
90
+ sourceIp: string;
91
+ addressFamily: 'ipv4' | 'ipv6';
92
+ heloDomain: string;
93
+ receiverHostname: string;
94
+ envelopeFrom: string;
95
+ dkimDomain?: string;
96
+ dkimSelector?: string;
97
+ };
98
+ dkim?: Array<{
99
+ is_valid: boolean;
100
+ domain: string | null;
101
+ selector: string | null;
102
+ status: string;
103
+ details: string | null;
104
+ }>;
105
+ spf?: { result: string; domain: string; ip: string; explanation: string | null } | null;
106
+ dmarc?: {
107
+ passed: boolean;
108
+ policy: string;
109
+ domain: string;
110
+ dkim_result: string;
111
+ spf_result: string;
112
+ action: string;
113
+ details: string | null;
114
+ } | null;
115
+ }
116
+
79
117
  /**
80
118
  * Helper to get the smartdata database instance
81
119
  */
@@ -175,6 +213,11 @@ export class CachedEmail extends plugins.smartdata.SmartdataCachedDocument<Cache
175
213
  @plugins.smartdata.svDb()
176
214
  public inboundSecurityResults?: string;
177
215
 
216
+ /** Authentication disposition, kept separate from delivery/lifecycle status. */
217
+ @plugins.smartdata.index()
218
+ @plugins.smartdata.svDb()
219
+ public inboundSecurityDisposition?: TCachedEmailSecurityDisposition;
220
+
178
221
  /**
179
222
  * Number of delivery attempts
180
223
  */
@@ -291,6 +334,9 @@ export class CachedEmail extends plugins.smartdata.SmartdataCachedDocument<Cache
291
334
  if (this.rawContentObjectKey) {
292
335
  delete savableObject.rawContent;
293
336
  }
337
+ if (this.inboundSecurityDisposition === undefined) {
338
+ delete savableObject.inboundSecurityDisposition;
339
+ }
294
340
  return savableObject;
295
341
  }
296
342
 
@@ -405,6 +451,19 @@ export class CachedEmail extends plugins.smartdata.SmartdataCachedDocument<Cache
405
451
  { messageId: { $regex: escapedSearch, $options: 'i' } },
406
452
  ];
407
453
  }
454
+ if (filterArg.from !== undefined || filterArg.to !== undefined) {
455
+ const from = filterArg.from;
456
+ const to = filterArg.to;
457
+ if (
458
+ !Number.isSafeInteger(from)
459
+ || !Number.isSafeInteger(to)
460
+ || (from as number) < 0
461
+ || (to as number) < (from as number)
462
+ ) {
463
+ throw new Error('Invalid CachedEmail time range');
464
+ }
465
+ query.acceptedAt = { $gte: from, $lte: to };
466
+ }
408
467
  return query;
409
468
  }
410
469
 
@@ -511,8 +570,7 @@ export class CachedEmail extends plugins.smartdata.SmartdataCachedDocument<Cache
511
570
  }
512
571
  return await CachedEmail.findLimited({
513
572
  direction: 'inbound',
514
- status: { $in: ['flagged', 'rejected', 'deferred'] },
515
- inboundSecurityResults: { $exists: true, $ne: '' },
573
+ inboundSecurityDisposition: { $in: ['flagged', 'rejected'] },
516
574
  acceptedAt: { $gte: cutoffArg },
517
575
  }, safeLimit, { acceptedAt: -1 });
518
576
  }
@@ -405,7 +405,21 @@ export class AcceptedEmailSpool {
405
405
  const plan = emailServer.createAcceptedEnvelopeDispatchPlan(context, idempotencyKeys);
406
406
  const nonStoreEntries = plan.filter((entry) => entry.action.type !== 'store');
407
407
 
408
- const acceptance = evaluateInboundAcceptance(context.securityResults);
408
+ cachedEmail.direction = this.deriveDirectionFromResolvedRoutes(
409
+ context.resolvedRecipientRoutes,
410
+ context.envelope.rcptTo,
411
+ );
412
+ // Submission/relay authentication is not a receiver verdict. Only locally
413
+ // received mail is classified or enforced here; outbound mail gets its
414
+ // post-signing, actual-egress self-check from the delivery transaction.
415
+ const acceptance = evaluateInboundAcceptance(
416
+ cachedEmail.direction === 'inbound' ? context.securityResults : null,
417
+ );
418
+ if (cachedEmail.direction === 'inbound' && context.securityResults) {
419
+ cachedEmail.inboundSecurityDisposition = acceptance.dmarcReject
420
+ ? 'rejected'
421
+ : acceptance.status;
422
+ }
409
423
  const startedAtMs = Date.now();
410
424
 
411
425
  const persistedRawMessage = this.setDcRouterCacheIdHeader(
@@ -413,10 +427,6 @@ export class AcceptedEmailSpool {
413
427
  cachedEmail.id,
414
428
  );
415
429
  await this.persistRawMessage(cachedEmail, persistedRawMessage);
416
- cachedEmail.direction = this.deriveDirectionFromResolvedRoutes(
417
- context.resolvedRecipientRoutes,
418
- context.envelope.rcptTo,
419
- );
420
430
  cachedEmail.acceptedAt = startedAtMs;
421
431
 
422
432
  if (acceptance.dmarcReject) {
@@ -446,12 +456,9 @@ export class AcceptedEmailSpool {
446
456
  throw new AcceptEnvelopeRejectionError(550, rejectionMessage);
447
457
  }
448
458
 
449
- if (acceptance.status === 'flagged') {
450
- cachedEmail.status = 'flagged';
451
- cachedEmail.lastError = acceptance.doubts.join('; ');
452
- } else {
453
- cachedEmail.status = 'accepted';
454
- }
459
+ // Security disposition is orthogonal to the delivery lifecycle. The Ops
460
+ // API synthesizes the visible flagged badge from inboundSecurityDisposition.
461
+ cachedEmail.status = 'accepted';
455
462
  if (nonStoreEntries.length > 0) {
456
463
  // Relay recipients are dispatched after this row is committed. Park the
457
464
  // row in a status the spool actually scans so a crash between the commit
@@ -657,12 +664,11 @@ export class AcceptedEmailSpool {
657
664
  cachedEmail.routeData = JSON.stringify(routeData);
658
665
 
659
666
  const stillPending = outcomeArg.pendingRecipients.length > 0;
660
- const acceptanceStatus = cachedEmail.lastError && !stillPending ? 'flagged' : 'accepted';
661
667
  if (stillPending) {
662
668
  cachedEmail.status = 'deferred';
663
669
  cachedEmail.nextAttempt = new Date(Date.now() + ENVELOPE_DISPATCH_RETRY_DELAY_MS);
664
670
  } else if (cachedEmail.status === 'deferred') {
665
- cachedEmail.status = acceptanceStatus;
671
+ cachedEmail.status = 'accepted';
666
672
  cachedEmail.nextAttempt = new Date();
667
673
  }
668
674
  await cachedEmail.save();
@@ -701,7 +707,7 @@ export class AcceptedEmailSpool {
701
707
  // Nothing left to dispatch: settle the row back onto its acceptance status
702
708
  // so a fully dispatched envelope does not linger as deferred.
703
709
  if (cachedEmailArg.status === 'deferred') {
704
- cachedEmailArg.status = cachedEmailArg.lastError ? 'flagged' : 'accepted';
710
+ cachedEmailArg.status = 'accepted';
705
711
  cachedEmailArg.nextAttempt = new Date();
706
712
  await cachedEmailArg.save();
707
713
  await this.notifyEmailQueuePersisted(cachedEmailArg, 'envelope-dispatch-settled');
@@ -134,6 +134,14 @@ export class MailEgressCoordinator {
134
134
  connectPort: proxy.listenPort,
135
135
  poolKey: `remoteIngress:${proxy.edgeId}:${context.host}:${context.port}`,
136
136
  ehloHostname: selected.hostname,
137
+ sourceEvidence: tunnelManager.waitForEgressConnectionEvidence(proxy.proxyId, proxy.edgeId)
138
+ .then((eventArg) => {
139
+ if (!eventArg?.sourceIp || !eventArg.addressFamily) return null;
140
+ return {
141
+ sourceIp: eventArg.sourceIp,
142
+ addressFamily: eventArg.addressFamily,
143
+ };
144
+ }),
137
145
  close: async () => {
138
146
  if (closed) {
139
147
  return;
@@ -24,18 +24,17 @@ export function evaluateInboundAcceptance(securityResults: any): IInboundAccepta
24
24
  doubts.push('DKIM signature(s) present but none verified');
25
25
  }
26
26
 
27
- // Only enforcing DMARC policies produce a security finding.
27
+ // A failed DMARC alignment is a security finding even when the sender
28
+ // publishes p=none. Policy controls enforcement, not the authentication result.
28
29
  const dmarc = securityResults?.dmarc;
29
30
  if (dmarc && dmarc.passed === false) {
30
31
  const policy = String(dmarc.policy || '').toLowerCase();
31
32
  const action = String(dmarc.action || '').toLowerCase();
32
- if (policy === 'reject' || policy === 'quarantine' || action === 'reject' || action === 'quarantine') {
33
- dmarcDomain = dmarc.domain || undefined;
34
- if (action === 'reject' || policy === 'reject') {
35
- dmarcReject = true;
36
- }
37
- doubts.push(`DMARC failed for ${dmarc.domain || 'sender domain'} (policy ${dmarc.policy || 'unknown'})`);
33
+ dmarcDomain = dmarc.domain || undefined;
34
+ if (action === 'reject' || policy === 'reject') {
35
+ dmarcReject = true;
38
36
  }
37
+ doubts.push(`DMARC failed for ${dmarc.domain || 'sender domain'} (policy ${dmarc.policy || 'unknown'})`);
39
38
  }
40
39
  return {
41
40
  status: doubts.length > 0 ? 'flagged' : 'accepted',