@serve.zone/platformclient 1.1.5 → 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.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.platformclient.d.ts +2 -1
- package/dist_ts/classes.platformclient.js +7 -1
- package/dist_ts/email/classes.emailconnector.d.ts +108 -0
- package/dist_ts/email/classes.emailconnector.js +366 -1
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/package.json +4 -4
- package/readme.md +77 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.platformclient.ts +8 -1
- package/ts/email/classes.emailconnector.ts +486 -0
- package/ts/index.ts +1 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@serve.zone/platformclient',
|
|
6
|
-
version: '
|
|
6
|
+
version: '2.0.0',
|
|
7
7
|
description: 'a module that makes it really easy to use the serve.zone platform inside your app'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSw0QkFBNEI7SUFDbEMsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLG1GQUFtRjtDQUNqRyxDQUFBIn0=
|
|
@@ -35,9 +35,10 @@ export declare class SzPlatformClient {
|
|
|
35
35
|
private getConfiguredAuthorizationString;
|
|
36
36
|
private getAuthorizationStringFromBindings;
|
|
37
37
|
private setAuthorizationTag;
|
|
38
|
+
stop(): Promise<void>;
|
|
38
39
|
private discoverPlatformBindings;
|
|
39
40
|
private parsePlatformBindingsEnv;
|
|
40
41
|
private mergePlatformBindings;
|
|
41
|
-
|
|
42
|
+
getConfiguredEnvVar(envNameArg: string): Promise<string | undefined>;
|
|
42
43
|
private getProcessEnvVar;
|
|
43
44
|
}
|
|
@@ -128,6 +128,12 @@ export class SzPlatformClient {
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
+
async stop() {
|
|
132
|
+
await this.emailConnector.stop();
|
|
133
|
+
if (this.typedsocket) {
|
|
134
|
+
await this.typedsocket.stop();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
131
137
|
async discoverPlatformBindings() {
|
|
132
138
|
const bindings = [];
|
|
133
139
|
const envBindings = await this.getConfiguredEnvVar('SERVEZONE_PLATFORM_BINDINGS');
|
|
@@ -175,4 +181,4 @@ export class SzPlatformClient {
|
|
|
175
181
|
return processEnv?.[envNameArg];
|
|
176
182
|
}
|
|
177
183
|
}
|
|
178
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
184
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5wbGF0Zm9ybWNsaWVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMucGxhdGZvcm1jbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sbUNBQW1DLENBQUM7QUFDckUsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ2pFLE9BQU8sRUFBRSwyQkFBMkIsRUFBRSxNQUFNLDhDQUE4QyxDQUFDO0FBQzNGLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLG9DQUFvQyxDQUFDO0FBQ3ZFLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBWXhDLE1BQU0sT0FBTyxnQkFBZ0I7SUFjM0IsWUFBWSxzQkFBMEQ7UUFiL0QsY0FBUyxHQUFHLEtBQUssQ0FBQztRQUdsQixxQkFBZ0IsR0FBNEQsRUFBRSxDQUFDO1FBQy9FLGdCQUFXLEdBQXFDLElBQUksT0FBTyxDQUFDLFlBQVksQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUV0RixpQkFBWSxHQUFHLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUV4QyxtQkFBYyxHQUFHLElBQUksZ0JBQWdCLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDNUMsaUJBQVksR0FBRyxJQUFJLGNBQWMsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUN4Qyw4QkFBeUIsR0FBRyxJQUFJLDJCQUEyQixDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ2xFLG9CQUFlLEdBQUcsSUFBSSxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUduRCxJQUFJLENBQUMsWUFBWSxDQUFDLHNCQUFzQixDQUFDLENBQUM7SUFDNUMsQ0FBQztJQUVNLEtBQUssQ0FBQyxJQUFJLENBQUMsc0JBQTBEO1FBQzFFLElBQUksQ0FBQyxZQUFZLENBQUMsc0JBQXNCLENBQUMsQ0FBQztRQUMxQyxJQUFJLENBQUMsSUFB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
|
|
@@ -1,9 +1,102 @@
|
|
|
1
1
|
import type { SzPlatformClient } from '../classes.platformclient.js';
|
|
2
2
|
import * as plugins from '../plugins.js';
|
|
3
|
+
export type TServiceMailTlsMode = 'plain' | 'starttls' | 'implicitTls';
|
|
4
|
+
export interface IServiceMailSmtpCredentials {
|
|
5
|
+
from: string;
|
|
6
|
+
host: string;
|
|
7
|
+
port: number;
|
|
8
|
+
tlsMode: TServiceMailTlsMode;
|
|
9
|
+
username: string;
|
|
10
|
+
password: string;
|
|
11
|
+
addressToken?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface IServiceMailTypedCredentials {
|
|
14
|
+
from: string;
|
|
15
|
+
typedUrl: string;
|
|
16
|
+
credentialId: string;
|
|
17
|
+
credentialSecret: string;
|
|
18
|
+
addressToken?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface IServiceMailStatus {
|
|
21
|
+
ready: boolean;
|
|
22
|
+
from?: string;
|
|
23
|
+
host?: string;
|
|
24
|
+
port?: number;
|
|
25
|
+
tlsMode?: TServiceMailTlsMode;
|
|
26
|
+
typedUrl?: string;
|
|
27
|
+
transport?: 'typedsocket' | 'smtp' | 'none';
|
|
28
|
+
usernameConfigured: boolean;
|
|
29
|
+
passwordConfigured: boolean;
|
|
30
|
+
typedCredentialConfigured?: boolean;
|
|
31
|
+
smtpReady?: boolean;
|
|
32
|
+
missing: string[];
|
|
33
|
+
}
|
|
34
|
+
export interface IServiceMailAttachment {
|
|
35
|
+
filename: string;
|
|
36
|
+
contentType?: string;
|
|
37
|
+
content: string | Uint8Array;
|
|
38
|
+
}
|
|
39
|
+
export interface IServiceMailSendOptions {
|
|
40
|
+
from?: string;
|
|
41
|
+
to: string | string[];
|
|
42
|
+
cc?: string | string[];
|
|
43
|
+
bcc?: string | string[];
|
|
44
|
+
subject: string;
|
|
45
|
+
text?: string;
|
|
46
|
+
html?: string;
|
|
47
|
+
headers?: Record<string, string>;
|
|
48
|
+
attachments?: IServiceMailAttachment[];
|
|
49
|
+
}
|
|
50
|
+
export interface IServiceMailSendResult {
|
|
51
|
+
messageId?: string;
|
|
52
|
+
accepted?: string[];
|
|
53
|
+
rejected?: string[];
|
|
54
|
+
response?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface IServiceMailInboundMessage extends plugins.servezoneInterfaces.data.IMailInboundMessagePayload {
|
|
57
|
+
from: string;
|
|
58
|
+
to: string[];
|
|
59
|
+
receivedAtIso: string;
|
|
60
|
+
}
|
|
61
|
+
export interface IServiceMailInboundHandlerResult {
|
|
62
|
+
accepted?: boolean;
|
|
63
|
+
workAppMessageId?: string;
|
|
64
|
+
message?: string;
|
|
65
|
+
}
|
|
66
|
+
export type IServiceMailInboundHandleResult = plugins.servezoneInterfaces.requests.mail.IReq_DeliverInboundMail['response'];
|
|
67
|
+
export type TServiceMailInboundHandler = (messageArg: IServiceMailInboundMessage) => Promise<IServiceMailInboundHandlerResult | void> | IServiceMailInboundHandlerResult | void;
|
|
3
68
|
export declare class SzEmailConnector {
|
|
4
69
|
platformClientRef: SzPlatformClient;
|
|
70
|
+
private mailTypedSocket?;
|
|
71
|
+
private mailTypedSocketUrl?;
|
|
72
|
+
private mailTypedSocketPromise?;
|
|
73
|
+
private mailTypedSocketAbortController?;
|
|
74
|
+
private inboundHandler?;
|
|
75
|
+
private inboundTypedHandlerRegistered;
|
|
5
76
|
constructor(platformClientRefArg: SzPlatformClient);
|
|
77
|
+
/**
|
|
78
|
+
* Legacy platform-service email path. New service-mail code should use sendMail().
|
|
79
|
+
*/
|
|
6
80
|
sendEmail(optionsArg: plugins.servezoneInterfaces.platform.email.IReq_SendEmail['request']): Promise<string | undefined>;
|
|
81
|
+
getServiceMailStatus(addressArg?: string): Promise<IServiceMailStatus>;
|
|
82
|
+
getServiceMailCredentials(addressArg?: string): Promise<IServiceMailSmtpCredentials>;
|
|
83
|
+
sendText(optionsArg: Omit<IServiceMailSendOptions, 'html'> & {
|
|
84
|
+
text: string;
|
|
85
|
+
}): Promise<IServiceMailSendResult>;
|
|
86
|
+
sendHtml(optionsArg: Omit<IServiceMailSendOptions, 'text'> & {
|
|
87
|
+
html: string;
|
|
88
|
+
}): Promise<IServiceMailSendResult>;
|
|
89
|
+
sendMail(optionsArg: IServiceMailSendOptions): Promise<IServiceMailSendResult>;
|
|
90
|
+
registerInboundHandler(handlerArg: TServiceMailInboundHandler, addressArg?: string): Promise<plugins.servezoneInterfaces.requests.mail.IReq_RegisterServiceMailEndpoint['response']>;
|
|
91
|
+
stop(): Promise<void>;
|
|
92
|
+
normalizeInboundMessage(payloadArg: unknown): IServiceMailInboundMessage;
|
|
93
|
+
handleInboundPayload(payloadArg: unknown, handlerArg: TServiceMailInboundHandler): Promise<IServiceMailInboundHandleResult>;
|
|
94
|
+
createInboundHandler(handlerArg: TServiceMailInboundHandler): (payloadArg: unknown) => Promise<{
|
|
95
|
+
accepted: boolean;
|
|
96
|
+
workAppMessageId?: string;
|
|
97
|
+
message?: string;
|
|
98
|
+
}>;
|
|
99
|
+
private ensureInboundTypedHandler;
|
|
7
100
|
sendNotification(optionsArg: {
|
|
8
101
|
toArg: string;
|
|
9
102
|
subject: string;
|
|
@@ -14,4 +107,19 @@ export declare class SzEmailConnector {
|
|
|
14
107
|
subject: string;
|
|
15
108
|
text: string;
|
|
16
109
|
}): Promise<void>;
|
|
110
|
+
private readMailEnv;
|
|
111
|
+
private readMailEnvVar;
|
|
112
|
+
private getCredentialEnvVarFromBindings;
|
|
113
|
+
private hasAnyMailEnvValue;
|
|
114
|
+
private getServiceMailTypedCredentials;
|
|
115
|
+
private getMailTypedSocket;
|
|
116
|
+
private stopMailTypedSocket;
|
|
117
|
+
private normalizeTypedSocketUrl;
|
|
118
|
+
private normalizeTlsMode;
|
|
119
|
+
private getMailEnvToken;
|
|
120
|
+
private listToArray;
|
|
121
|
+
private normalizeAttachmentContent;
|
|
122
|
+
private asRecord;
|
|
123
|
+
private normalizeHeaders;
|
|
124
|
+
private extractHeader;
|
|
17
125
|
}
|
|
@@ -2,8 +2,12 @@ import * as plugins from '../plugins.js';
|
|
|
2
2
|
import { logger } from '../logger.js';
|
|
3
3
|
export class SzEmailConnector {
|
|
4
4
|
constructor(platformClientRefArg) {
|
|
5
|
+
this.inboundTypedHandlerRegistered = false;
|
|
5
6
|
this.platformClientRef = platformClientRefArg;
|
|
6
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Legacy platform-service email path. New service-mail code should use sendMail().
|
|
10
|
+
*/
|
|
7
11
|
async sendEmail(optionsArg) {
|
|
8
12
|
if (this.platformClientRef.debugMode) {
|
|
9
13
|
logger.log('info', `sent email with subject ${optionsArg.title} to ${optionsArg.to}
|
|
@@ -19,7 +23,368 @@ export class SzEmailConnector {
|
|
|
19
23
|
const response = await typedRequest.fire(optionsArg);
|
|
20
24
|
return response.responseId;
|
|
21
25
|
}
|
|
26
|
+
async getServiceMailStatus(addressArg) {
|
|
27
|
+
const requestedAddress = addressArg?.trim().toLowerCase();
|
|
28
|
+
const token = requestedAddress ? this.getMailEnvToken(requestedAddress) : undefined;
|
|
29
|
+
const scopedEnv = token ? await this.readMailEnv(token) : undefined;
|
|
30
|
+
const defaultEnv = token ? await this.readMailEnv() : undefined;
|
|
31
|
+
const useDefaultEnv = requestedAddress
|
|
32
|
+
&& !this.hasAnyMailEnvValue(scopedEnv)
|
|
33
|
+
&& defaultEnv?.from?.trim().toLowerCase() === requestedAddress;
|
|
34
|
+
const env = useDefaultEnv ? defaultEnv : scopedEnv || await this.readMailEnv();
|
|
35
|
+
const missingPrefix = useDefaultEnv || !token ? undefined : token;
|
|
36
|
+
const port = env.smtpPort ? Number(env.smtpPort) : undefined;
|
|
37
|
+
const tlsMode = this.normalizeTlsMode(env.smtpTlsMode || 'starttls');
|
|
38
|
+
const typedMissing = [];
|
|
39
|
+
const smtpMissing = [];
|
|
40
|
+
if (!env.typedUrl)
|
|
41
|
+
typedMissing.push(missingPrefix ? `MAIL_${missingPrefix}_TYPED_URL` : 'MAIL_TYPED_URL');
|
|
42
|
+
if (!env.apiCredentialId)
|
|
43
|
+
typedMissing.push(missingPrefix ? `MAIL_${missingPrefix}_API_CREDENTIAL_ID` : 'MAIL_API_CREDENTIAL_ID');
|
|
44
|
+
if (!env.apiCredentialSecret)
|
|
45
|
+
typedMissing.push(missingPrefix ? `MAIL_${missingPrefix}_API_CREDENTIAL_SECRET` : 'MAIL_API_CREDENTIAL_SECRET');
|
|
46
|
+
if (!env.smtpHost)
|
|
47
|
+
smtpMissing.push(missingPrefix ? `MAIL_${missingPrefix}_SMTP_HOST` : 'SMTP_HOST');
|
|
48
|
+
if (!env.smtpPort || !Number.isInteger(port) || port < 1 || port > 65535) {
|
|
49
|
+
smtpMissing.push(missingPrefix ? `MAIL_${missingPrefix}_SMTP_PORT` : 'SMTP_PORT');
|
|
50
|
+
}
|
|
51
|
+
if (!env.smtpUsername)
|
|
52
|
+
smtpMissing.push(missingPrefix ? `MAIL_${missingPrefix}_SMTP_USERNAME` : 'SMTP_USERNAME');
|
|
53
|
+
if (!env.smtpPassword)
|
|
54
|
+
smtpMissing.push(missingPrefix ? `MAIL_${missingPrefix}_SMTP_PASSWORD` : 'SMTP_PASSWORD');
|
|
55
|
+
const fromMissing = env.from ? [] : [missingPrefix ? `MAIL_${missingPrefix}_FROM` : 'MAIL_FROM'];
|
|
56
|
+
const typedReady = fromMissing.length === 0 && typedMissing.length === 0;
|
|
57
|
+
const smtpReady = fromMissing.length === 0 && smtpMissing.length === 0;
|
|
58
|
+
return {
|
|
59
|
+
ready: typedReady,
|
|
60
|
+
from: env.from,
|
|
61
|
+
host: env.smtpHost,
|
|
62
|
+
port,
|
|
63
|
+
tlsMode,
|
|
64
|
+
typedUrl: env.typedUrl,
|
|
65
|
+
transport: typedReady ? 'typedsocket' : smtpReady ? 'smtp' : 'none',
|
|
66
|
+
usernameConfigured: Boolean(env.smtpUsername),
|
|
67
|
+
passwordConfigured: Boolean(env.smtpPassword),
|
|
68
|
+
typedCredentialConfigured: Boolean(env.apiCredentialId && env.apiCredentialSecret),
|
|
69
|
+
smtpReady,
|
|
70
|
+
missing: typedReady ? [] : [...fromMissing, ...typedMissing],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
async getServiceMailCredentials(addressArg) {
|
|
74
|
+
const requestedAddress = addressArg?.trim().toLowerCase();
|
|
75
|
+
const requestedToken = requestedAddress ? this.getMailEnvToken(requestedAddress) : undefined;
|
|
76
|
+
const scopedEnv = requestedToken ? await this.readMailEnv(requestedToken) : undefined;
|
|
77
|
+
const hasScopedConfig = scopedEnv && Object.values(scopedEnv).some(Boolean);
|
|
78
|
+
const env = hasScopedConfig ? scopedEnv : await this.readMailEnv();
|
|
79
|
+
const from = env.from?.trim().toLowerCase();
|
|
80
|
+
if (!from)
|
|
81
|
+
throw new Error('MAIL_FROM is required for service mail');
|
|
82
|
+
if (requestedAddress && from !== requestedAddress) {
|
|
83
|
+
throw new Error(`No service mail credentials are configured for ${requestedAddress}`);
|
|
84
|
+
}
|
|
85
|
+
const port = Number(env.smtpPort);
|
|
86
|
+
if (!env.smtpHost)
|
|
87
|
+
throw new Error('SMTP_HOST is required for service mail');
|
|
88
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
89
|
+
throw new Error('SMTP_PORT must be an integer between 1 and 65535');
|
|
90
|
+
}
|
|
91
|
+
if (!env.smtpUsername)
|
|
92
|
+
throw new Error('SMTP_USERNAME is required for service mail');
|
|
93
|
+
if (!env.smtpPassword)
|
|
94
|
+
throw new Error('SMTP_PASSWORD is required for service mail');
|
|
95
|
+
return {
|
|
96
|
+
from,
|
|
97
|
+
host: env.smtpHost,
|
|
98
|
+
port,
|
|
99
|
+
tlsMode: this.normalizeTlsMode(env.smtpTlsMode || 'starttls'),
|
|
100
|
+
username: env.smtpUsername,
|
|
101
|
+
password: env.smtpPassword,
|
|
102
|
+
addressToken: hasScopedConfig ? requestedToken : undefined,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async sendText(optionsArg) {
|
|
106
|
+
return await this.sendMail(optionsArg);
|
|
107
|
+
}
|
|
108
|
+
async sendHtml(optionsArg) {
|
|
109
|
+
return await this.sendMail(optionsArg);
|
|
110
|
+
}
|
|
111
|
+
async sendMail(optionsArg) {
|
|
112
|
+
if (!optionsArg.text && !optionsArg.html) {
|
|
113
|
+
throw new Error('Either text or html is required for service mail');
|
|
114
|
+
}
|
|
115
|
+
if (this.platformClientRef.debugMode) {
|
|
116
|
+
logger.log('info', `service mail debug send: ${optionsArg.subject} to ${this.listToArray(optionsArg.to).join(', ')}`);
|
|
117
|
+
return { messageId: 'debug' };
|
|
118
|
+
}
|
|
119
|
+
const credentials = await this.getServiceMailTypedCredentials(optionsArg.from);
|
|
120
|
+
const typedSocket = await this.getMailTypedSocket(credentials.typedUrl);
|
|
121
|
+
const request = typedSocket.createTypedRequest('enqueueMail');
|
|
122
|
+
const result = await request.fire({
|
|
123
|
+
auth: {
|
|
124
|
+
credentialId: credentials.credentialId,
|
|
125
|
+
credentialSecret: credentials.credentialSecret,
|
|
126
|
+
},
|
|
127
|
+
outboundIdentityId: credentials.credentialId,
|
|
128
|
+
message: {
|
|
129
|
+
from: credentials.from,
|
|
130
|
+
to: this.listToArray(optionsArg.to),
|
|
131
|
+
cc: optionsArg.cc ? this.listToArray(optionsArg.cc) : undefined,
|
|
132
|
+
bcc: optionsArg.bcc ? this.listToArray(optionsArg.bcc) : undefined,
|
|
133
|
+
subject: optionsArg.subject,
|
|
134
|
+
text: optionsArg.text,
|
|
135
|
+
html: optionsArg.html,
|
|
136
|
+
headers: optionsArg.headers,
|
|
137
|
+
attachments: optionsArg.attachments?.map((attachmentArg) => ({
|
|
138
|
+
filename: attachmentArg.filename,
|
|
139
|
+
contentType: attachmentArg.contentType,
|
|
140
|
+
binaryAttachmentString: this.normalizeAttachmentContent(attachmentArg.content),
|
|
141
|
+
})),
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
if (!result.accepted) {
|
|
145
|
+
throw new Error(result.message || 'service mail was not accepted by dcrouter');
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
messageId: result.spoolItemId,
|
|
149
|
+
accepted: this.listToArray(optionsArg.to),
|
|
150
|
+
rejected: [],
|
|
151
|
+
response: result.message,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
async registerInboundHandler(handlerArg, addressArg) {
|
|
155
|
+
this.inboundHandler = handlerArg;
|
|
156
|
+
this.ensureInboundTypedHandler();
|
|
157
|
+
const credentials = await this.getServiceMailTypedCredentials(addressArg);
|
|
158
|
+
const typedSocket = await this.getMailTypedSocket(credentials.typedUrl);
|
|
159
|
+
const request = typedSocket.createTypedRequest('registerServiceMailEndpoint');
|
|
160
|
+
const result = await request.fire({
|
|
161
|
+
auth: {
|
|
162
|
+
credentialId: credentials.credentialId,
|
|
163
|
+
credentialSecret: credentials.credentialSecret,
|
|
164
|
+
},
|
|
165
|
+
addresses: [credentials.from],
|
|
166
|
+
});
|
|
167
|
+
if (!result.success) {
|
|
168
|
+
throw new Error(result.message || 'Could not register service mail endpoint');
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
async stop() {
|
|
173
|
+
await this.stopMailTypedSocket();
|
|
174
|
+
}
|
|
175
|
+
normalizeInboundMessage(payloadArg) {
|
|
176
|
+
const payload = this.asRecord(payloadArg);
|
|
177
|
+
const deliveryCandidate = this.asRecord(payload.delivery || payload.message || payload);
|
|
178
|
+
const envelope = this.asRecord(deliveryCandidate.envelope);
|
|
179
|
+
const rawMessage = typeof deliveryCandidate.rawMessage === 'string' ? deliveryCandidate.rawMessage : '';
|
|
180
|
+
const rcptTo = Array.isArray(envelope.rcptTo) ? envelope.rcptTo.map(String) : [];
|
|
181
|
+
const receivedAt = typeof deliveryCandidate.receivedAt === 'number' ? deliveryCandidate.receivedAt : Date.now();
|
|
182
|
+
return {
|
|
183
|
+
spoolItemId: String(deliveryCandidate.spoolItemId || deliveryCandidate.id || ''),
|
|
184
|
+
owner: deliveryCandidate.owner,
|
|
185
|
+
envelope: {
|
|
186
|
+
mailFrom: String(envelope.mailFrom || deliveryCandidate.from || ''),
|
|
187
|
+
rcptTo,
|
|
188
|
+
},
|
|
189
|
+
rawMessage,
|
|
190
|
+
sizeBytes: typeof deliveryCandidate.sizeBytes === 'number' ? deliveryCandidate.sizeBytes : rawMessage.length,
|
|
191
|
+
messageId: deliveryCandidate.messageId ? String(deliveryCandidate.messageId) : undefined,
|
|
192
|
+
subject: deliveryCandidate.subject ? String(deliveryCandidate.subject) : this.extractHeader(rawMessage, 'subject'),
|
|
193
|
+
headers: this.normalizeHeaders(deliveryCandidate.headers),
|
|
194
|
+
source: deliveryCandidate.source,
|
|
195
|
+
receivedAt,
|
|
196
|
+
from: String(envelope.mailFrom || deliveryCandidate.from || ''),
|
|
197
|
+
to: rcptTo,
|
|
198
|
+
receivedAtIso: new Date(receivedAt).toISOString(),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
async handleInboundPayload(payloadArg, handlerArg) {
|
|
202
|
+
const message = this.normalizeInboundMessage(payloadArg);
|
|
203
|
+
const result = await handlerArg(message);
|
|
204
|
+
return {
|
|
205
|
+
accepted: result?.accepted !== false,
|
|
206
|
+
workAppMessageId: result?.workAppMessageId,
|
|
207
|
+
message: result?.message,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
createInboundHandler(handlerArg) {
|
|
211
|
+
return async (payloadArg) => await this.handleInboundPayload(payloadArg, handlerArg);
|
|
212
|
+
}
|
|
213
|
+
ensureInboundTypedHandler() {
|
|
214
|
+
if (this.inboundTypedHandlerRegistered)
|
|
215
|
+
return;
|
|
216
|
+
this.inboundTypedHandlerRegistered = true;
|
|
217
|
+
this.platformClientRef.typedrouter.addTypedHandler(new plugins.typedrequest.TypedHandler('deliverInboundMail', async (dataArg) => {
|
|
218
|
+
if (!this.inboundHandler) {
|
|
219
|
+
return { accepted: false, message: 'No service mail inbound handler registered' };
|
|
220
|
+
}
|
|
221
|
+
return await this.handleInboundPayload(dataArg.delivery, this.inboundHandler);
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
22
224
|
async sendNotification(optionsArg) { }
|
|
23
225
|
async sendActionLink(optionsArg) { }
|
|
226
|
+
async readMailEnv(tokenArg) {
|
|
227
|
+
const prefix = tokenArg ? `MAIL_${tokenArg}_` : '';
|
|
228
|
+
return {
|
|
229
|
+
from: await this.readMailEnvVar(tokenArg ? `${prefix}FROM` : 'MAIL_FROM'),
|
|
230
|
+
smtpHost: await this.readMailEnvVar(`${prefix}SMTP_HOST`),
|
|
231
|
+
smtpPort: await this.readMailEnvVar(`${prefix}SMTP_PORT`),
|
|
232
|
+
smtpTlsMode: await this.readMailEnvVar(`${prefix}SMTP_TLS_MODE`),
|
|
233
|
+
smtpUsername: await this.readMailEnvVar(`${prefix}SMTP_USERNAME`),
|
|
234
|
+
smtpPassword: await this.readMailEnvVar(`${prefix}SMTP_PASSWORD`),
|
|
235
|
+
typedUrl: await this.readMailEnvVar(tokenArg ? `${prefix}TYPED_URL` : 'MAIL_TYPED_URL'),
|
|
236
|
+
apiCredentialId: await this.readMailEnvVar(tokenArg ? `${prefix}API_CREDENTIAL_ID` : 'MAIL_API_CREDENTIAL_ID'),
|
|
237
|
+
apiCredentialSecret: await this.readMailEnvVar(tokenArg ? `${prefix}API_CREDENTIAL_SECRET` : 'MAIL_API_CREDENTIAL_SECRET'),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
async readMailEnvVar(envNameArg) {
|
|
241
|
+
return await this.platformClientRef.getConfiguredEnvVar(envNameArg)
|
|
242
|
+
|| this.getCredentialEnvVarFromBindings(envNameArg);
|
|
243
|
+
}
|
|
244
|
+
getCredentialEnvVarFromBindings(envNameArg) {
|
|
245
|
+
for (const binding of this.platformClientRef.platformBindings || []) {
|
|
246
|
+
if (binding.desiredState === 'disabled' || binding.status === 'failed')
|
|
247
|
+
continue;
|
|
248
|
+
if (binding.capability !== 'email')
|
|
249
|
+
continue;
|
|
250
|
+
for (const credential of binding.credentials || []) {
|
|
251
|
+
const value = credential.env?.[envNameArg];
|
|
252
|
+
if (value)
|
|
253
|
+
return value;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
hasAnyMailEnvValue(envArg) {
|
|
258
|
+
return Boolean(envArg && Object.values(envArg).some(Boolean));
|
|
259
|
+
}
|
|
260
|
+
async getServiceMailTypedCredentials(addressArg) {
|
|
261
|
+
const requestedAddress = addressArg?.trim().toLowerCase();
|
|
262
|
+
const requestedToken = requestedAddress ? this.getMailEnvToken(requestedAddress) : undefined;
|
|
263
|
+
const scopedEnv = requestedToken ? await this.readMailEnv(requestedToken) : undefined;
|
|
264
|
+
const hasScopedConfig = scopedEnv && Object.values(scopedEnv).some(Boolean);
|
|
265
|
+
const env = hasScopedConfig ? scopedEnv : await this.readMailEnv();
|
|
266
|
+
const from = env.from?.trim().toLowerCase();
|
|
267
|
+
if (!from)
|
|
268
|
+
throw new Error('MAIL_FROM is required for typed service mail');
|
|
269
|
+
if (requestedAddress && from !== requestedAddress) {
|
|
270
|
+
throw new Error(`No typed service mail credentials are configured for ${requestedAddress}`);
|
|
271
|
+
}
|
|
272
|
+
if (!env.typedUrl)
|
|
273
|
+
throw new Error('MAIL_TYPED_URL is required for typed service mail');
|
|
274
|
+
if (!env.apiCredentialId)
|
|
275
|
+
throw new Error('MAIL_API_CREDENTIAL_ID is required for typed service mail');
|
|
276
|
+
if (!env.apiCredentialSecret)
|
|
277
|
+
throw new Error('MAIL_API_CREDENTIAL_SECRET is required for typed service mail');
|
|
278
|
+
return {
|
|
279
|
+
from,
|
|
280
|
+
typedUrl: this.normalizeTypedSocketUrl(env.typedUrl),
|
|
281
|
+
credentialId: env.apiCredentialId,
|
|
282
|
+
credentialSecret: env.apiCredentialSecret,
|
|
283
|
+
addressToken: hasScopedConfig ? requestedToken : undefined,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
async getMailTypedSocket(urlArg) {
|
|
287
|
+
const normalizedUrl = this.normalizeTypedSocketUrl(urlArg);
|
|
288
|
+
if (this.mailTypedSocket && this.mailTypedSocketUrl === normalizedUrl) {
|
|
289
|
+
return this.mailTypedSocket;
|
|
290
|
+
}
|
|
291
|
+
if (this.mailTypedSocketPromise && this.mailTypedSocketUrl === normalizedUrl) {
|
|
292
|
+
return await this.mailTypedSocketPromise;
|
|
293
|
+
}
|
|
294
|
+
if (this.mailTypedSocket || this.mailTypedSocketPromise) {
|
|
295
|
+
await this.stopMailTypedSocket();
|
|
296
|
+
}
|
|
297
|
+
this.mailTypedSocketUrl = normalizedUrl;
|
|
298
|
+
const abortController = new AbortController();
|
|
299
|
+
this.mailTypedSocketAbortController = abortController;
|
|
300
|
+
const socketPromise = plugins.typedsocket.TypedSocket.createClient(this.platformClientRef.typedrouter, normalizedUrl, { autoReconnect: true, abortSignal: abortController.signal });
|
|
301
|
+
this.mailTypedSocketPromise = socketPromise;
|
|
302
|
+
try {
|
|
303
|
+
const socket = await socketPromise;
|
|
304
|
+
if (this.mailTypedSocketPromise === socketPromise && this.mailTypedSocketUrl === normalizedUrl) {
|
|
305
|
+
this.mailTypedSocket = socket;
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
await socket.stop();
|
|
309
|
+
}
|
|
310
|
+
return socket;
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
if (this.mailTypedSocketPromise === socketPromise) {
|
|
314
|
+
this.mailTypedSocketPromise = undefined;
|
|
315
|
+
this.mailTypedSocketUrl = undefined;
|
|
316
|
+
this.mailTypedSocketAbortController = undefined;
|
|
317
|
+
}
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
async stopMailTypedSocket() {
|
|
322
|
+
const socket = this.mailTypedSocket;
|
|
323
|
+
const socketPromise = this.mailTypedSocketPromise;
|
|
324
|
+
const abortController = this.mailTypedSocketAbortController;
|
|
325
|
+
this.mailTypedSocket = undefined;
|
|
326
|
+
this.mailTypedSocketPromise = undefined;
|
|
327
|
+
this.mailTypedSocketUrl = undefined;
|
|
328
|
+
this.mailTypedSocketAbortController = undefined;
|
|
329
|
+
abortController?.abort();
|
|
330
|
+
if (socket) {
|
|
331
|
+
await socket.stop();
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (socketPromise) {
|
|
335
|
+
try {
|
|
336
|
+
const pendingSocket = await socketPromise;
|
|
337
|
+
await pendingSocket.stop();
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
// The pending connection already failed; there is no socket to stop.
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
normalizeTypedSocketUrl(urlArg) {
|
|
345
|
+
return urlArg.trim().replace(/\/typedrequest\/?$/, '').replace(/\/+$/, '');
|
|
346
|
+
}
|
|
347
|
+
normalizeTlsMode(valueArg) {
|
|
348
|
+
if (valueArg === 'plain' || valueArg === 'starttls' || valueArg === 'implicitTls')
|
|
349
|
+
return valueArg;
|
|
350
|
+
throw new Error(`Invalid SMTP_TLS_MODE: ${valueArg}`);
|
|
351
|
+
}
|
|
352
|
+
getMailEnvToken(addressArg) {
|
|
353
|
+
const token = addressArg.trim().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').toUpperCase();
|
|
354
|
+
return /^\d/.test(token) ? `ADDR_${token}` : token;
|
|
355
|
+
}
|
|
356
|
+
listToArray(valueArg) {
|
|
357
|
+
return Array.isArray(valueArg) ? valueArg : [valueArg];
|
|
358
|
+
}
|
|
359
|
+
normalizeAttachmentContent(contentArg) {
|
|
360
|
+
return Buffer.from(contentArg).toString('base64');
|
|
361
|
+
}
|
|
362
|
+
asRecord(valueArg) {
|
|
363
|
+
return valueArg && typeof valueArg === 'object' ? valueArg : {};
|
|
364
|
+
}
|
|
365
|
+
normalizeHeaders(headersArg) {
|
|
366
|
+
if (!headersArg || typeof headersArg !== 'object')
|
|
367
|
+
return undefined;
|
|
368
|
+
const result = {};
|
|
369
|
+
for (const [key, value] of Object.entries(headersArg)) {
|
|
370
|
+
if (Array.isArray(value)) {
|
|
371
|
+
result[key] = value.map(String);
|
|
372
|
+
}
|
|
373
|
+
else if (value !== undefined && value !== null) {
|
|
374
|
+
result[key] = String(value);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return result;
|
|
378
|
+
}
|
|
379
|
+
extractHeader(rawMessageArg, headerNameArg) {
|
|
380
|
+
const lowerName = `${headerNameArg.toLowerCase()}:`;
|
|
381
|
+
for (const line of rawMessageArg.split(/\r?\n/)) {
|
|
382
|
+
if (!line)
|
|
383
|
+
return undefined;
|
|
384
|
+
if (line.toLowerCase().startsWith(lowerName)) {
|
|
385
|
+
return line.slice(lowerName.length).trim();
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
24
389
|
}
|
|
25
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5lbWFpbGNvbm5lY3Rvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2VtYWlsL2NsYXNzZXMuZW1haWxjb25uZWN0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFFekMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUV0QyxNQUFNLE9BQU8sZ0JBQWdCO0lBRzNCLFlBQVksb0JBQXNDO1FBQ2hELElBQUksQ0FBQyxpQkFBaUIsR0FBRyxvQkFBb0IsQ0FBQztJQUNoRCxDQUFDO0lBRU0sS0FBSyxDQUFDLFNBQVMsQ0FDcEIsVUFBZ0Y7UUFFaEYsSUFBSSxJQUFJLENBQUMsaUJBQWlCLENBQUMsU0FBUyxFQUFFLENBQUM7WUFDckMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsMkJBQTJCLFVBQVUsQ0FBQyxLQUFLLE9BQU8sVUFBVSxDQUFDLEVBQUU7O1VBRTlFLFVBQVUsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLFVBQVUsSUFBSSxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDOztTQUVyRSxDQUFDLENBQUM7UUFDUCxDQUFDO1FBRUQsSUFBSSxJQUFJLENBQUMsaUJBQWlCLENBQUMsU0FBUyxFQUFFLENBQUM7WUFDckMsT0FBTztRQUNULENBQUM7UUFFRCxNQUFNLFlBQVksR0FDaEIsSUFBSSxDQUFDLGlCQUFpQixDQUFDLFdBQVcsQ0FBQyxrQkFBa0IsQ0FDbkQsV0FBVyxDQUNaLENBQUM7UUFDSixNQUFNLFFBQVEsR0FBRyxNQUFNLFlBQVksQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDckQsT0FBTyxRQUFRLENBQUMsVUFBVSxDQUFDO0lBQzdCLENBQUM7SUFFTSxLQUFLLENBQUMsZ0JBQWdCLENBQUMsVUFBNEQsSUFBRyxDQUFDO0lBRXZGLEtBQUssQ0FBQyxjQUFjLENBQUMsVUFBNEQsSUFBRyxDQUFDO0NBQzdGIn0=
|
|
390
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5lbWFpbGNvbm5lY3Rvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2VtYWlsL2NsYXNzZXMuZW1haWxjb25uZWN0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFFekMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGNBQWMsQ0FBQztBQWlGdEMsTUFBTSxPQUFPLGdCQUFnQjtJQVMzQixZQUFZLG9CQUFzQztRQUYxQyxrQ0FBNkIsR0FBRyxLQUFLLENBQUM7UUFHNUMsSUFBSSxDQUFDLGlCQUFpQixHQUFHLG9CQUFvQixDQUFDO0lBQ2hELENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxTQUFTLENBQ3BCLFVBQWdGO1FBRWhGLElBQUksSUFBSSxDQUFDLGlCQUFpQixDQUFDLFNBQVMsRUFBRSxDQUFDO1lBQ3JDLE1BQU0sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLDJCQUEyQixVQUFVLENBQUMsS0FBSyxPQUFPLFVBQVUsQ0FBQyxFQUFFOztVQUU5RSxVQUFVLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxVQUFVLElBQUksRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQzs7U0FFckUsQ0FBQyxDQUFDO1FBQ1AsQ0FBQztRQUVELElBQUksSUFBSSxDQUFDLGlCQUFpQixDQUFDLFNBQVMsRUFBRSxDQUFDO1lBQ3JDLE9BQU87UUFDVCxDQUFDO1FBRUQsTUFBTSxZQUFZLEdBQ2hCLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxXQUFXLENBQUMsa0JBQWtCLENBQ25ELFdBQVcsQ0FDWixDQUFDO1FBQ0osTUFBTSxRQUFRLEdBQUcsTUFBTSxZQUFZLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQ3JELE9BQU8sUUFBUSxDQUFDLFVBQVUsQ0FB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
|
package/dist_ts/index.d.ts
CHANGED
package/dist_ts/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export * from './classes.platformclient.js';
|
|
2
|
-
|
|
2
|
+
export * from './email/classes.emailconnector.js';
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLDZCQUE2QixDQUFDO0FBQzVDLGNBQWMsbUNBQW1DLENBQUMifQ==
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serve.zone/platformclient",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "a module that makes it really easy to use the serve.zone platform inside your app",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@git.zone/tsbuild": "^4.4.2",
|
|
13
|
-
"@git.zone/tsbundle": "^2.
|
|
13
|
+
"@git.zone/tsbundle": "^2.11.0",
|
|
14
14
|
"@git.zone/tsrun": "^2.0.4",
|
|
15
15
|
"@git.zone/tstest": "^3.6.6",
|
|
16
|
-
"@types/node": "^25.9.
|
|
16
|
+
"@types/node": "^25.9.3"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@api.global/typedrequest": "^3.3.0",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"@push.rocks/qenv": "^6.1.4",
|
|
23
23
|
"@push.rocks/smartlog": "^3.2.2",
|
|
24
24
|
"@push.rocks/smartntml": "^2.0.9",
|
|
25
|
-
"@serve.zone/interfaces": "^
|
|
25
|
+
"@serve.zone/interfaces": "^7.28.0"
|
|
26
26
|
},
|
|
27
27
|
"repository": {
|
|
28
28
|
"type": "git",
|
package/readme.md
CHANGED
|
@@ -38,7 +38,7 @@ await platformClient.emailConnector.sendEmail({
|
|
|
38
38
|
|
|
39
39
|
| Connector | Main methods | Platform capability |
|
|
40
40
|
| --- | --- | --- |
|
|
41
|
-
| `emailConnector` | `sendEmail()` | `email` |
|
|
41
|
+
| `emailConnector` | `getServiceMailStatus()`, `getServiceMailCredentials()`, `sendMail()`, `sendText()`, `sendHtml()`, `registerInboundHandler()`, `normalizeInboundMessage()`, `handleInboundPayload()`, `createInboundHandler()`, `sendEmail()` | `email` |
|
|
42
42
|
| `smsConnector` | `sendSms()`, `sendSmsVerifcation()` | `sms` |
|
|
43
43
|
| `pushNotificationConnector` | `sendPushNotification()` | `pushnotification` |
|
|
44
44
|
| `letterConnector` | `sendLetter()` | `letter` |
|
|
@@ -57,6 +57,10 @@ The client needs an authorization string and a platform endpoint. You can provid
|
|
|
57
57
|
|
|
58
58
|
Binding environment variables must contain JSON encoded `IPlatformBinding` objects from `@serve.zone/interfaces`.
|
|
59
59
|
|
|
60
|
+
Service mail credentials can be provided directly as environment variables or through `email` platform binding credentials. Standard platformclient mail uses dcrouter TypedSocket/TypedRequest service credentials. The default sender uses `MAIL_FROM`, `MAIL_TYPED_URL`, `MAIL_API_CREDENTIAL_ID`, and `MAIL_API_CREDENTIAL_SECRET`. Additional configured sender addresses use scoped variables such as `MAIL_TEST_SERVICE_GATED_ONE_FROM`, `MAIL_TEST_SERVICE_GATED_ONE_TYPED_URL`, `MAIL_TEST_SERVICE_GATED_ONE_API_CREDENTIAL_ID`, and `MAIL_TEST_SERVICE_GATED_ONE_API_CREDENTIAL_SECRET`.
|
|
61
|
+
|
|
62
|
+
SMTP compatibility credentials are still discoverable with `getServiceMailCredentials()` for applications that explicitly implement SMTP themselves. Those compatibility variables are `SMTP_HOST`, `SMTP_PORT`, `SMTP_TLS_MODE`, `SMTP_USERNAME`, and `SMTP_PASSWORD`, plus scoped `MAIL_<TOKEN>_SMTP_*` variants. They are not used by `sendMail()`, `sendText()`, `sendHtml()`, or `registerInboundHandler()`.
|
|
63
|
+
|
|
60
64
|
```typescript
|
|
61
65
|
import { SzPlatformClient } from '@serve.zone/platformclient';
|
|
62
66
|
|
|
@@ -100,9 +104,79 @@ console.log(verificationCode); // 123456
|
|
|
100
104
|
|
|
101
105
|
The current SMS verification method is spelled `sendSmsVerifcation()` in code. Use that exact method name until the public API changes.
|
|
102
106
|
|
|
107
|
+
## Lifecycle
|
|
108
|
+
|
|
109
|
+
`SzPlatformClient` keeps TypedSocket connections open for platform requests and typed service-mail delivery. Call `stop()` when a short-lived process, worker, or test is done with the client.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
const client = new SzPlatformClient({
|
|
113
|
+
token: process.env.SERVEZONE_PLATFORM_TOKEN,
|
|
114
|
+
platformUrl: process.env.SERVEZONE_PLATFORM_URL,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await client.init();
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
await client.emailConnector.sendText({
|
|
121
|
+
to: 'user@example.com',
|
|
122
|
+
subject: 'Done',
|
|
123
|
+
text: 'The job has finished.',
|
|
124
|
+
});
|
|
125
|
+
} finally {
|
|
126
|
+
await client.stop();
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
103
130
|
## Connector Examples
|
|
104
131
|
|
|
105
|
-
|
|
132
|
+
Service mail over managed TypedSocket credentials:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
const status = await client.emailConnector.getServiceMailStatus();
|
|
136
|
+
|
|
137
|
+
if (status.ready) {
|
|
138
|
+
await client.emailConnector.sendText({
|
|
139
|
+
to: 'user@example.com',
|
|
140
|
+
subject: 'Service mail ready',
|
|
141
|
+
text: 'This message was sent through the managed service mail identity.',
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`getServiceMailStatus().ready` means the typed service-mail path is configured. The status also exposes `transport`, `typedUrl`, `typedCredentialConfigured`, and `smtpReady` so applications can distinguish standard TypedSocket mail from explicit SMTP compatibility configuration.
|
|
147
|
+
|
|
148
|
+
Specific sender address:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
await client.emailConnector.sendHtml({
|
|
152
|
+
from: 'test-service@gated.one',
|
|
153
|
+
to: 'user@example.com',
|
|
154
|
+
subject: 'Platform capability test',
|
|
155
|
+
html: '<p>The service mail identity is working.</p>',
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Typed inbound mail registration:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
await client.emailConnector.registerInboundHandler(async (message) => {
|
|
163
|
+
console.log(message.from, message.to, message.subject);
|
|
164
|
+
return { accepted: true, workAppMessageId: message.messageId };
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`registerInboundHandler()` registers this client connection as the typed endpoint for the configured service mail address. dcrouter then delivers inbound messages through the shared `deliverInboundMail` TypedRequest contract.
|
|
169
|
+
|
|
170
|
+
Inbound message normalization for webhook or direct TypedRequest-style delivery:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
const inboundHandler = client.emailConnector.createInboundHandler(async (message) => {
|
|
174
|
+
console.log(message.from, message.to, message.subject);
|
|
175
|
+
return { accepted: true };
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Legacy platform-service email request:
|
|
106
180
|
|
|
107
181
|
```typescript
|
|
108
182
|
await client.emailConnector.sendEmail({
|
|
@@ -144,7 +218,7 @@ await client.letterConnector.sendLetter({
|
|
|
144
218
|
});
|
|
145
219
|
```
|
|
146
220
|
|
|
147
|
-
|
|
221
|
+
Service-mail helper option and result types are exported from `@serve.zone/platformclient`. The legacy platform-service request fields are defined in `@serve.zone/interfaces` under `platform.email`, `platform.sms`, `platform.pushnotification`, and `platform.letter`.
|
|
148
222
|
|
|
149
223
|
## Platform Bindings
|
|
150
224
|
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -174,6 +174,13 @@ export class SzPlatformClient {
|
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
public async stop(): Promise<void> {
|
|
178
|
+
await this.emailConnector.stop();
|
|
179
|
+
if (this.typedsocket) {
|
|
180
|
+
await this.typedsocket.stop();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
177
184
|
private async discoverPlatformBindings() {
|
|
178
185
|
const bindings: plugins.servezoneInterfaces.platform.IPlatformBinding[] = [];
|
|
179
186
|
const envBindings = await this.getConfiguredEnvVar('SERVEZONE_PLATFORM_BINDINGS');
|
|
@@ -217,7 +224,7 @@ export class SzPlatformClient {
|
|
|
217
224
|
return Array.from(bindingMap.values());
|
|
218
225
|
}
|
|
219
226
|
|
|
220
|
-
|
|
227
|
+
public async getConfiguredEnvVar(envNameArg: string) {
|
|
221
228
|
const processValue = this.getProcessEnvVar(envNameArg);
|
|
222
229
|
if (processValue) {
|
|
223
230
|
return processValue;
|
|
@@ -3,13 +3,101 @@ import * as plugins from '../plugins.js';
|
|
|
3
3
|
|
|
4
4
|
import { logger } from '../logger.js';
|
|
5
5
|
|
|
6
|
+
export type TServiceMailTlsMode = 'plain' | 'starttls' | 'implicitTls';
|
|
7
|
+
|
|
8
|
+
export interface IServiceMailSmtpCredentials {
|
|
9
|
+
from: string;
|
|
10
|
+
host: string;
|
|
11
|
+
port: number;
|
|
12
|
+
tlsMode: TServiceMailTlsMode;
|
|
13
|
+
username: string;
|
|
14
|
+
password: string;
|
|
15
|
+
addressToken?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface IServiceMailTypedCredentials {
|
|
19
|
+
from: string;
|
|
20
|
+
typedUrl: string;
|
|
21
|
+
credentialId: string;
|
|
22
|
+
credentialSecret: string;
|
|
23
|
+
addressToken?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface IServiceMailStatus {
|
|
27
|
+
ready: boolean;
|
|
28
|
+
from?: string;
|
|
29
|
+
host?: string;
|
|
30
|
+
port?: number;
|
|
31
|
+
tlsMode?: TServiceMailTlsMode;
|
|
32
|
+
typedUrl?: string;
|
|
33
|
+
transport?: 'typedsocket' | 'smtp' | 'none';
|
|
34
|
+
usernameConfigured: boolean;
|
|
35
|
+
passwordConfigured: boolean;
|
|
36
|
+
typedCredentialConfigured?: boolean;
|
|
37
|
+
smtpReady?: boolean;
|
|
38
|
+
missing: string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface IServiceMailAttachment {
|
|
42
|
+
filename: string;
|
|
43
|
+
contentType?: string;
|
|
44
|
+
content: string | Uint8Array;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface IServiceMailSendOptions {
|
|
48
|
+
from?: string;
|
|
49
|
+
to: string | string[];
|
|
50
|
+
cc?: string | string[];
|
|
51
|
+
bcc?: string | string[];
|
|
52
|
+
subject: string;
|
|
53
|
+
text?: string;
|
|
54
|
+
html?: string;
|
|
55
|
+
headers?: Record<string, string>;
|
|
56
|
+
attachments?: IServiceMailAttachment[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface IServiceMailSendResult {
|
|
60
|
+
messageId?: string;
|
|
61
|
+
accepted?: string[];
|
|
62
|
+
rejected?: string[];
|
|
63
|
+
response?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface IServiceMailInboundMessage extends plugins.servezoneInterfaces.data.IMailInboundMessagePayload {
|
|
67
|
+
from: string;
|
|
68
|
+
to: string[];
|
|
69
|
+
receivedAtIso: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface IServiceMailInboundHandlerResult {
|
|
73
|
+
accepted?: boolean;
|
|
74
|
+
workAppMessageId?: string;
|
|
75
|
+
message?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type IServiceMailInboundHandleResult =
|
|
79
|
+
plugins.servezoneInterfaces.requests.mail.IReq_DeliverInboundMail['response'];
|
|
80
|
+
|
|
81
|
+
export type TServiceMailInboundHandler = (
|
|
82
|
+
messageArg: IServiceMailInboundMessage,
|
|
83
|
+
) => Promise<IServiceMailInboundHandlerResult | void> | IServiceMailInboundHandlerResult | void;
|
|
84
|
+
|
|
6
85
|
export class SzEmailConnector {
|
|
7
86
|
public platformClientRef: SzPlatformClient;
|
|
87
|
+
private mailTypedSocket?: plugins.typedsocket.TypedSocket;
|
|
88
|
+
private mailTypedSocketUrl?: string;
|
|
89
|
+
private mailTypedSocketPromise?: Promise<plugins.typedsocket.TypedSocket>;
|
|
90
|
+
private mailTypedSocketAbortController?: AbortController;
|
|
91
|
+
private inboundHandler?: TServiceMailInboundHandler;
|
|
92
|
+
private inboundTypedHandlerRegistered = false;
|
|
8
93
|
|
|
9
94
|
constructor(platformClientRefArg: SzPlatformClient) {
|
|
10
95
|
this.platformClientRef = platformClientRefArg;
|
|
11
96
|
}
|
|
12
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Legacy platform-service email path. New service-mail code should use sendMail().
|
|
100
|
+
*/
|
|
13
101
|
public async sendEmail(
|
|
14
102
|
optionsArg: plugins.servezoneInterfaces.platform.email.IReq_SendEmail['request']
|
|
15
103
|
) {
|
|
@@ -33,7 +121,405 @@ export class SzEmailConnector {
|
|
|
33
121
|
return response.responseId;
|
|
34
122
|
}
|
|
35
123
|
|
|
124
|
+
public async getServiceMailStatus(addressArg?: string): Promise<IServiceMailStatus> {
|
|
125
|
+
const requestedAddress = addressArg?.trim().toLowerCase();
|
|
126
|
+
const token = requestedAddress ? this.getMailEnvToken(requestedAddress) : undefined;
|
|
127
|
+
const scopedEnv = token ? await this.readMailEnv(token) : undefined;
|
|
128
|
+
const defaultEnv = token ? await this.readMailEnv() : undefined;
|
|
129
|
+
const useDefaultEnv = requestedAddress
|
|
130
|
+
&& !this.hasAnyMailEnvValue(scopedEnv)
|
|
131
|
+
&& defaultEnv?.from?.trim().toLowerCase() === requestedAddress;
|
|
132
|
+
const env = useDefaultEnv ? defaultEnv! : scopedEnv || await this.readMailEnv();
|
|
133
|
+
const missingPrefix = useDefaultEnv || !token ? undefined : token;
|
|
134
|
+
const port = env.smtpPort ? Number(env.smtpPort) : undefined;
|
|
135
|
+
const tlsMode = this.normalizeTlsMode(env.smtpTlsMode || 'starttls');
|
|
136
|
+
const typedMissing: string[] = [];
|
|
137
|
+
const smtpMissing: string[] = [];
|
|
138
|
+
if (!env.typedUrl) typedMissing.push(missingPrefix ? `MAIL_${missingPrefix}_TYPED_URL` : 'MAIL_TYPED_URL');
|
|
139
|
+
if (!env.apiCredentialId) typedMissing.push(missingPrefix ? `MAIL_${missingPrefix}_API_CREDENTIAL_ID` : 'MAIL_API_CREDENTIAL_ID');
|
|
140
|
+
if (!env.apiCredentialSecret) typedMissing.push(missingPrefix ? `MAIL_${missingPrefix}_API_CREDENTIAL_SECRET` : 'MAIL_API_CREDENTIAL_SECRET');
|
|
141
|
+
if (!env.smtpHost) smtpMissing.push(missingPrefix ? `MAIL_${missingPrefix}_SMTP_HOST` : 'SMTP_HOST');
|
|
142
|
+
if (!env.smtpPort || !Number.isInteger(port) || port! < 1 || port! > 65535) {
|
|
143
|
+
smtpMissing.push(missingPrefix ? `MAIL_${missingPrefix}_SMTP_PORT` : 'SMTP_PORT');
|
|
144
|
+
}
|
|
145
|
+
if (!env.smtpUsername) smtpMissing.push(missingPrefix ? `MAIL_${missingPrefix}_SMTP_USERNAME` : 'SMTP_USERNAME');
|
|
146
|
+
if (!env.smtpPassword) smtpMissing.push(missingPrefix ? `MAIL_${missingPrefix}_SMTP_PASSWORD` : 'SMTP_PASSWORD');
|
|
147
|
+
const fromMissing = env.from ? [] : [missingPrefix ? `MAIL_${missingPrefix}_FROM` : 'MAIL_FROM'];
|
|
148
|
+
const typedReady = fromMissing.length === 0 && typedMissing.length === 0;
|
|
149
|
+
const smtpReady = fromMissing.length === 0 && smtpMissing.length === 0;
|
|
150
|
+
return {
|
|
151
|
+
ready: typedReady,
|
|
152
|
+
from: env.from,
|
|
153
|
+
host: env.smtpHost,
|
|
154
|
+
port,
|
|
155
|
+
tlsMode,
|
|
156
|
+
typedUrl: env.typedUrl,
|
|
157
|
+
transport: typedReady ? 'typedsocket' : smtpReady ? 'smtp' : 'none',
|
|
158
|
+
usernameConfigured: Boolean(env.smtpUsername),
|
|
159
|
+
passwordConfigured: Boolean(env.smtpPassword),
|
|
160
|
+
typedCredentialConfigured: Boolean(env.apiCredentialId && env.apiCredentialSecret),
|
|
161
|
+
smtpReady,
|
|
162
|
+
missing: typedReady ? [] : [...fromMissing, ...typedMissing],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
public async getServiceMailCredentials(addressArg?: string): Promise<IServiceMailSmtpCredentials> {
|
|
167
|
+
const requestedAddress = addressArg?.trim().toLowerCase();
|
|
168
|
+
const requestedToken = requestedAddress ? this.getMailEnvToken(requestedAddress) : undefined;
|
|
169
|
+
const scopedEnv = requestedToken ? await this.readMailEnv(requestedToken) : undefined;
|
|
170
|
+
const hasScopedConfig = scopedEnv && Object.values(scopedEnv).some(Boolean);
|
|
171
|
+
const env = hasScopedConfig ? scopedEnv! : await this.readMailEnv();
|
|
172
|
+
const from = env.from?.trim().toLowerCase();
|
|
173
|
+
if (!from) throw new Error('MAIL_FROM is required for service mail');
|
|
174
|
+
if (requestedAddress && from !== requestedAddress) {
|
|
175
|
+
throw new Error(`No service mail credentials are configured for ${requestedAddress}`);
|
|
176
|
+
}
|
|
177
|
+
const port = Number(env.smtpPort);
|
|
178
|
+
if (!env.smtpHost) throw new Error('SMTP_HOST is required for service mail');
|
|
179
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
180
|
+
throw new Error('SMTP_PORT must be an integer between 1 and 65535');
|
|
181
|
+
}
|
|
182
|
+
if (!env.smtpUsername) throw new Error('SMTP_USERNAME is required for service mail');
|
|
183
|
+
if (!env.smtpPassword) throw new Error('SMTP_PASSWORD is required for service mail');
|
|
184
|
+
return {
|
|
185
|
+
from,
|
|
186
|
+
host: env.smtpHost,
|
|
187
|
+
port,
|
|
188
|
+
tlsMode: this.normalizeTlsMode(env.smtpTlsMode || 'starttls'),
|
|
189
|
+
username: env.smtpUsername,
|
|
190
|
+
password: env.smtpPassword,
|
|
191
|
+
addressToken: hasScopedConfig ? requestedToken : undefined,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public async sendText(optionsArg: Omit<IServiceMailSendOptions, 'html'> & { text: string }) {
|
|
196
|
+
return await this.sendMail(optionsArg);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public async sendHtml(optionsArg: Omit<IServiceMailSendOptions, 'text'> & { html: string }) {
|
|
200
|
+
return await this.sendMail(optionsArg);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
public async sendMail(optionsArg: IServiceMailSendOptions): Promise<IServiceMailSendResult> {
|
|
204
|
+
if (!optionsArg.text && !optionsArg.html) {
|
|
205
|
+
throw new Error('Either text or html is required for service mail');
|
|
206
|
+
}
|
|
207
|
+
if (this.platformClientRef.debugMode) {
|
|
208
|
+
logger.log('info', `service mail debug send: ${optionsArg.subject} to ${this.listToArray(optionsArg.to).join(', ')}`);
|
|
209
|
+
return { messageId: 'debug' };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const credentials = await this.getServiceMailTypedCredentials(optionsArg.from);
|
|
213
|
+
const typedSocket = await this.getMailTypedSocket(credentials.typedUrl);
|
|
214
|
+
const request = typedSocket.createTypedRequest<plugins.servezoneInterfaces.requests.mail.IReq_EnqueueMail>('enqueueMail');
|
|
215
|
+
const result = await request.fire({
|
|
216
|
+
auth: {
|
|
217
|
+
credentialId: credentials.credentialId,
|
|
218
|
+
credentialSecret: credentials.credentialSecret,
|
|
219
|
+
},
|
|
220
|
+
outboundIdentityId: credentials.credentialId,
|
|
221
|
+
message: {
|
|
222
|
+
from: credentials.from,
|
|
223
|
+
to: this.listToArray(optionsArg.to),
|
|
224
|
+
cc: optionsArg.cc ? this.listToArray(optionsArg.cc) : undefined,
|
|
225
|
+
bcc: optionsArg.bcc ? this.listToArray(optionsArg.bcc) : undefined,
|
|
226
|
+
subject: optionsArg.subject,
|
|
227
|
+
text: optionsArg.text,
|
|
228
|
+
html: optionsArg.html,
|
|
229
|
+
headers: optionsArg.headers,
|
|
230
|
+
attachments: optionsArg.attachments?.map((attachmentArg) => ({
|
|
231
|
+
filename: attachmentArg.filename,
|
|
232
|
+
contentType: attachmentArg.contentType,
|
|
233
|
+
binaryAttachmentString: this.normalizeAttachmentContent(attachmentArg.content),
|
|
234
|
+
})),
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
if (!result.accepted) {
|
|
238
|
+
throw new Error(result.message || 'service mail was not accepted by dcrouter');
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
messageId: result.spoolItemId,
|
|
242
|
+
accepted: this.listToArray(optionsArg.to),
|
|
243
|
+
rejected: [],
|
|
244
|
+
response: result.message,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
public async registerInboundHandler(
|
|
249
|
+
handlerArg: TServiceMailInboundHandler,
|
|
250
|
+
addressArg?: string,
|
|
251
|
+
): Promise<plugins.servezoneInterfaces.requests.mail.IReq_RegisterServiceMailEndpoint['response']> {
|
|
252
|
+
this.inboundHandler = handlerArg;
|
|
253
|
+
this.ensureInboundTypedHandler();
|
|
254
|
+
const credentials = await this.getServiceMailTypedCredentials(addressArg);
|
|
255
|
+
const typedSocket = await this.getMailTypedSocket(credentials.typedUrl);
|
|
256
|
+
const request = typedSocket.createTypedRequest<plugins.servezoneInterfaces.requests.mail.IReq_RegisterServiceMailEndpoint>('registerServiceMailEndpoint');
|
|
257
|
+
const result = await request.fire({
|
|
258
|
+
auth: {
|
|
259
|
+
credentialId: credentials.credentialId,
|
|
260
|
+
credentialSecret: credentials.credentialSecret,
|
|
261
|
+
},
|
|
262
|
+
addresses: [credentials.from],
|
|
263
|
+
});
|
|
264
|
+
if (!result.success) {
|
|
265
|
+
throw new Error(result.message || 'Could not register service mail endpoint');
|
|
266
|
+
}
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
public async stop(): Promise<void> {
|
|
271
|
+
await this.stopMailTypedSocket();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
public normalizeInboundMessage(payloadArg: unknown): IServiceMailInboundMessage {
|
|
275
|
+
const payload = this.asRecord(payloadArg);
|
|
276
|
+
const deliveryCandidate = this.asRecord(payload.delivery || payload.message || payload);
|
|
277
|
+
const envelope = this.asRecord(deliveryCandidate.envelope);
|
|
278
|
+
const rawMessage = typeof deliveryCandidate.rawMessage === 'string' ? deliveryCandidate.rawMessage : '';
|
|
279
|
+
const rcptTo = Array.isArray(envelope.rcptTo) ? envelope.rcptTo.map(String) : [];
|
|
280
|
+
const receivedAt = typeof deliveryCandidate.receivedAt === 'number' ? deliveryCandidate.receivedAt : Date.now();
|
|
281
|
+
return {
|
|
282
|
+
spoolItemId: String(deliveryCandidate.spoolItemId || deliveryCandidate.id || ''),
|
|
283
|
+
owner: deliveryCandidate.owner as plugins.servezoneInterfaces.data.IMailResourceOwner | undefined,
|
|
284
|
+
envelope: {
|
|
285
|
+
mailFrom: String(envelope.mailFrom || deliveryCandidate.from || ''),
|
|
286
|
+
rcptTo,
|
|
287
|
+
},
|
|
288
|
+
rawMessage,
|
|
289
|
+
sizeBytes: typeof deliveryCandidate.sizeBytes === 'number' ? deliveryCandidate.sizeBytes : rawMessage.length,
|
|
290
|
+
messageId: deliveryCandidate.messageId ? String(deliveryCandidate.messageId) : undefined,
|
|
291
|
+
subject: deliveryCandidate.subject ? String(deliveryCandidate.subject) : this.extractHeader(rawMessage, 'subject'),
|
|
292
|
+
headers: this.normalizeHeaders(deliveryCandidate.headers),
|
|
293
|
+
source: deliveryCandidate.source as plugins.servezoneInterfaces.data.IMailConnectionInfo | undefined,
|
|
294
|
+
receivedAt,
|
|
295
|
+
from: String(envelope.mailFrom || deliveryCandidate.from || ''),
|
|
296
|
+
to: rcptTo,
|
|
297
|
+
receivedAtIso: new Date(receivedAt).toISOString(),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
public async handleInboundPayload(
|
|
302
|
+
payloadArg: unknown,
|
|
303
|
+
handlerArg: TServiceMailInboundHandler,
|
|
304
|
+
): Promise<IServiceMailInboundHandleResult> {
|
|
305
|
+
const message = this.normalizeInboundMessage(payloadArg);
|
|
306
|
+
const result = await handlerArg(message);
|
|
307
|
+
return {
|
|
308
|
+
accepted: result?.accepted !== false,
|
|
309
|
+
workAppMessageId: result?.workAppMessageId,
|
|
310
|
+
message: result?.message,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
public createInboundHandler(handlerArg: TServiceMailInboundHandler) {
|
|
315
|
+
return async (payloadArg: unknown) => await this.handleInboundPayload(payloadArg, handlerArg);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private ensureInboundTypedHandler(): void {
|
|
319
|
+
if (this.inboundTypedHandlerRegistered) return;
|
|
320
|
+
this.inboundTypedHandlerRegistered = true;
|
|
321
|
+
this.platformClientRef.typedrouter.addTypedHandler(
|
|
322
|
+
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.mail.IReq_DeliverInboundMail>(
|
|
323
|
+
'deliverInboundMail',
|
|
324
|
+
async (dataArg) => {
|
|
325
|
+
if (!this.inboundHandler) {
|
|
326
|
+
return { accepted: false, message: 'No service mail inbound handler registered' };
|
|
327
|
+
}
|
|
328
|
+
return await this.handleInboundPayload(dataArg.delivery, this.inboundHandler);
|
|
329
|
+
},
|
|
330
|
+
),
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
36
334
|
public async sendNotification(optionsArg: { toArg: string; subject: string; text: string }) {}
|
|
37
335
|
|
|
38
336
|
public async sendActionLink(optionsArg: { toArg: string; subject: string; text: string }) {}
|
|
337
|
+
|
|
338
|
+
private async readMailEnv(tokenArg?: string): Promise<{
|
|
339
|
+
from?: string;
|
|
340
|
+
smtpHost?: string;
|
|
341
|
+
smtpPort?: string;
|
|
342
|
+
smtpTlsMode?: string;
|
|
343
|
+
smtpUsername?: string;
|
|
344
|
+
smtpPassword?: string;
|
|
345
|
+
typedUrl?: string;
|
|
346
|
+
apiCredentialId?: string;
|
|
347
|
+
apiCredentialSecret?: string;
|
|
348
|
+
}> {
|
|
349
|
+
const prefix = tokenArg ? `MAIL_${tokenArg}_` : '';
|
|
350
|
+
return {
|
|
351
|
+
from: await this.readMailEnvVar(tokenArg ? `${prefix}FROM` : 'MAIL_FROM'),
|
|
352
|
+
smtpHost: await this.readMailEnvVar(`${prefix}SMTP_HOST`),
|
|
353
|
+
smtpPort: await this.readMailEnvVar(`${prefix}SMTP_PORT`),
|
|
354
|
+
smtpTlsMode: await this.readMailEnvVar(`${prefix}SMTP_TLS_MODE`),
|
|
355
|
+
smtpUsername: await this.readMailEnvVar(`${prefix}SMTP_USERNAME`),
|
|
356
|
+
smtpPassword: await this.readMailEnvVar(`${prefix}SMTP_PASSWORD`),
|
|
357
|
+
typedUrl: await this.readMailEnvVar(tokenArg ? `${prefix}TYPED_URL` : 'MAIL_TYPED_URL'),
|
|
358
|
+
apiCredentialId: await this.readMailEnvVar(tokenArg ? `${prefix}API_CREDENTIAL_ID` : 'MAIL_API_CREDENTIAL_ID'),
|
|
359
|
+
apiCredentialSecret: await this.readMailEnvVar(tokenArg ? `${prefix}API_CREDENTIAL_SECRET` : 'MAIL_API_CREDENTIAL_SECRET'),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private async readMailEnvVar(envNameArg: string): Promise<string | undefined> {
|
|
364
|
+
return await this.platformClientRef.getConfiguredEnvVar(envNameArg)
|
|
365
|
+
|| this.getCredentialEnvVarFromBindings(envNameArg);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private getCredentialEnvVarFromBindings(envNameArg: string): string | undefined {
|
|
369
|
+
for (const binding of this.platformClientRef.platformBindings || []) {
|
|
370
|
+
if (binding.desiredState === 'disabled' || binding.status === 'failed') continue;
|
|
371
|
+
if (binding.capability !== 'email') continue;
|
|
372
|
+
for (const credential of binding.credentials || []) {
|
|
373
|
+
const value = credential.env?.[envNameArg];
|
|
374
|
+
if (value) return value;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private hasAnyMailEnvValue(envArg?: {
|
|
380
|
+
from?: string;
|
|
381
|
+
smtpHost?: string;
|
|
382
|
+
smtpPort?: string;
|
|
383
|
+
smtpTlsMode?: string;
|
|
384
|
+
smtpUsername?: string;
|
|
385
|
+
smtpPassword?: string;
|
|
386
|
+
typedUrl?: string;
|
|
387
|
+
apiCredentialId?: string;
|
|
388
|
+
apiCredentialSecret?: string;
|
|
389
|
+
}): boolean {
|
|
390
|
+
return Boolean(envArg && Object.values(envArg).some(Boolean));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private async getServiceMailTypedCredentials(addressArg?: string): Promise<IServiceMailTypedCredentials> {
|
|
394
|
+
const requestedAddress = addressArg?.trim().toLowerCase();
|
|
395
|
+
const requestedToken = requestedAddress ? this.getMailEnvToken(requestedAddress) : undefined;
|
|
396
|
+
const scopedEnv = requestedToken ? await this.readMailEnv(requestedToken) : undefined;
|
|
397
|
+
const hasScopedConfig = scopedEnv && Object.values(scopedEnv).some(Boolean);
|
|
398
|
+
const env = hasScopedConfig ? scopedEnv! : await this.readMailEnv();
|
|
399
|
+
const from = env.from?.trim().toLowerCase();
|
|
400
|
+
if (!from) throw new Error('MAIL_FROM is required for typed service mail');
|
|
401
|
+
if (requestedAddress && from !== requestedAddress) {
|
|
402
|
+
throw new Error(`No typed service mail credentials are configured for ${requestedAddress}`);
|
|
403
|
+
}
|
|
404
|
+
if (!env.typedUrl) throw new Error('MAIL_TYPED_URL is required for typed service mail');
|
|
405
|
+
if (!env.apiCredentialId) throw new Error('MAIL_API_CREDENTIAL_ID is required for typed service mail');
|
|
406
|
+
if (!env.apiCredentialSecret) throw new Error('MAIL_API_CREDENTIAL_SECRET is required for typed service mail');
|
|
407
|
+
return {
|
|
408
|
+
from,
|
|
409
|
+
typedUrl: this.normalizeTypedSocketUrl(env.typedUrl),
|
|
410
|
+
credentialId: env.apiCredentialId,
|
|
411
|
+
credentialSecret: env.apiCredentialSecret,
|
|
412
|
+
addressToken: hasScopedConfig ? requestedToken : undefined,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private async getMailTypedSocket(urlArg: string): Promise<plugins.typedsocket.TypedSocket> {
|
|
417
|
+
const normalizedUrl = this.normalizeTypedSocketUrl(urlArg);
|
|
418
|
+
if (this.mailTypedSocket && this.mailTypedSocketUrl === normalizedUrl) {
|
|
419
|
+
return this.mailTypedSocket;
|
|
420
|
+
}
|
|
421
|
+
if (this.mailTypedSocketPromise && this.mailTypedSocketUrl === normalizedUrl) {
|
|
422
|
+
return await this.mailTypedSocketPromise;
|
|
423
|
+
}
|
|
424
|
+
if (this.mailTypedSocket || this.mailTypedSocketPromise) {
|
|
425
|
+
await this.stopMailTypedSocket();
|
|
426
|
+
}
|
|
427
|
+
this.mailTypedSocketUrl = normalizedUrl;
|
|
428
|
+
const abortController = new AbortController();
|
|
429
|
+
this.mailTypedSocketAbortController = abortController;
|
|
430
|
+
const socketPromise = plugins.typedsocket.TypedSocket.createClient(
|
|
431
|
+
this.platformClientRef.typedrouter,
|
|
432
|
+
normalizedUrl,
|
|
433
|
+
{ autoReconnect: true, abortSignal: abortController.signal },
|
|
434
|
+
);
|
|
435
|
+
this.mailTypedSocketPromise = socketPromise;
|
|
436
|
+
try {
|
|
437
|
+
const socket = await socketPromise;
|
|
438
|
+
if (this.mailTypedSocketPromise === socketPromise && this.mailTypedSocketUrl === normalizedUrl) {
|
|
439
|
+
this.mailTypedSocket = socket;
|
|
440
|
+
} else {
|
|
441
|
+
await socket.stop();
|
|
442
|
+
}
|
|
443
|
+
return socket;
|
|
444
|
+
} catch (error) {
|
|
445
|
+
if (this.mailTypedSocketPromise === socketPromise) {
|
|
446
|
+
this.mailTypedSocketPromise = undefined;
|
|
447
|
+
this.mailTypedSocketUrl = undefined;
|
|
448
|
+
this.mailTypedSocketAbortController = undefined;
|
|
449
|
+
}
|
|
450
|
+
throw error;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
private async stopMailTypedSocket(): Promise<void> {
|
|
455
|
+
const socket = this.mailTypedSocket;
|
|
456
|
+
const socketPromise = this.mailTypedSocketPromise;
|
|
457
|
+
const abortController = this.mailTypedSocketAbortController;
|
|
458
|
+
this.mailTypedSocket = undefined;
|
|
459
|
+
this.mailTypedSocketPromise = undefined;
|
|
460
|
+
this.mailTypedSocketUrl = undefined;
|
|
461
|
+
this.mailTypedSocketAbortController = undefined;
|
|
462
|
+
abortController?.abort();
|
|
463
|
+
if (socket) {
|
|
464
|
+
await socket.stop();
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (socketPromise) {
|
|
468
|
+
try {
|
|
469
|
+
const pendingSocket = await socketPromise;
|
|
470
|
+
await pendingSocket.stop();
|
|
471
|
+
} catch {
|
|
472
|
+
// The pending connection already failed; there is no socket to stop.
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
private normalizeTypedSocketUrl(urlArg: string): string {
|
|
478
|
+
return urlArg.trim().replace(/\/typedrequest\/?$/, '').replace(/\/+$/, '');
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
private normalizeTlsMode(valueArg: string): TServiceMailTlsMode {
|
|
482
|
+
if (valueArg === 'plain' || valueArg === 'starttls' || valueArg === 'implicitTls') return valueArg;
|
|
483
|
+
throw new Error(`Invalid SMTP_TLS_MODE: ${valueArg}`);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
private getMailEnvToken(addressArg: string): string {
|
|
487
|
+
const token = addressArg.trim().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').toUpperCase();
|
|
488
|
+
return /^\d/.test(token) ? `ADDR_${token}` : token;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
private listToArray(valueArg: string | string[]): string[] {
|
|
492
|
+
return Array.isArray(valueArg) ? valueArg : [valueArg];
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
private normalizeAttachmentContent(contentArg: string | Uint8Array): string {
|
|
496
|
+
return Buffer.from(contentArg).toString('base64');
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
private asRecord(valueArg: unknown): Record<string, unknown> {
|
|
500
|
+
return valueArg && typeof valueArg === 'object' ? valueArg as Record<string, unknown> : {};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
private normalizeHeaders(headersArg: unknown): Record<string, string | string[]> | undefined {
|
|
504
|
+
if (!headersArg || typeof headersArg !== 'object') return undefined;
|
|
505
|
+
const result: Record<string, string | string[]> = {};
|
|
506
|
+
for (const [key, value] of Object.entries(headersArg as Record<string, unknown>)) {
|
|
507
|
+
if (Array.isArray(value)) {
|
|
508
|
+
result[key] = value.map(String);
|
|
509
|
+
} else if (value !== undefined && value !== null) {
|
|
510
|
+
result[key] = String(value);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return result;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
private extractHeader(rawMessageArg: string, headerNameArg: string): string | undefined {
|
|
517
|
+
const lowerName = `${headerNameArg.toLowerCase()}:`;
|
|
518
|
+
for (const line of rawMessageArg.split(/\r?\n/)) {
|
|
519
|
+
if (!line) return undefined;
|
|
520
|
+
if (line.toLowerCase().startsWith(lowerName)) {
|
|
521
|
+
return line.slice(lowerName.length).trim();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
39
525
|
}
|
package/ts/index.ts
CHANGED