feat: support Cloud Mail email provider

- Merge the latest dev branch into PR #212 for a clean dev-targeted review base.

- Add Cloud Mail/SkyMail provider support for email generation and verification-code polling.

- Move Cloud Mail runtime logic into a dedicated background module and add docs plus regression tests.
This commit is contained in:
QLHazyCoder
2026-05-08 17:40:41 +08:00
24 changed files with 977 additions and 588 deletions
+156
View File
@@ -0,0 +1,156 @@
const test = require('node:test');
const assert = require('node:assert/strict');
require('../background/cloudmail-provider.js');
function createProviderApi(options = {}) {
const {
receiveMailbox = '',
messages = [{
id: 'mail-1',
address: 'user@example.com',
receivedDateTime: '2026-05-07T09:20:00.000Z',
subject: 'OpenAI verification code',
from: { emailAddress: { address: 'noreply@tm.openai.com' } },
bodyPreview: 'Your verification code is 123456.',
}],
} = options;
const logs = [];
const listCalls = [];
const fetchImpl = async (url, request = {}) => {
const payload = request.body ? JSON.parse(request.body) : {};
if (String(url).includes('/api/public/emailList')) {
listCalls.push(payload.toEmail || '');
return {
ok: true,
text: async () => JSON.stringify({ code: 200, data: { records: messages } }),
};
}
return {
ok: true,
text: async () => JSON.stringify({ code: 200, data: { token: 'token' } }),
};
};
const api = globalThis.MultiPageBackgroundCloudMailProvider.createCloudMailProvider({
addLog: async (message, level) => logs.push({ message, level }),
buildCloudMailHeaders: () => ({}),
CLOUD_MAIL_DEFAULT_PAGE_SIZE: 20,
CLOUD_MAIL_GENERATOR: 'cloudmail',
CLOUD_MAIL_PROVIDER: 'cloudmail',
fetchImpl,
getCloudMailTokenFromResponse: () => 'token',
getState: async () => ({}),
joinCloudMailUrl: (baseUrl, path) => `${baseUrl}${path}`,
normalizeCloudMailAddress: (value) => String(value || '').trim().toLowerCase(),
normalizeCloudMailBaseUrl: (value) => String(value || '').trim(),
normalizeCloudMailDomain: (value) => String(value || '').trim(),
normalizeCloudMailDomains: (values) => values || [],
normalizeCloudMailMailApiMessages: (payload) => payload?.data?.records || [],
pickVerificationMessageWithTimeFallback: (currentMessages) => ({
match: currentMessages[0]
? {
code: String(currentMessages[0].bodyPreview).match(/(\d{6})/)[1],
receivedAt: Date.parse(currentMessages[0].receivedDateTime),
message: currentMessages[0],
}
: null,
usedRelaxedFilters: false,
usedTimeFallback: false,
}),
setEmailState: async () => {},
setPersistentSettings: async () => {},
sleepWithStop: async () => {},
throwIfStopped: () => {},
});
return {
...api,
snapshot() {
return { logs, listCalls };
},
};
}
test('pollCloudMailVerificationCode returns code for generated Cloud Mail address', async () => {
const api = createProviderApi();
const result = await api.pollCloudMailVerificationCode(4, {
email: 'user@example.com',
cloudMailBaseUrl: 'https://mail.example.com',
cloudMailAdminEmail: 'admin@example.com',
cloudMailAdminPassword: 'secret',
cloudMailToken: 'token',
}, {
targetEmail: 'user@example.com',
maxAttempts: 1,
intervalMs: 1,
});
assert.equal(result.code, '123456');
assert.deepEqual(api.snapshot().listCalls, ['user@example.com']);
});
test('pollCloudMailVerificationCode prefers configured receive mailbox over registration email', async () => {
const api = createProviderApi({
receiveMailbox: 'forward-box@example.com',
messages: [{
id: 'mail-2',
address: 'forward-box@example.com',
receivedDateTime: '2026-05-07T10:20:00.000Z',
subject: 'Login verification code',
from: { emailAddress: { address: 'noreply@tm.openai.com' } },
bodyPreview: 'Your verification code is 654321.',
}],
});
const result = await api.pollCloudMailVerificationCode(8, {
email: 'duck-forwarded@duck.com',
mailProvider: 'cloudmail',
emailGenerator: 'duck',
cloudMailBaseUrl: 'https://mail.example.com',
cloudMailReceiveMailbox: 'forward-box@example.com',
cloudMailAdminEmail: 'admin@example.com',
cloudMailAdminPassword: 'secret',
cloudMailToken: 'token',
}, {
targetEmail: 'duck-forwarded@duck.com',
maxAttempts: 1,
intervalMs: 1,
});
assert.equal(result.code, '654321');
assert.deepEqual(api.snapshot().listCalls, ['forward-box@example.com']);
});
test('pollCloudMailVerificationCode ignores stale receive mailbox when generator owns the address', async () => {
const api = createProviderApi({
receiveMailbox: 'forward-box@example.com',
messages: [{
id: 'mail-3',
address: 'generated@example.com',
receivedDateTime: '2026-05-07T11:20:00.000Z',
subject: 'Signup verification code',
from: { emailAddress: { address: 'noreply@tm.openai.com' } },
bodyPreview: 'Your verification code is 246810.',
}],
});
const result = await api.pollCloudMailVerificationCode(4, {
email: 'generated@example.com',
mailProvider: 'cloudmail',
emailGenerator: 'cloudmail',
cloudMailBaseUrl: 'https://mail.example.com',
cloudMailReceiveMailbox: 'forward-box@example.com',
cloudMailAdminEmail: 'admin@example.com',
cloudMailAdminPassword: 'secret',
cloudMailToken: 'token',
}, {
targetEmail: 'generated@example.com',
maxAttempts: 1,
intervalMs: 1,
});
assert.equal(result.code, '246810');
assert.deepEqual(api.snapshot().listCalls, ['generated@example.com']);
});