@aglyn/plugins-outreach 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +93 -0
- package/package.json +52 -0
- package/src/index.d.ts +22 -0
- package/src/index.js +29 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/compliance-section.d.ts +20 -0
- package/src/lib/components/compliance-section.js +291 -0
- package/src/lib/components/compliance-section.js.map +1 -0
- package/src/lib/components/enroll-dialog.d.ts +36 -0
- package/src/lib/components/enroll-dialog.js +656 -0
- package/src/lib/components/enroll-dialog.js.map +1 -0
- package/src/lib/components/enrollments-table.d.ts +28 -0
- package/src/lib/components/enrollments-table.js +375 -0
- package/src/lib/components/enrollments-table.js.map +1 -0
- package/src/lib/components/mailbox-card.d.ts +46 -0
- package/src/lib/components/mailbox-card.js +517 -0
- package/src/lib/components/mailbox-card.js.map +1 -0
- package/src/lib/components/mailboxes-section.d.ts +24 -0
- package/src/lib/components/mailboxes-section.js +258 -0
- package/src/lib/components/mailboxes-section.js.map +1 -0
- package/src/lib/components/outreach-console-page.d.ts +17 -0
- package/src/lib/components/outreach-console-page.js +81 -0
- package/src/lib/components/outreach-console-page.js.map +1 -0
- package/src/lib/components/outreach-console-sections.d.ts +43 -0
- package/src/lib/components/outreach-console-sections.js +55 -0
- package/src/lib/components/outreach-console-sections.js.map +1 -0
- package/src/lib/components/outreach-ui.d.ts +51 -0
- package/src/lib/components/outreach-ui.js +171 -0
- package/src/lib/components/outreach-ui.js.map +1 -0
- package/src/lib/components/send-window-fields.d.ts +25 -0
- package/src/lib/components/send-window-fields.js +178 -0
- package/src/lib/components/send-window-fields.js.map +1 -0
- package/src/lib/components/sequence-detail.d.ts +54 -0
- package/src/lib/components/sequence-detail.js +435 -0
- package/src/lib/components/sequence-detail.js.map +1 -0
- package/src/lib/components/sequence-editor.d.ts +37 -0
- package/src/lib/components/sequence-editor.js +485 -0
- package/src/lib/components/sequence-editor.js.map +1 -0
- package/src/lib/components/sequence-mailbox-picker.d.ts +44 -0
- package/src/lib/components/sequence-mailbox-picker.js +137 -0
- package/src/lib/components/sequence-mailbox-picker.js.map +1 -0
- package/src/lib/components/sequence-preview.d.ts +27 -0
- package/src/lib/components/sequence-preview.js +146 -0
- package/src/lib/components/sequence-preview.js.map +1 -0
- package/src/lib/components/sequence-step-card.d.ts +31 -0
- package/src/lib/components/sequence-step-card.js +326 -0
- package/src/lib/components/sequence-step-card.js.map +1 -0
- package/src/lib/components/sequences-section.d.ts +40 -0
- package/src/lib/components/sequences-section.js +310 -0
- package/src/lib/components/sequences-section.js.map +1 -0
- package/src/lib/components/use-outreach-api.d.ts +38 -0
- package/src/lib/components/use-outreach-api.js +139 -0
- package/src/lib/components/use-outreach-api.js.map +1 -0
- package/src/lib/components/use-outreach-crm.d.ts +50 -0
- package/src/lib/components/use-outreach-crm.js +200 -0
- package/src/lib/components/use-outreach-crm.js.map +1 -0
- package/src/lib/components/use-outreach-data.d.ts +55 -0
- package/src/lib/components/use-outreach-data.js +199 -0
- package/src/lib/components/use-outreach-data.js.map +1 -0
- package/src/lib/components/use-outreach-mailbox-api.d.ts +30 -0
- package/src/lib/components/use-outreach-mailbox-api.js +101 -0
- package/src/lib/components/use-outreach-mailbox-api.js.map +1 -0
- package/src/lib/components/use-outreach-mailboxes.d.ts +22 -0
- package/src/lib/components/use-outreach-mailboxes.js +68 -0
- package/src/lib/components/use-outreach-mailboxes.js.map +1 -0
- package/src/lib/components/use-outreach-settings.d.ts +17 -0
- package/src/lib/components/use-outreach-settings.js +64 -0
- package/src/lib/components/use-outreach-settings.js.map +1 -0
- package/src/lib/constants/api-routes.d.ts +68 -0
- package/src/lib/constants/api-routes.js +57 -0
- package/src/lib/constants/api-routes.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +46 -0
- package/src/lib/constants/bundle-common.js +55 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/constants/runtime-jobs.d.ts +24 -0
- package/src/lib/constants/runtime-jobs.js +24 -0
- package/src/lib/constants/runtime-jobs.js.map +1 -0
- package/src/lib/declarations.console-server.d.ts +38 -0
- package/src/lib/declarations.console-server.js +99 -0
- package/src/lib/declarations.console-server.js.map +1 -0
- package/src/lib/engine/compose.d.ts +86 -0
- package/src/lib/engine/compose.js +212 -0
- package/src/lib/engine/compose.js.map +1 -0
- package/src/lib/engine/delivery-status.d.ts +53 -0
- package/src/lib/engine/delivery-status.js +184 -0
- package/src/lib/engine/delivery-status.js.map +1 -0
- package/src/lib/engine/do-not-contact.d.ts +18 -0
- package/src/lib/engine/do-not-contact.js +34 -0
- package/src/lib/engine/do-not-contact.js.map +1 -0
- package/src/lib/engine/enrollment-state.d.ts +156 -0
- package/src/lib/engine/enrollment-state.js +303 -0
- package/src/lib/engine/enrollment-state.js.map +1 -0
- package/src/lib/engine/gates.d.ts +125 -0
- package/src/lib/engine/gates.js +232 -0
- package/src/lib/engine/gates.js.map +1 -0
- package/src/lib/engine/index.d.ts +28 -0
- package/src/lib/engine/index.js +33 -0
- package/src/lib/engine/index.js.map +1 -0
- package/src/lib/engine/mailbox-health.d.ts +49 -0
- package/src/lib/engine/mailbox-health.js +83 -0
- package/src/lib/engine/mailbox-health.js.map +1 -0
- package/src/lib/engine/opt-out-intent.d.ts +41 -0
- package/src/lib/engine/opt-out-intent.js +220 -0
- package/src/lib/engine/opt-out-intent.js.map +1 -0
- package/src/lib/engine/recipient-country.d.ts +41 -0
- package/src/lib/engine/recipient-country.js +171 -0
- package/src/lib/engine/recipient-country.js.map +1 -0
- package/src/lib/engine/schedule.d.ts +70 -0
- package/src/lib/engine/schedule.js +115 -0
- package/src/lib/engine/schedule.js.map +1 -0
- package/src/lib/engine/sending-capacity.d.ts +96 -0
- package/src/lib/engine/sending-capacity.js +172 -0
- package/src/lib/engine/sending-capacity.js.map +1 -0
- package/src/lib/engine/sequence-validation.d.ts +90 -0
- package/src/lib/engine/sequence-validation.js +291 -0
- package/src/lib/engine/sequence-validation.js.map +1 -0
- package/src/lib/engine/thread-classification.d.ts +78 -0
- package/src/lib/engine/thread-classification.js +224 -0
- package/src/lib/engine/thread-classification.js.map +1 -0
- package/src/lib/engine/thread-message.d.ts +80 -0
- package/src/lib/engine/thread-message.js +143 -0
- package/src/lib/engine/thread-message.js.map +1 -0
- package/src/lib/enrollment/enroll-people.d.ts +81 -0
- package/src/lib/enrollment/enroll-people.js +250 -0
- package/src/lib/enrollment/enroll-people.js.map +1 -0
- package/src/lib/enrollment/gate-lookups.d.ts +32 -0
- package/src/lib/enrollment/gate-lookups.js +154 -0
- package/src/lib/enrollment/gate-lookups.js.map +1 -0
- package/src/lib/mailboxes/mailbox-api.d.ts +126 -0
- package/src/lib/mailboxes/mailbox-api.js +66 -0
- package/src/lib/mailboxes/mailbox-api.js.map +1 -0
- package/src/lib/mailboxes/mailbox-credentials.d.ts +95 -0
- package/src/lib/mailboxes/mailbox-credentials.js +102 -0
- package/src/lib/mailboxes/mailbox-credentials.js.map +1 -0
- package/src/lib/mailboxes/mailbox-erasure.d.ts +69 -0
- package/src/lib/mailboxes/mailbox-erasure.js +148 -0
- package/src/lib/mailboxes/mailbox-erasure.js.map +1 -0
- package/src/lib/mailboxes/mailbox-gate.d.ts +75 -0
- package/src/lib/mailboxes/mailbox-gate.js +98 -0
- package/src/lib/mailboxes/mailbox-gate.js.map +1 -0
- package/src/lib/mailboxes/mailbox-revoke.d.ts +59 -0
- package/src/lib/mailboxes/mailbox-revoke.js +41 -0
- package/src/lib/mailboxes/mailbox-revoke.js.map +1 -0
- package/src/lib/mailboxes/mailbox-routes.d.ts +119 -0
- package/src/lib/mailboxes/mailbox-routes.js +605 -0
- package/src/lib/mailboxes/mailbox-routes.js.map +1 -0
- package/src/lib/mailboxes/mailbox-settings.d.ts +110 -0
- package/src/lib/mailboxes/mailbox-settings.js +212 -0
- package/src/lib/mailboxes/mailbox-settings.js.map +1 -0
- package/src/lib/mailboxes/mailbox-transport.d.ts +64 -0
- package/src/lib/mailboxes/mailbox-transport.js +94 -0
- package/src/lib/mailboxes/mailbox-transport.js.map +1 -0
- package/src/lib/mailboxes/oauth-redirect.d.ts +41 -0
- package/src/lib/mailboxes/oauth-redirect.js +63 -0
- package/src/lib/mailboxes/oauth-redirect.js.map +1 -0
- package/src/lib/mailboxes/oauth-state.d.ts +152 -0
- package/src/lib/mailboxes/oauth-state.js +212 -0
- package/src/lib/mailboxes/oauth-state.js.map +1 -0
- package/src/lib/mailboxes/outreach-config.d.ts +64 -0
- package/src/lib/mailboxes/outreach-config.js +77 -0
- package/src/lib/mailboxes/outreach-config.js.map +1 -0
- package/src/lib/mailboxes/register-mailbox-routes.d.ts +47 -0
- package/src/lib/mailboxes/register-mailbox-routes.js +135 -0
- package/src/lib/mailboxes/register-mailbox-routes.js.map +1 -0
- package/src/lib/model/compliance-settings.d.ts +78 -0
- package/src/lib/model/compliance-settings.js +155 -0
- package/src/lib/model/compliance-settings.js.map +1 -0
- package/src/lib/model/outreach-api.d.ts +210 -0
- package/src/lib/model/outreach-api.js +28 -0
- package/src/lib/model/outreach-api.js.map +1 -0
- package/src/lib/model/outreach.types.d.ts +563 -0
- package/src/lib/model/outreach.types.js +225 -0
- package/src/lib/model/outreach.types.js.map +1 -0
- package/src/lib/model/sequence-draft.d.ts +84 -0
- package/src/lib/model/sequence-draft.js +239 -0
- package/src/lib/model/sequence-draft.js.map +1 -0
- package/src/lib/model/step-preview.d.ts +45 -0
- package/src/lib/model/step-preview.js +98 -0
- package/src/lib/model/step-preview.js.map +1 -0
- package/src/lib/model/stored-records.d.ts +23 -0
- package/src/lib/model/stored-records.js +84 -0
- package/src/lib/model/stored-records.js.map +1 -0
- package/src/lib/plugin.d.ts +46 -0
- package/src/lib/plugin.js +86 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/routes/enroll-routes.d.ts +64 -0
- package/src/lib/routes/enroll-routes.js +350 -0
- package/src/lib/routes/enroll-routes.js.map +1 -0
- package/src/lib/routes/enrollment-routes.d.ts +19 -0
- package/src/lib/routes/enrollment-routes.js +171 -0
- package/src/lib/routes/enrollment-routes.js.map +1 -0
- package/src/lib/routes/platform-deps.d.ts +51 -0
- package/src/lib/routes/platform-deps.js +66 -0
- package/src/lib/routes/platform-deps.js.map +1 -0
- package/src/lib/routes/preview-routes.d.ts +33 -0
- package/src/lib/routes/preview-routes.js +117 -0
- package/src/lib/routes/preview-routes.js.map +1 -0
- package/src/lib/routes/register-routes.d.ts +22 -0
- package/src/lib/routes/register-routes.js +92 -0
- package/src/lib/routes/register-routes.js.map +1 -0
- package/src/lib/routes/route-deps.d.ts +49 -0
- package/src/lib/routes/route-deps.js +27 -0
- package/src/lib/routes/route-deps.js.map +1 -0
- package/src/lib/routes/route-gate.d.ts +94 -0
- package/src/lib/routes/route-gate.js +87 -0
- package/src/lib/routes/route-gate.js.map +1 -0
- package/src/lib/routes/route-http.d.ts +32 -0
- package/src/lib/routes/route-http.js +62 -0
- package/src/lib/routes/route-http.js.map +1 -0
- package/src/lib/routes/sequence-routes.d.ts +32 -0
- package/src/lib/routes/sequence-routes.js +359 -0
- package/src/lib/routes/sequence-routes.js.map +1 -0
- package/src/lib/routes/settings-routes.d.ts +30 -0
- package/src/lib/routes/settings-routes.js +73 -0
- package/src/lib/routes/settings-routes.js.map +1 -0
- package/src/lib/runtime/enrollment-events.d.ts +86 -0
- package/src/lib/runtime/enrollment-events.js +141 -0
- package/src/lib/runtime/enrollment-events.js.map +1 -0
- package/src/lib/runtime/fixtures/fake-gmail.d.ts +121 -0
- package/src/lib/runtime/fixtures/fake-gmail.js +312 -0
- package/src/lib/runtime/fixtures/fake-gmail.js.map +1 -0
- package/src/lib/runtime/mailbox-health-store.d.ts +47 -0
- package/src/lib/runtime/mailbox-health-store.js +168 -0
- package/src/lib/runtime/mailbox-health-store.js.map +1 -0
- package/src/lib/runtime/person-erasure.d.ts +39 -0
- package/src/lib/runtime/person-erasure.js +57 -0
- package/src/lib/runtime/person-erasure.js.map +1 -0
- package/src/lib/runtime/platform-runtime-deps.d.ts +29 -0
- package/src/lib/runtime/platform-runtime-deps.js +98 -0
- package/src/lib/runtime/platform-runtime-deps.js.map +1 -0
- package/src/lib/runtime/runtime-deps.d.ts +77 -0
- package/src/lib/runtime/runtime-deps.js +18 -0
- package/src/lib/runtime/runtime-deps.js.map +1 -0
- package/src/lib/runtime/send-job.d.ts +42 -0
- package/src/lib/runtime/send-job.js +780 -0
- package/src/lib/runtime/send-job.js.map +1 -0
- package/src/lib/runtime/sync-job.d.ts +41 -0
- package/src/lib/runtime/sync-job.js +480 -0
- package/src/lib/runtime/sync-job.js.map +1 -0
- package/src/lib/runtime/timeline.d.ts +67 -0
- package/src/lib/runtime/timeline.js +93 -0
- package/src/lib/runtime/timeline.js.map +1 -0
- package/src/lib/runtime/unsubscribe-link.d.ts +75 -0
- package/src/lib/runtime/unsubscribe-link.js +121 -0
- package/src/lib/runtime/unsubscribe-link.js.map +1 -0
- package/src/lib/runtime/unsubscribe-route.d.ts +24 -0
- package/src/lib/runtime/unsubscribe-route.js +103 -0
- package/src/lib/runtime/unsubscribe-route.js.map +1 -0
- package/src/lib/server.d.ts +53 -0
- package/src/lib/server.js +94 -0
- package/src/lib/server.js.map +1 -0
- package/src/lib/storage/compliance-settings-store.d.ts +44 -0
- package/src/lib/storage/compliance-settings-store.js +78 -0
- package/src/lib/storage/compliance-settings-store.js.map +1 -0
- package/src/lib/storage/do-not-contact-store.d.ts +60 -0
- package/src/lib/storage/do-not-contact-store.js +168 -0
- package/src/lib/storage/do-not-contact-store.js.map +1 -0
- package/src/lib/storage/outreach-records.d.ts +33 -0
- package/src/lib/storage/outreach-records.js +35 -0
- package/src/lib/storage/outreach-records.js.map +1 -0
- package/src/lib/subprocessors.d.ts +48 -0
- package/src/lib/subprocessors.js +70 -0
- package/src/lib/subprocessors.js.map +1 -0
- package/src/lib/transport/gmail-client.d.ts +145 -0
- package/src/lib/transport/gmail-client.js +229 -0
- package/src/lib/transport/gmail-client.js.map +1 -0
- package/src/lib/transport/gmail-errors.d.ts +84 -0
- package/src/lib/transport/gmail-errors.js +138 -0
- package/src/lib/transport/gmail-errors.js.map +1 -0
- package/src/lib/transport/google-oauth.d.ts +138 -0
- package/src/lib/transport/google-oauth.js +247 -0
- package/src/lib/transport/google-oauth.js.map +1 -0
- package/src/lib/transport/http.d.ts +53 -0
- package/src/lib/transport/http.js +79 -0
- package/src/lib/transport/http.js.map +1 -0
- package/src/lib/transport/rfc5322.d.ts +116 -0
- package/src/lib/transport/rfc5322.js +330 -0
- package/src/lib/transport/rfc5322.js.map +1 -0
- package/src/lib/transport/send-message.d.ts +69 -0
- package/src/lib/transport/send-message.js +77 -0
- package/src/lib/transport/send-message.js.map +1 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
import { createGmailClient } from "../transport/gmail-client.js";
|
|
18
|
+
import { GmailTransportError } from "../transport/gmail-errors.js";
|
|
19
|
+
import { buildGoogleAuthorizationUrl, exchangeGoogleAuthorizationCode, grantedScopesInclude, OUTREACH_REQUIRED_GRANTED_SCOPES, pkceChallenge, readGoogleIdToken } from "../transport/google-oauth.js";
|
|
20
|
+
import { Rfc5322MessageError } from "../transport/rfc5322.js";
|
|
21
|
+
import { sendOutreachMessage } from "../transport/send-message.js";
|
|
22
|
+
import { buildConnectReturnFragment } from "./mailbox-api.js";
|
|
23
|
+
import { mailboxCredentialsRef, mailboxRef, outreachMailboxId, readMailboxCredentials, sealMailboxRefreshToken } from "./mailbox-credentials.js";
|
|
24
|
+
import { revokeMailboxGrant } from "./mailbox-revoke.js";
|
|
25
|
+
import { outreachMemberGate, refusal } from "./mailbox-gate.js";
|
|
26
|
+
import { defaultSendAs, isValidTimezone, OUTREACH_DEFAULT_DAILY_CAP, OUTREACH_DEFAULT_WINDOW, OUTREACH_MAX_MAILBOXES_PER_MEMBER, validateDailyCap, validateDisplayName, validateSendWindow, verifiedSendAsAddresses } from "./mailbox-settings.js";
|
|
27
|
+
import { markOutreachMailboxReconnectRequired, openOutreachMailboxClient } from "./mailbox-transport.js";
|
|
28
|
+
import { consumeOutreachOAuthState, mintOutreachOAuthState, outreachOidcNonce, outreachPkceVerifier, readOutreachOAuthState, recordOutreachOAuthState } from "./oauth-state.js";
|
|
29
|
+
import { OUTREACH_NOT_CONFIGURED_MESSAGE } from "./outreach-config.js";
|
|
30
|
+
/** Test sends one mailbox may make in an hour. */ export const OUTREACH_TEST_SENDS_PER_HOUR = 5;
|
|
31
|
+
/**
|
|
32
|
+
* The org activity target a mailbox's rows are filed under: Outreach's own
|
|
33
|
+
* namespaced resource, which the feed reads as "Mailbox".
|
|
34
|
+
*/ export const OUTREACH_MAILBOX_ACTIVITY_TARGET = 'outreach:mailbox';
|
|
35
|
+
const MAILBOX_ID = /^[A-Za-z0-9_-]{1,128}$/;
|
|
36
|
+
const MAX_CODE_CHARS = 2048;
|
|
37
|
+
const noStore = {
|
|
38
|
+
'Cache-Control': 'no-store'
|
|
39
|
+
};
|
|
40
|
+
const ok = (body)=>Response.json(body, {
|
|
41
|
+
status: 200,
|
|
42
|
+
headers: noStore
|
|
43
|
+
});
|
|
44
|
+
function methodNotAllowed(allow) {
|
|
45
|
+
return Response.json({
|
|
46
|
+
error: 'Method not allowed',
|
|
47
|
+
reason: 'method-not-allowed'
|
|
48
|
+
}, {
|
|
49
|
+
status: 405,
|
|
50
|
+
headers: _extends({}, noStore, {
|
|
51
|
+
Allow: allow
|
|
52
|
+
})
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async function readBody(request) {
|
|
56
|
+
const body = await request.json().catch(()=>null);
|
|
57
|
+
return body && typeof body === 'object' && !Array.isArray(body) ? body : {};
|
|
58
|
+
}
|
|
59
|
+
function notConfigured() {
|
|
60
|
+
return refusal(503, 'not-configured', OUTREACH_NOT_CONFIGURED_MESSAGE);
|
|
61
|
+
}
|
|
62
|
+
/** A mailbox document read defensively into the model shape. */ function readMailbox(id, data) {
|
|
63
|
+
if (!data) return null;
|
|
64
|
+
return _extends({}, data, {
|
|
65
|
+
id
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const activityName = (mailbox)=>mailbox.email;
|
|
69
|
+
export function createOutreachMailboxRoutes(deps) {
|
|
70
|
+
/** The gate, then the mailbox the body names, then who may touch it. */ async function loadManagedMailbox(request, body, who) {
|
|
71
|
+
const gate = await outreachMemberGate(request, body['orgId'], deps.gate);
|
|
72
|
+
if (gate instanceof Response) return gate;
|
|
73
|
+
const mailboxId = typeof body['mailboxId'] === 'string' ? body['mailboxId'] : '';
|
|
74
|
+
if (!MAILBOX_ID.test(mailboxId)) return refusal(400, 'invalid-request', 'Name the mailbox.');
|
|
75
|
+
const snapshot = await mailboxRef(deps.firestore(), gate.orgId, mailboxId).get();
|
|
76
|
+
const mailbox = readMailbox(mailboxId, snapshot.exists ? snapshot.data() : undefined);
|
|
77
|
+
if (!mailbox) return refusal(404, 'mailbox-not-found', 'That mailbox is not connected to this organization.');
|
|
78
|
+
const own = mailbox.connectedByUid === gate.uid;
|
|
79
|
+
if (!own && !(who === 'member-or-admin' && gate.isOrgAdmin)) {
|
|
80
|
+
return refusal(403, 'not-your-mailbox', who === 'member' ? 'Only the member who connected this mailbox can do that.' : 'Only the member who connected this mailbox, or an organization owner or admin, can change it.');
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
gate,
|
|
84
|
+
mailbox
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const availability = async (request)=>{
|
|
88
|
+
if (request.method !== 'GET') return methodNotAllowed('GET');
|
|
89
|
+
const gate = await outreachMemberGate(request, new URL(request.url).searchParams.get('orgId'), deps.gate);
|
|
90
|
+
if (gate instanceof Response) return gate;
|
|
91
|
+
const configured = deps.readConfig().configured && deps.stateSigningConfigured() && Boolean(deps.redirectUri(request.url));
|
|
92
|
+
return ok({
|
|
93
|
+
configured,
|
|
94
|
+
canManageAll: gate.isOrgAdmin
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
const connect = async (request)=>{
|
|
98
|
+
if (request.method !== 'POST') return methodNotAllowed('POST');
|
|
99
|
+
const body = await readBody(request);
|
|
100
|
+
const gate = await outreachMemberGate(request, body['orgId'], deps.gate);
|
|
101
|
+
if (gate instanceof Response) return gate;
|
|
102
|
+
const config = deps.readConfig();
|
|
103
|
+
const redirectUri = deps.redirectUri(request.url);
|
|
104
|
+
if (!config.configured || !redirectUri || !deps.stateSigningConfigured()) return notConfigured();
|
|
105
|
+
const nowMs = deps.now();
|
|
106
|
+
const { state, claims } = mintOutreachOAuthState({
|
|
107
|
+
orgId: gate.orgId,
|
|
108
|
+
uid: gate.uid,
|
|
109
|
+
nowMs
|
|
110
|
+
});
|
|
111
|
+
await recordOutreachOAuthState(deps.firestore(), {
|
|
112
|
+
claims,
|
|
113
|
+
redirectUri,
|
|
114
|
+
nowMs
|
|
115
|
+
});
|
|
116
|
+
const url = buildGoogleAuthorizationUrl({
|
|
117
|
+
clientId: config.config.clientId,
|
|
118
|
+
redirectUri,
|
|
119
|
+
state,
|
|
120
|
+
codeChallenge: pkceChallenge(outreachPkceVerifier(claims.nonce)),
|
|
121
|
+
nonce: outreachOidcNonce(claims.nonce),
|
|
122
|
+
loginHint: gate.email
|
|
123
|
+
});
|
|
124
|
+
return ok({
|
|
125
|
+
url
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
const oauthCallback = async (request)=>{
|
|
129
|
+
var _params_get;
|
|
130
|
+
if (request.method !== 'GET') return methodNotAllowed('GET');
|
|
131
|
+
const params = new URL(request.url).searchParams;
|
|
132
|
+
const read = readOutreachOAuthState(params.get('state'), deps.now());
|
|
133
|
+
const plain = (status, text)=>new Response(text, {
|
|
134
|
+
status,
|
|
135
|
+
headers: _extends({}, noStore, {
|
|
136
|
+
'Content-Type': 'text/plain; charset=utf-8',
|
|
137
|
+
'Referrer-Policy': 'no-referrer'
|
|
138
|
+
})
|
|
139
|
+
});
|
|
140
|
+
if (read.ok === false && read.refusal === 'state-invalid') {
|
|
141
|
+
return plain(400, 'This connection link is not valid. Return to the console and connect the mailbox again.');
|
|
142
|
+
}
|
|
143
|
+
const claims = read.ok ? read.claims : read.claims;
|
|
144
|
+
const org = await deps.gate.readOrg(claims.orgId);
|
|
145
|
+
const slug = typeof (org == null ? void 0 : org['slug']) === 'string' ? org['slug'] : '';
|
|
146
|
+
if (!slug) return plain(404, 'That organization could not be found.');
|
|
147
|
+
let fragment;
|
|
148
|
+
const googleError = params.get('error');
|
|
149
|
+
const code = (_params_get = params.get('code')) != null ? _params_get : '';
|
|
150
|
+
if (googleError) {
|
|
151
|
+
fragment = {
|
|
152
|
+
kind: 'error',
|
|
153
|
+
reason: googleError === 'access_denied' ? 'access_denied' : 'google_error'
|
|
154
|
+
};
|
|
155
|
+
} else if (!read.ok) {
|
|
156
|
+
fragment = {
|
|
157
|
+
kind: 'error',
|
|
158
|
+
reason: 'expired'
|
|
159
|
+
};
|
|
160
|
+
} else if (!code || code.length > MAX_CODE_CHARS) {
|
|
161
|
+
fragment = {
|
|
162
|
+
kind: 'error',
|
|
163
|
+
reason: 'google_error'
|
|
164
|
+
};
|
|
165
|
+
} else {
|
|
166
|
+
var _params_get1;
|
|
167
|
+
fragment = {
|
|
168
|
+
kind: 'code',
|
|
169
|
+
code,
|
|
170
|
+
state: (_params_get1 = params.get('state')) != null ? _params_get1 : ''
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
// A RELATIVE location on the callback's own origin: the path is built from
|
|
174
|
+
// the organization's slug, never from anything the request supplied.
|
|
175
|
+
return new Response(null, {
|
|
176
|
+
status: 303,
|
|
177
|
+
headers: _extends({}, noStore, {
|
|
178
|
+
'Referrer-Policy': 'no-referrer',
|
|
179
|
+
Location: `/${encodeURIComponent(slug)}/outreach/mailboxes#${buildConnectReturnFragment(fragment)}`
|
|
180
|
+
})
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
const connectComplete = async (request)=>{
|
|
184
|
+
var _aliases_confirmed;
|
|
185
|
+
if (request.method !== 'POST') return methodNotAllowed('POST');
|
|
186
|
+
const body = await readBody(request);
|
|
187
|
+
const gate = await outreachMemberGate(request, body['orgId'], deps.gate);
|
|
188
|
+
if (gate instanceof Response) return gate;
|
|
189
|
+
const config = deps.readConfig();
|
|
190
|
+
if (!config.configured || !deps.stateSigningConfigured()) return notConfigured();
|
|
191
|
+
const code = typeof body['code'] === 'string' ? body['code'] : '';
|
|
192
|
+
if (!code || code.length > MAX_CODE_CHARS) return refusal(400, 'invalid-request', 'The connection did not return a code.');
|
|
193
|
+
const firestore = deps.firestore();
|
|
194
|
+
const nowMs = deps.now();
|
|
195
|
+
const read = readOutreachOAuthState(body['state'], nowMs);
|
|
196
|
+
if (read.ok === false && read.refusal === 'state-invalid') {
|
|
197
|
+
return refusal(400, 'state-invalid', 'This connection could not be verified. Connect the mailbox again.');
|
|
198
|
+
}
|
|
199
|
+
const claims = read.ok ? read.claims : read.claims;
|
|
200
|
+
if (claims.uid !== gate.uid) {
|
|
201
|
+
return refusal(403, 'state-user-mismatch', 'This connection was started by another member. Sign in as that member, or connect the mailbox again.');
|
|
202
|
+
}
|
|
203
|
+
if (claims.orgId !== gate.orgId) {
|
|
204
|
+
return refusal(400, 'state-org-mismatch', 'This connection was started in another organization.');
|
|
205
|
+
}
|
|
206
|
+
if (!read.ok) return refusal(410, 'state-expired', 'This connection took too long. Connect the mailbox again.');
|
|
207
|
+
const consumed = await consumeOutreachOAuthState(firestore, {
|
|
208
|
+
claims,
|
|
209
|
+
nowMs
|
|
210
|
+
});
|
|
211
|
+
if (consumed.ok === false) {
|
|
212
|
+
if (consumed.refusal === 'state-expired') {
|
|
213
|
+
return refusal(410, 'state-expired', 'This connection took too long. Connect the mailbox again.');
|
|
214
|
+
}
|
|
215
|
+
return refusal(409, consumed.refusal, consumed.refusal === 'state-superseded' ? 'A newer connection was started. Finish that one, or connect the mailbox again.' : 'This connection was already used. Connect the mailbox again.');
|
|
216
|
+
}
|
|
217
|
+
let grant;
|
|
218
|
+
try {
|
|
219
|
+
grant = await exchangeGoogleAuthorizationCode({
|
|
220
|
+
clientId: config.config.clientId,
|
|
221
|
+
clientSecret: config.config.clientSecret,
|
|
222
|
+
code,
|
|
223
|
+
redirectUri: consumed.redirectUri,
|
|
224
|
+
codeVerifier: outreachPkceVerifier(claims.nonce)
|
|
225
|
+
}, deps.transport);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
if (error instanceof GmailTransportError && error.code === 'invalid_grant') {
|
|
228
|
+
return refusal(400, 'code-rejected', "Google's sign-in expired or was already used. Connect the mailbox again.");
|
|
229
|
+
}
|
|
230
|
+
if (error instanceof GmailTransportError && error.code === 'client_misconfigured') {
|
|
231
|
+
return refusal(503, 'not-configured', "Google refused this deployment's OAuth client.");
|
|
232
|
+
}
|
|
233
|
+
return googleUnavailable(error);
|
|
234
|
+
}
|
|
235
|
+
if (!grant.refreshToken) {
|
|
236
|
+
return refusal(422, 'refresh-token-missing', 'Google did not grant offline access, so the mailbox could not send later. Connect it again.');
|
|
237
|
+
}
|
|
238
|
+
if (!grantedScopesInclude(grant.scope, OUTREACH_REQUIRED_GRANTED_SCOPES)) {
|
|
239
|
+
return refusal(422, 'scopes-missing', 'Sequences needs permission to send and to read your mail. Connect again and allow both.');
|
|
240
|
+
}
|
|
241
|
+
const identity = readGoogleIdToken(grant.idToken, {
|
|
242
|
+
clientId: config.config.clientId,
|
|
243
|
+
nonce: outreachOidcNonce(claims.nonce),
|
|
244
|
+
nowMs
|
|
245
|
+
});
|
|
246
|
+
if (identity.ok === false) {
|
|
247
|
+
return refusal(422, 'identity-unverified', identity.refusal === 'email-unverified' ? "Google has not verified this account's email address." : "Google's answer could not be verified. Connect the mailbox again.");
|
|
248
|
+
}
|
|
249
|
+
const client = createGmailClient(_extends({}, deps.transport, {
|
|
250
|
+
clientId: config.config.clientId,
|
|
251
|
+
clientSecret: config.config.clientSecret,
|
|
252
|
+
refreshToken: grant.refreshToken
|
|
253
|
+
}));
|
|
254
|
+
let profileEmail;
|
|
255
|
+
let sendAsOptions;
|
|
256
|
+
try {
|
|
257
|
+
const [profile, sendAs] = await Promise.all([
|
|
258
|
+
client.getProfile(),
|
|
259
|
+
client.listSendAs()
|
|
260
|
+
]);
|
|
261
|
+
profileEmail = profile.emailAddress;
|
|
262
|
+
sendAsOptions = verifiedSendAsAddresses(sendAs);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
if (error instanceof GmailTransportError && error.reconnectRequired) {
|
|
265
|
+
return refusal(422, 'scopes-missing', 'Sequences needs permission to send and to read your mail. Connect again and allow both.');
|
|
266
|
+
}
|
|
267
|
+
return googleUnavailable(error);
|
|
268
|
+
}
|
|
269
|
+
if (profileEmail !== identity.identity.email) {
|
|
270
|
+
return refusal(422, 'account-mismatch', 'The Gmail account and the Google sign-in did not match. Connect again.');
|
|
271
|
+
}
|
|
272
|
+
if (!sendAsOptions.some((option)=>option.email === profileEmail)) {
|
|
273
|
+
sendAsOptions = [
|
|
274
|
+
{
|
|
275
|
+
email: profileEmail,
|
|
276
|
+
displayName: '',
|
|
277
|
+
isPrimary: true,
|
|
278
|
+
isDefault: !sendAsOptions.some((o)=>o.isDefault)
|
|
279
|
+
},
|
|
280
|
+
...sendAsOptions
|
|
281
|
+
];
|
|
282
|
+
}
|
|
283
|
+
const mailboxId = outreachMailboxId(gate.orgId, gate.uid, identity.identity.sub);
|
|
284
|
+
const mailboxDoc = mailboxRef(firestore, gate.orgId, mailboxId);
|
|
285
|
+
const credentialDoc = mailboxCredentialsRef(firestore, mailboxId);
|
|
286
|
+
const [existingMailbox, existingCredential] = await Promise.all([
|
|
287
|
+
mailboxDoc.get(),
|
|
288
|
+
credentialDoc.get()
|
|
289
|
+
]);
|
|
290
|
+
const current = readMailbox(mailboxId, existingMailbox.exists ? existingMailbox.data() : undefined);
|
|
291
|
+
if (!current) {
|
|
292
|
+
const mine = await firestore.collection('orgs').doc(gate.orgId).collection('outreachMailboxes').where('connectedByUid', '==', gate.uid).limit(OUTREACH_MAX_MAILBOXES_PER_MEMBER).get();
|
|
293
|
+
if (mine.size >= OUTREACH_MAX_MAILBOXES_PER_MEMBER) {
|
|
294
|
+
return refusal(409, 'mailbox-limit', `You can connect up to ${OUTREACH_MAX_MAILBOXES_PER_MEMBER} mailboxes. Disconnect one before connecting another.`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const defaultOption = sendAsOptions.find((option)=>option.email === defaultSendAs(sendAsOptions, profileEmail));
|
|
298
|
+
let mailbox;
|
|
299
|
+
/** On a reconnect, only the fields a connect decides — see the write below. */ let reconnected = null;
|
|
300
|
+
if (current) {
|
|
301
|
+
var _ref;
|
|
302
|
+
const keepSendAs = sendAsOptions.some((option)=>option.email === current.sendAs);
|
|
303
|
+
reconnected = {
|
|
304
|
+
provider: 'google',
|
|
305
|
+
email: profileEmail,
|
|
306
|
+
sendAs: keepSendAs ? current.sendAs : (_ref = defaultOption == null ? void 0 : defaultOption.email) != null ? _ref : profileEmail,
|
|
307
|
+
sendAsOptions,
|
|
308
|
+
// A reconnect restores a mailbox Google had cut off, and leaves a
|
|
309
|
+
// mailbox its member paused paused.
|
|
310
|
+
status: current.status === 'paused' ? 'paused' : 'connected',
|
|
311
|
+
connectedAtMs: nowMs,
|
|
312
|
+
updatedAtMs: nowMs
|
|
313
|
+
};
|
|
314
|
+
mailbox = _extends({}, current, reconnected);
|
|
315
|
+
} else {
|
|
316
|
+
var _ref1;
|
|
317
|
+
const timezone = isValidTimezone(body['timezone']) ? String(body['timezone']) : 'UTC';
|
|
318
|
+
const displayName = validateDisplayName((defaultOption == null ? void 0 : defaultOption.displayName) || gate.name || '');
|
|
319
|
+
mailbox = {
|
|
320
|
+
id: mailboxId,
|
|
321
|
+
provider: 'google',
|
|
322
|
+
email: profileEmail,
|
|
323
|
+
sendAs: (_ref1 = defaultOption == null ? void 0 : defaultOption.email) != null ? _ref1 : profileEmail,
|
|
324
|
+
sendAsOptions,
|
|
325
|
+
displayName: typeof displayName === 'string' ? displayName : '',
|
|
326
|
+
status: 'connected',
|
|
327
|
+
dailyCap: OUTREACH_DEFAULT_DAILY_CAP,
|
|
328
|
+
window: _extends({}, OUTREACH_DEFAULT_WINDOW, {
|
|
329
|
+
days: [
|
|
330
|
+
...OUTREACH_DEFAULT_WINDOW.days
|
|
331
|
+
]
|
|
332
|
+
}),
|
|
333
|
+
timezone,
|
|
334
|
+
rampStartedAtMs: nowMs,
|
|
335
|
+
health: {
|
|
336
|
+
sentToday: 0,
|
|
337
|
+
sentOnDay: null,
|
|
338
|
+
bounces: 0,
|
|
339
|
+
replies: 0,
|
|
340
|
+
lastSentAtMs: null,
|
|
341
|
+
lastErrorAtMs: null,
|
|
342
|
+
lastErrorCode: null,
|
|
343
|
+
daily: {}
|
|
344
|
+
},
|
|
345
|
+
connectedByUid: gate.uid,
|
|
346
|
+
connectedAtMs: nowMs,
|
|
347
|
+
createdAtMs: nowMs,
|
|
348
|
+
updatedAtMs: nowMs
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
const credential = _extends({
|
|
352
|
+
id: mailboxId,
|
|
353
|
+
orgId: gate.orgId,
|
|
354
|
+
mailboxId,
|
|
355
|
+
provider: 'google',
|
|
356
|
+
connectedByUid: gate.uid,
|
|
357
|
+
providerAccountId: identity.identity.sub,
|
|
358
|
+
email: profileEmail,
|
|
359
|
+
scopes: grant.scope.split(/\s+/).filter(Boolean)
|
|
360
|
+
}, sealMailboxRefreshToken(grant.refreshToken, mailboxId, config.config.keyring), {
|
|
361
|
+
createdAtMs: Number(existingCredential.exists ? existingCredential.get('createdAtMs') : 0) || nowMs,
|
|
362
|
+
updatedAtMs: nowMs
|
|
363
|
+
});
|
|
364
|
+
const batch = firestore.batch();
|
|
365
|
+
// A reconnect MERGES only what a connect decides, so the settings a member
|
|
366
|
+
// chose and the health counters the sending runtime increments are never
|
|
367
|
+
// written back from a read that may already be stale.
|
|
368
|
+
if (reconnected) batch.set(mailboxDoc, reconnected, {
|
|
369
|
+
merge: true
|
|
370
|
+
});
|
|
371
|
+
else batch.set(mailboxDoc, mailbox);
|
|
372
|
+
batch.set(credentialDoc, credential);
|
|
373
|
+
await batch.commit();
|
|
374
|
+
const aliases = await deps.confirmAliasesByProvider(firestore, {
|
|
375
|
+
orgId: gate.orgId,
|
|
376
|
+
uid: gate.uid,
|
|
377
|
+
addresses: sendAsOptions.map((option)=>option.email),
|
|
378
|
+
nowMs
|
|
379
|
+
}).catch((error)=>{
|
|
380
|
+
console.error('[outreach] confirming member aliases from send-as failed', error);
|
|
381
|
+
return {
|
|
382
|
+
ok: false
|
|
383
|
+
};
|
|
384
|
+
});
|
|
385
|
+
await deps.logOrgActivity(gate.orgId, {
|
|
386
|
+
uid: gate.uid,
|
|
387
|
+
email: gate.email
|
|
388
|
+
}, current ? 'Reconnected a mailbox in Sequences' : 'Connected a mailbox in Sequences', {
|
|
389
|
+
type: OUTREACH_MAILBOX_ACTIVITY_TARGET,
|
|
390
|
+
id: mailboxId,
|
|
391
|
+
name: activityName(mailbox)
|
|
392
|
+
});
|
|
393
|
+
return ok({
|
|
394
|
+
ok: true,
|
|
395
|
+
mailbox,
|
|
396
|
+
created: !current,
|
|
397
|
+
confirmedAliases: aliases.ok ? (_aliases_confirmed = aliases.confirmed) != null ? _aliases_confirmed : [] : []
|
|
398
|
+
});
|
|
399
|
+
};
|
|
400
|
+
const settings = async (request)=>{
|
|
401
|
+
if (request.method !== 'POST') return methodNotAllowed('POST');
|
|
402
|
+
const body = await readBody(request);
|
|
403
|
+
const loaded = await loadManagedMailbox(request, body, 'member-or-admin');
|
|
404
|
+
if (loaded instanceof Response) return loaded;
|
|
405
|
+
const { gate, mailbox } = loaded;
|
|
406
|
+
const update = {};
|
|
407
|
+
const invalid = (message)=>refusal(400, 'invalid-settings', message);
|
|
408
|
+
if (body['sendAs'] !== undefined) {
|
|
409
|
+
var _mailbox_sendAsOptions;
|
|
410
|
+
const sendAs = typeof body['sendAs'] === 'string' ? body['sendAs'].trim().toLowerCase() : '';
|
|
411
|
+
if (!((_mailbox_sendAsOptions = mailbox.sendAsOptions) != null ? _mailbox_sendAsOptions : []).some((option)=>option.email === sendAs)) {
|
|
412
|
+
return invalid('Choose one of the addresses Gmail has verified for this account.');
|
|
413
|
+
}
|
|
414
|
+
update.sendAs = sendAs;
|
|
415
|
+
}
|
|
416
|
+
if (body['displayName'] !== undefined) {
|
|
417
|
+
const name = validateDisplayName(body['displayName']);
|
|
418
|
+
if (typeof name !== 'string') return invalid(name.message);
|
|
419
|
+
update.displayName = name;
|
|
420
|
+
}
|
|
421
|
+
if (body['dailyCap'] !== undefined) {
|
|
422
|
+
const cap = validateDailyCap(body['dailyCap']);
|
|
423
|
+
if (typeof cap !== 'number') return invalid(cap.message);
|
|
424
|
+
update.dailyCap = cap;
|
|
425
|
+
}
|
|
426
|
+
if (body['window'] !== undefined) {
|
|
427
|
+
const window = validateSendWindow(body['window']);
|
|
428
|
+
if (!('days' in window)) return invalid(window.message);
|
|
429
|
+
update.window = window;
|
|
430
|
+
}
|
|
431
|
+
if (body['timezone'] !== undefined) {
|
|
432
|
+
if (!isValidTimezone(body['timezone'])) return invalid('Choose a timezone from the list.');
|
|
433
|
+
update.timezone = body['timezone'];
|
|
434
|
+
}
|
|
435
|
+
if (!Object.keys(update).length) return ok({
|
|
436
|
+
ok: true,
|
|
437
|
+
mailbox
|
|
438
|
+
});
|
|
439
|
+
const nowMs = deps.now();
|
|
440
|
+
await mailboxRef(deps.firestore(), gate.orgId, mailbox.id).update(_extends({}, update, {
|
|
441
|
+
updatedAtMs: nowMs
|
|
442
|
+
}));
|
|
443
|
+
return ok({
|
|
444
|
+
ok: true,
|
|
445
|
+
mailbox: _extends({}, mailbox, update, {
|
|
446
|
+
updatedAtMs: nowMs
|
|
447
|
+
})
|
|
448
|
+
});
|
|
449
|
+
};
|
|
450
|
+
const status = async (request)=>{
|
|
451
|
+
if (request.method !== 'POST') return methodNotAllowed('POST');
|
|
452
|
+
const body = await readBody(request);
|
|
453
|
+
if (typeof body['paused'] !== 'boolean') return refusal(400, 'invalid-request', 'Say whether to pause the mailbox.');
|
|
454
|
+
const loaded = await loadManagedMailbox(request, body, 'member-or-admin');
|
|
455
|
+
if (loaded instanceof Response) return loaded;
|
|
456
|
+
const { gate, mailbox } = loaded;
|
|
457
|
+
if (mailbox.status === 'reconnect_required' || mailbox.status === 'disconnected') {
|
|
458
|
+
return refusal(409, 'reconnect-required', 'Reconnect this mailbox before pausing or resuming it.');
|
|
459
|
+
}
|
|
460
|
+
const next = body['paused'] ? 'paused' : 'connected';
|
|
461
|
+
if (mailbox.status === next) return ok({
|
|
462
|
+
ok: true,
|
|
463
|
+
mailbox
|
|
464
|
+
});
|
|
465
|
+
const nowMs = deps.now();
|
|
466
|
+
// A member's pause or resume answers an automatic pause (AGL-2981): its
|
|
467
|
+
// reason goes, and the next bounce is judged as new evidence.
|
|
468
|
+
await mailboxRef(deps.firestore(), gate.orgId, mailbox.id).update({
|
|
469
|
+
status: next,
|
|
470
|
+
autoPause: null,
|
|
471
|
+
updatedAtMs: nowMs
|
|
472
|
+
});
|
|
473
|
+
await deps.logOrgActivity(gate.orgId, {
|
|
474
|
+
uid: gate.uid,
|
|
475
|
+
email: gate.email
|
|
476
|
+
}, next === 'paused' ? 'Paused a mailbox in Sequences' : 'Resumed a mailbox in Sequences', {
|
|
477
|
+
type: OUTREACH_MAILBOX_ACTIVITY_TARGET,
|
|
478
|
+
id: mailbox.id,
|
|
479
|
+
name: activityName(mailbox)
|
|
480
|
+
});
|
|
481
|
+
return ok({
|
|
482
|
+
ok: true,
|
|
483
|
+
mailbox: _extends({}, mailbox, {
|
|
484
|
+
status: next,
|
|
485
|
+
autoPause: null,
|
|
486
|
+
updatedAtMs: nowMs
|
|
487
|
+
})
|
|
488
|
+
});
|
|
489
|
+
};
|
|
490
|
+
const test = async (request)=>{
|
|
491
|
+
if (request.method !== 'POST') return methodNotAllowed('POST');
|
|
492
|
+
const body = await readBody(request);
|
|
493
|
+
const loaded = await loadManagedMailbox(request, body, 'member');
|
|
494
|
+
if (loaded instanceof Response) return loaded;
|
|
495
|
+
const { gate, mailbox } = loaded;
|
|
496
|
+
if (mailbox.status === 'reconnect_required' || mailbox.status === 'disconnected') {
|
|
497
|
+
return refusal(409, 'reconnect-required', 'Reconnect this mailbox before sending from it.');
|
|
498
|
+
}
|
|
499
|
+
if (!deps.readConfig().configured) return notConfigured();
|
|
500
|
+
const limited = await deps.consumeRateLimit(`outreach-mailbox-test:${mailbox.id}`, {
|
|
501
|
+
limit: OUTREACH_TEST_SENDS_PER_HOUR,
|
|
502
|
+
windowMs: 60 * 60 * 1000
|
|
503
|
+
});
|
|
504
|
+
if (!limited.allowed) {
|
|
505
|
+
return refusal(429, 'rate-limited', `A mailbox can send ${OUTREACH_TEST_SENDS_PER_HOUR} tests an hour. Try again later.`);
|
|
506
|
+
}
|
|
507
|
+
const firestore = deps.firestore();
|
|
508
|
+
const nowMs = deps.now();
|
|
509
|
+
const opened = await openOutreachMailboxClient(firestore, {
|
|
510
|
+
mailboxId: mailbox.id
|
|
511
|
+
}, _extends({}, deps.transport, {
|
|
512
|
+
readConfig: deps.readConfig,
|
|
513
|
+
now: deps.now
|
|
514
|
+
}));
|
|
515
|
+
if (opened.ok === false) {
|
|
516
|
+
if (opened.reason === 'not-configured') return notConfigured();
|
|
517
|
+
await markOutreachMailboxReconnectRequired(firestore, {
|
|
518
|
+
orgId: gate.orgId,
|
|
519
|
+
mailboxId: mailbox.id,
|
|
520
|
+
errorCode: opened.reason,
|
|
521
|
+
nowMs
|
|
522
|
+
});
|
|
523
|
+
return refusal(409, 'reconnect-required', 'This mailbox needs to be connected again before it can send.');
|
|
524
|
+
}
|
|
525
|
+
try {
|
|
526
|
+
const sent = await sendOutreachMessage(opened.client, {
|
|
527
|
+
from: {
|
|
528
|
+
address: mailbox.sendAs,
|
|
529
|
+
name: mailbox.displayName
|
|
530
|
+
},
|
|
531
|
+
to: mailbox.email,
|
|
532
|
+
subject: 'Sequences test message',
|
|
533
|
+
text: [
|
|
534
|
+
'This is a test message from Sequences.',
|
|
535
|
+
'',
|
|
536
|
+
`It was sent through your connected mailbox as ${mailbox.sendAs}. ` + 'If it reached your inbox, this mailbox can send.'
|
|
537
|
+
].join('\n')
|
|
538
|
+
});
|
|
539
|
+
return ok({
|
|
540
|
+
ok: true,
|
|
541
|
+
sentTo: mailbox.email,
|
|
542
|
+
gmailMessageId: sent.gmailMessageId,
|
|
543
|
+
sentAtMs: nowMs
|
|
544
|
+
});
|
|
545
|
+
} catch (error) {
|
|
546
|
+
if (error instanceof GmailTransportError && error.reconnectRequired) {
|
|
547
|
+
await markOutreachMailboxReconnectRequired(firestore, {
|
|
548
|
+
orgId: gate.orgId,
|
|
549
|
+
mailboxId: mailbox.id,
|
|
550
|
+
errorCode: error.code,
|
|
551
|
+
nowMs
|
|
552
|
+
});
|
|
553
|
+
return refusal(409, 'reconnect-required', 'Google refused this mailbox’s access. Connect it again.');
|
|
554
|
+
}
|
|
555
|
+
if (error instanceof Rfc5322MessageError) {
|
|
556
|
+
return refusal(400, 'invalid-settings', `The test could not be written: ${error.message}`);
|
|
557
|
+
}
|
|
558
|
+
return googleUnavailable(error);
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
const disconnect = async (request)=>{
|
|
562
|
+
if (request.method !== 'POST') return methodNotAllowed('POST');
|
|
563
|
+
const body = await readBody(request);
|
|
564
|
+
const loaded = await loadManagedMailbox(request, body, 'member-or-admin');
|
|
565
|
+
if (loaded instanceof Response) return loaded;
|
|
566
|
+
const { gate, mailbox } = loaded;
|
|
567
|
+
const firestore = deps.firestore();
|
|
568
|
+
const credentialDoc = mailboxCredentialsRef(firestore, mailbox.id);
|
|
569
|
+
const snapshot = await credentialDoc.get();
|
|
570
|
+
const credential = readMailboxCredentials(snapshot.exists ? snapshot.data() : null);
|
|
571
|
+
const revocation = credential ? await revokeMailboxGrant(firestore, credential, deps) : 'already-invalid';
|
|
572
|
+
const batch = firestore.batch();
|
|
573
|
+
batch.delete(credentialDoc);
|
|
574
|
+
batch.delete(mailboxRef(firestore, gate.orgId, mailbox.id));
|
|
575
|
+
await batch.commit();
|
|
576
|
+
await deps.logOrgActivity(gate.orgId, {
|
|
577
|
+
uid: gate.uid,
|
|
578
|
+
email: gate.email
|
|
579
|
+
}, 'Disconnected a mailbox in Sequences', {
|
|
580
|
+
type: OUTREACH_MAILBOX_ACTIVITY_TARGET,
|
|
581
|
+
id: mailbox.id,
|
|
582
|
+
name: activityName(mailbox)
|
|
583
|
+
});
|
|
584
|
+
return ok({
|
|
585
|
+
ok: true,
|
|
586
|
+
revocation
|
|
587
|
+
});
|
|
588
|
+
};
|
|
589
|
+
return {
|
|
590
|
+
availability,
|
|
591
|
+
connect,
|
|
592
|
+
oauthCallback,
|
|
593
|
+
connectComplete,
|
|
594
|
+
settings,
|
|
595
|
+
status,
|
|
596
|
+
test,
|
|
597
|
+
disconnect
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
function googleUnavailable(error) {
|
|
601
|
+
if (!(error instanceof GmailTransportError)) throw error;
|
|
602
|
+
return refusal(502, 'google-unavailable', error.retryable ? 'Google did not answer. Try again in a moment.' : `Google refused the request: ${error.message}`);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
//# sourceMappingURL=mailbox-routes.js.map
|