@remit/smtp-worker 0.0.1 → 0.0.2
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/package.json +48 -38
- package/src/data-ports.ts +19 -17
- package/src/handlers/send-message.test.ts +4 -1
- package/src/handlers/send-message.ts +43 -24
package/package.json
CHANGED
|
@@ -1,40 +1,50 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
2
|
+
"name": "@remit/smtp-worker",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
|
+
"default": "./src/index.ts"
|
|
10
|
+
},
|
|
11
|
+
"./data-ports": {
|
|
12
|
+
"types": "./src/data-ports.ts",
|
|
13
|
+
"default": "./src/data-ports.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"bundle": "node build.mjs",
|
|
18
|
+
"test:typecheck": "tsgo --noEmit",
|
|
19
|
+
"test:run": "node --env-file=../../localhost-test-unit.env --test 'src/**/*.test.ts'",
|
|
20
|
+
"test": "npm run test:typecheck && npm run test:run"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@aws-sdk/client-sqs": "*",
|
|
24
|
+
"@aws-sdk/core": "*",
|
|
25
|
+
"@remit/data-ports": "*",
|
|
26
|
+
"@remit/drizzle-service": "*",
|
|
27
|
+
"@remit/domain-enums": "*",
|
|
28
|
+
"@remit/logger-lambda": "*",
|
|
29
|
+
"@remit/mail-oauth-service": "*",
|
|
30
|
+
"@remit/mailbox-service": "*",
|
|
31
|
+
"@remit/secrets-service": "*",
|
|
32
|
+
"@remit/smtp-service": "*",
|
|
33
|
+
"@remit/sqs-client": "*",
|
|
34
|
+
"@remit/storage-service": "*",
|
|
35
|
+
"@types/aws-lambda": "*",
|
|
36
|
+
"@types/pg": "^8.0.0",
|
|
37
|
+
"esbuild": "^0.27.7",
|
|
38
|
+
"expect-env": "*",
|
|
39
|
+
"p-map": "*"
|
|
40
|
+
},
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/remit-mail/remit.git",
|
|
48
|
+
"directory": "packages/smtp-worker"
|
|
49
|
+
}
|
|
40
50
|
}
|
package/src/data-ports.ts
CHANGED
|
@@ -96,31 +96,33 @@ const buildSqliteDataPorts = async (): Promise<SmtpDataPorts> => {
|
|
|
96
96
|
};
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
+
let injectedDataPorts: SmtpDataPorts | null = null;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Register the DynamoDB-backed SMTP data ports from the composition root. The
|
|
103
|
+
* DynamoDB composition lives outside this shared, open-core module and is never
|
|
104
|
+
* imported here. The relational backends compose in-package below and never
|
|
105
|
+
* touch this seam.
|
|
106
|
+
*/
|
|
107
|
+
export const setSmtpDataPorts = (ports: SmtpDataPorts): void => {
|
|
108
|
+
injectedDataPorts = ports;
|
|
109
|
+
};
|
|
110
|
+
|
|
99
111
|
/**
|
|
100
112
|
* Select the SMTP data ports from the environment, mirroring the other workers'
|
|
101
113
|
* `DATA_BACKEND` selection:
|
|
102
114
|
*
|
|
103
115
|
* - `DATA_BACKEND=postgres` → Drizzle repos over `PG_CONNECTION_URL`.
|
|
104
116
|
* - `DATA_BACKEND=sqlite` → Drizzle repos over the shared `SQLITE_DB_PATH` file.
|
|
105
|
-
* - otherwise →
|
|
106
|
-
* unchanged from the pre-convergence worker).
|
|
107
|
-
*
|
|
108
|
-
* The DynamoDB branch loads its composition through a dynamic `import()`, so the
|
|
109
|
-
* sole importer of the closed `@remit/remit-electrodb-service`
|
|
110
|
-
* (`./compose-dynamodb.js`) is never loaded on the relational (open-core) tree,
|
|
111
|
-
* where that module is not shipped. esbuild still bundles it into the Lambda
|
|
112
|
-
* because the specifier is a relative import, not an `external`.
|
|
117
|
+
* - otherwise → the DynamoDB ElectroDB ports the composition root injected.
|
|
113
118
|
*/
|
|
114
119
|
export const buildDataPortsFromEnv = async (): Promise<SmtpDataPorts> => {
|
|
115
120
|
if (process.env.DATA_BACKEND === "postgres") return buildPostgresDataPorts();
|
|
116
121
|
if (process.env.DATA_BACKEND === "sqlite") return buildSqliteDataPorts();
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
"./compose-dynamodb.js" as string
|
|
124
|
-
)) as ComposeDynamoDBModule;
|
|
125
|
-
return buildDynamoDBDataPorts();
|
|
122
|
+
if (!injectedDataPorts) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
"no DynamoDB SMTP data ports registered — register them with setSmtpDataPorts() from your composition root",
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return injectedDataPorts;
|
|
126
128
|
};
|
|
@@ -11,7 +11,10 @@ import {
|
|
|
11
11
|
type EncryptedPayload,
|
|
12
12
|
serializeEncryptedPayload,
|
|
13
13
|
} from "@remit/secrets-service";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
type SendResult,
|
|
16
|
+
SmtpConnectionError,
|
|
17
|
+
} from "@remit/smtp-service";
|
|
15
18
|
import type { SendMessageEvent } from "../events.js";
|
|
16
19
|
import { type SendMessageDeps, sendMessage } from "./send-message-core.js";
|
|
17
20
|
|
|
@@ -23,11 +23,19 @@ import {
|
|
|
23
23
|
import { sendMail } from "@remit/smtp-service";
|
|
24
24
|
import { resolveSqsCredentials } from "@remit/sqs-client";
|
|
25
25
|
import { env } from "expect-env";
|
|
26
|
-
import { buildDataPortsFromEnv } from "../data-ports.js";
|
|
26
|
+
import { buildDataPortsFromEnv, type SmtpDataPorts } from "../data-ports.js";
|
|
27
27
|
import type { SendMessageEvent } from "../events.js";
|
|
28
28
|
import { sendMessage } from "./send-message-core.js";
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
// The data ports are resolved lazily and cached, not at module load: the
|
|
31
|
+
// DynamoDB backend is injected by the composition root before the first send,
|
|
32
|
+
// so building at import time would race the registration.
|
|
33
|
+
let portsPromise: Promise<SmtpDataPorts> | null = null;
|
|
34
|
+
const getPorts = (): Promise<SmtpDataPorts> => {
|
|
35
|
+
if (!portsPromise) portsPromise = buildDataPortsFromEnv();
|
|
36
|
+
return portsPromise;
|
|
37
|
+
};
|
|
38
|
+
|
|
31
39
|
const dataKeyProvider = createKmsDataKeyProvider(env.KMS_KEY_ID);
|
|
32
40
|
const secrets = createSecretsService(dataKeyProvider);
|
|
33
41
|
|
|
@@ -48,12 +56,6 @@ const getTokenService = () => {
|
|
|
48
56
|
return _tokenService;
|
|
49
57
|
};
|
|
50
58
|
|
|
51
|
-
const accountService = ports.account;
|
|
52
|
-
const outboxService = ports.outboxMessage;
|
|
53
|
-
const addressService = ports.address;
|
|
54
|
-
const messageService = ports.message;
|
|
55
|
-
const envelopeService = ports.envelope;
|
|
56
|
-
|
|
57
59
|
// APPEND_SENT_MESSAGE is processed by the IMAP worker via the
|
|
58
60
|
// message-management queue (see remit-imap-worker/src/emit.ts).
|
|
59
61
|
const messageMgmtQueueUrl = env.SQS_QUEUE_URL_MESSAGE_MGMT;
|
|
@@ -90,7 +92,8 @@ const findMessageByHeader = async (
|
|
|
90
92
|
messageIdHeader: string,
|
|
91
93
|
) => {
|
|
92
94
|
const messageId = deriveMessageId(accountId, messageIdHeader);
|
|
93
|
-
|
|
95
|
+
const { message } = await getPorts();
|
|
96
|
+
return message.get(messageId).catch((error: unknown) => {
|
|
94
97
|
if ((error as { name?: string })?.name === "NotFoundError") return null;
|
|
95
98
|
throw error;
|
|
96
99
|
});
|
|
@@ -99,7 +102,8 @@ const findMessageByHeader = async (
|
|
|
99
102
|
const getEnvelopeFromEmail = async (
|
|
100
103
|
messageId: string,
|
|
101
104
|
): Promise<string | null> => {
|
|
102
|
-
const
|
|
105
|
+
const { envelope } = await getPorts();
|
|
106
|
+
const data = await envelope
|
|
103
107
|
.getMessageData(messageId)
|
|
104
108
|
.catch((error: unknown) => {
|
|
105
109
|
if ((error as { name?: string })?.name === "NotFoundError") return null;
|
|
@@ -122,7 +126,8 @@ const buildCredentialDeps = (): AccountCredentialsDeps => ({
|
|
|
122
126
|
secrets,
|
|
123
127
|
tokenService: getTokenService(),
|
|
124
128
|
persistRotatedToken: async (accountId, encryptedHash, updatedAt) => {
|
|
125
|
-
await
|
|
129
|
+
const { account } = await getPorts();
|
|
130
|
+
await account.update(accountId, {
|
|
126
131
|
oauthRefreshTokenHash: encryptedHash,
|
|
127
132
|
oauthTokenUpdatedAt: updatedAt,
|
|
128
133
|
});
|
|
@@ -135,19 +140,25 @@ export const handleSendMessage = (
|
|
|
135
140
|
): Promise<void> => {
|
|
136
141
|
const credentialDeps = buildCredentialDeps();
|
|
137
142
|
return sendMessage(event, log, {
|
|
138
|
-
getOutbox: (accountConfigId, id) =>
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
getOutbox: async (accountConfigId, id) =>
|
|
144
|
+
(await getPorts()).outboxMessage.get(accountConfigId, id),
|
|
145
|
+
getAccount: async (id) => (await getPorts()).account.get(id),
|
|
146
|
+
updateOutbox: async (accountConfigId, id, patch) =>
|
|
147
|
+
(await getPorts()).outboxMessage.update(accountConfigId, id, patch),
|
|
148
|
+
updateOutboxStatus: async (accountConfigId, id, status) =>
|
|
149
|
+
(await getPorts()).outboxMessage.updateStatus(
|
|
150
|
+
accountConfigId,
|
|
151
|
+
id,
|
|
152
|
+
status,
|
|
153
|
+
),
|
|
154
|
+
markOutboxSent: async (accountConfigId, id, fields) =>
|
|
155
|
+
(await getPorts()).outboxMessage.markSent(accountConfigId, id, fields),
|
|
146
156
|
secrets,
|
|
147
157
|
resolveCredentials: (account) =>
|
|
148
158
|
resolveConnectionCredentials(account, credentialDeps),
|
|
149
159
|
updateConnectionState: async (id, state) => {
|
|
150
|
-
await
|
|
160
|
+
const { account } = await getPorts();
|
|
161
|
+
await account.update(id, {
|
|
151
162
|
connectionState:
|
|
152
163
|
state as (typeof ConnectionState)[keyof typeof ConnectionState],
|
|
153
164
|
});
|
|
@@ -156,10 +167,18 @@ export const handleSendMessage = (
|
|
|
156
167
|
emitAppendSentMessage,
|
|
157
168
|
engagement: {
|
|
158
169
|
resolveAddressId: deriveAddressId,
|
|
159
|
-
incrementOutboundCount: (accountConfigId, addressId, now) =>
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
170
|
+
incrementOutboundCount: async (accountConfigId, addressId, now) =>
|
|
171
|
+
(await getPorts()).address.incrementOutboundCount(
|
|
172
|
+
accountConfigId,
|
|
173
|
+
addressId,
|
|
174
|
+
now,
|
|
175
|
+
),
|
|
176
|
+
incrementReplyCount: async (accountConfigId, addressId, now) =>
|
|
177
|
+
(await getPorts()).address.incrementReplyCount(
|
|
178
|
+
accountConfigId,
|
|
179
|
+
addressId,
|
|
180
|
+
now,
|
|
181
|
+
),
|
|
163
182
|
findMessageByHeader,
|
|
164
183
|
getEnvelopeFromEmail,
|
|
165
184
|
},
|