@aglyn/plugins-email 1.0.0-beta.221 → 1.0.0-beta.222
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/README.md +5 -4
- package/package.json +16 -17
- package/src/lib/components/email-template-detail.d.ts +5 -13
- package/src/lib/components/email-template-detail.js +29 -426
- package/src/lib/components/email-template-detail.js.map +1 -1
- package/src/lib/components/email-zones.d.ts +15 -0
- package/src/lib/components/email-zones.js +1 -0
- package/src/lib/components/email-zones.js.map +1 -1
- package/src/lib/components/list-detail-card.d.ts +0 -36
- package/src/lib/components/list-detail-card.js +125 -84
- package/src/lib/components/list-detail-card.js.map +1 -1
- package/src/lib/components/list-members-panel.d.ts +17 -0
- package/src/lib/components/list-members-panel.js +14 -0
- package/src/lib/components/list-members-panel.js.map +1 -1
- package/src/lib/components/site-package-email-preview.d.ts +13 -0
- package/src/lib/components/site-package-email-preview.js +47 -0
- package/src/lib/components/site-package-email-preview.js.map +1 -0
- package/src/lib/components/suppressions-card.js +75 -6
- package/src/lib/components/suppressions-card.js.map +1 -1
- package/src/lib/components/use-org-email-topics.js +2 -9
- package/src/lib/components/use-org-email-topics.js.map +1 -1
- package/src/lib/constants/site-package.d.ts +21 -0
- package/src/lib/constants/site-package.js +21 -0
- package/src/lib/constants/site-package.js.map +1 -0
- package/src/lib/declarations.console-server.d.ts +12 -0
- package/src/lib/declarations.console-server.js +73 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/list-import.d.ts +64 -75
- package/src/lib/list-import.js +43 -158
- package/src/lib/list-import.js.map +1 -1
- package/src/lib/model/email-topic-catalog.d.ts +24 -0
- package/src/lib/model/email-topic-catalog.js +19 -0
- package/src/lib/model/email-topic-catalog.js.map +1 -1
- package/src/lib/model/index.d.ts +0 -25
- package/src/lib/model/index.js +0 -27
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/plugin.d.ts +8 -0
- package/src/lib/plugin.js +76 -2
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/person-eraser.d.ts +39 -0
- package/src/lib/server/person-eraser.js +44 -0
- package/src/lib/server/person-eraser.js.map +1 -0
- package/src/lib/server-console.js +9 -9
- package/src/lib/server-console.js.map +1 -1
- package/src/lib/server-list-gate.d.ts +7 -0
- package/src/lib/server-list-gate.js +10 -3
- package/src/lib/server-list-gate.js.map +1 -1
- package/src/lib/server-list-import.d.ts +20 -177
- package/src/lib/server-list-import.js +34 -594
- package/src/lib/server-list-import.js.map +1 -1
- package/src/lib/server.js +16 -29
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/stand-in-send-tally.d.ts +29 -0
- package/src/lib/testing/stand-in-send-tally.js +52 -0
- package/src/lib/testing/stand-in-send-tally.js.map +1 -0
- package/src/lib/transfer/email-transfer-catalog.d.ts +151 -0
- package/src/lib/transfer/email-transfer-catalog.js +536 -0
- package/src/lib/transfer/email-transfer-catalog.js.map +1 -0
- package/src/lib/transfer/list-import-steps.d.ts +44 -0
- package/src/lib/transfer/list-import-steps.js +201 -0
- package/src/lib/transfer/list-import-steps.js.map +1 -0
- package/src/lib/transfer/list-members.server.d.ts +165 -0
- package/src/lib/transfer/list-members.server.js +911 -0
- package/src/lib/transfer/list-members.server.js.map +1 -0
- package/src/lib/transfer/suppressions.server.d.ts +69 -0
- package/src/lib/transfer/suppressions.server.js +282 -0
- package/src/lib/transfer/suppressions.server.js.map +1 -0
- package/src/lib/transfer/topics-package.d.ts +56 -0
- package/src/lib/transfer/topics-package.js +87 -0
- package/src/lib/transfer/topics-package.js.map +1 -0
- package/src/lib/transfer/topics-package.server.d.ts +33 -0
- package/src/lib/transfer/topics-package.server.js +171 -0
- package/src/lib/transfer/topics-package.server.js.map +1 -0
- package/src/lib/transfer/transfer-reads.server.d.ts +25 -0
- package/src/lib/transfer/transfer-reads.server.js +34 -0
- package/src/lib/transfer/transfer-reads.server.js.map +1 -0
- package/src/lib/components/list-import-drawer.d.ts +0 -22
- package/src/lib/components/list-import-drawer.js +0 -662
- package/src/lib/components/list-import-drawer.js.map +0 -1
- package/src/lib/model/template-report.d.ts +0 -158
- package/src/lib/model/template-report.js +0 -249
- package/src/lib/model/template-report.js.map +0 -1
|
@@ -0,0 +1,151 @@
|
|
|
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
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* WHAT THE EMAIL PLUGIN IMPORTS AND EXPORTS (AGL-3529) — the half both the
|
|
19
|
+
* server and the console read: the resource keys, every field, the match
|
|
20
|
+
* keys, the header spellings, the locked rules and the answers of the
|
|
21
|
+
* plugin's own wizard steps.
|
|
22
|
+
*
|
|
23
|
+
* Two resources, both a site's:
|
|
24
|
+
*
|
|
25
|
+
* - `email.list-members` — one list's members, one list at a time
|
|
26
|
+
* (`instances`: `email.list-members:<listId>`): an import is INTO one list
|
|
27
|
+
* and an export is OF one, and the console opens both from the list's page.
|
|
28
|
+
* - `email.suppressions` — the site's own suppression list. An import only
|
|
29
|
+
* ever ADDS: an address already on the list is left exactly as it is, and
|
|
30
|
+
* nothing — the import, its undo — ever removes one.
|
|
31
|
+
*
|
|
32
|
+
* Types only from the transfer core, so the console registrar that reads
|
|
33
|
+
* this costs no transfer code.
|
|
34
|
+
*/
|
|
35
|
+
import type { MatchKeySpec, TransferAliasDictionary, TransferCatalogInput, TransferField, TransferLockedRule } from '@aglyn/aglyn/data-transfer';
|
|
36
|
+
/**
|
|
37
|
+
* One list's members, imported into and exported from that list: declared
|
|
38
|
+
* with `instances`, and reached per list as `email.list-members:<listId>`, so
|
|
39
|
+
* a job, a person's remembered export fields and the one-running-import rule
|
|
40
|
+
* each belong to one list.
|
|
41
|
+
*/
|
|
42
|
+
export declare const LIST_MEMBERS_RESOURCE = "email.list-members";
|
|
43
|
+
/**
|
|
44
|
+
* The resource key one list's members move under — the core's instance key
|
|
45
|
+
* (`transferResourceInstanceKey`), spelled out so the list's page names it
|
|
46
|
+
* without loading the transfer core; `email-transfer-catalog.spec.ts` holds
|
|
47
|
+
* the two to the same answer.
|
|
48
|
+
*/
|
|
49
|
+
export declare function listMembersResourceKey(listId: string): string;
|
|
50
|
+
/** The list a list-member resource key names, or `null` for any other key. */
|
|
51
|
+
export declare function listIdOfResourceKey(key: string): string | null;
|
|
52
|
+
/** A site's suppression list. */
|
|
53
|
+
export declare const SUPPRESSIONS_RESOURCE = "email.suppressions";
|
|
54
|
+
/**
|
|
55
|
+
* "People already on this list" — after Matching: whether the file may
|
|
56
|
+
* change the contact details of people the workspace already holds.
|
|
57
|
+
*/
|
|
58
|
+
export declare const LIST_EXISTING_STEP_ID = "email-existing";
|
|
59
|
+
/** "Permission" — after Conflicts: the statement of permission, with the screening. */
|
|
60
|
+
export declare const LIST_CONSENT_STEP_ID = "email-consent";
|
|
61
|
+
/** The answer of the {@link LIST_EXISTING_STEP_ID} step. */
|
|
62
|
+
export interface ListExistingAnswer {
|
|
63
|
+
/**
|
|
64
|
+
* The file's contact columns may change the details of a person the
|
|
65
|
+
* workspace already holds, under the field choices made in Conflicts.
|
|
66
|
+
* Otherwise they fill only a contact the import creates, and somebody
|
|
67
|
+
* already on the list is left exactly as they are.
|
|
68
|
+
*/
|
|
69
|
+
updateContacts: boolean;
|
|
70
|
+
}
|
|
71
|
+
/** The answer of the {@link LIST_CONSENT_STEP_ID} step. */
|
|
72
|
+
export interface ListConsentAnswer {
|
|
73
|
+
/**
|
|
74
|
+
* The operator states they have permission to email everyone in the file.
|
|
75
|
+
* Without it only people with an opt-in on record are added.
|
|
76
|
+
*/
|
|
77
|
+
attest: boolean;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The existing-people answer in `extras`, or the default — leave everybody
|
|
81
|
+
* the workspace holds as they are. Anything but a literal `true` is no.
|
|
82
|
+
*/
|
|
83
|
+
export declare function readListExistingAnswer(extras: Readonly<Record<string, unknown>> | undefined): ListExistingAnswer;
|
|
84
|
+
/**
|
|
85
|
+
* The permission answer in `extras`, or `null` when the step was never
|
|
86
|
+
* answered. Anything but a literal `true` is no statement at all.
|
|
87
|
+
*/
|
|
88
|
+
export declare function readListConsentAnswer(extras: Readonly<Record<string, unknown>> | undefined): ListConsentAnswer | null;
|
|
89
|
+
/** What the screening found, as the list's import ledger keeps it and the permission step shows it. */
|
|
90
|
+
export interface ListImportScreeningReport {
|
|
91
|
+
roleAccounts: number;
|
|
92
|
+
roleAccountSamples: string[];
|
|
93
|
+
purchaseTellColumns: string[];
|
|
94
|
+
declaresBasis: boolean;
|
|
95
|
+
/** The consent gate over the first new addresses — a sample, with its size. */
|
|
96
|
+
sample: {
|
|
97
|
+
size: number;
|
|
98
|
+
of: number;
|
|
99
|
+
optedIn: number;
|
|
100
|
+
needAttestation: number;
|
|
101
|
+
refused: number;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** A contact-record column's field id: `contact:<the record's own field name>`. */
|
|
105
|
+
export declare const CONTACT_FIELD_PREFIX = "contact:";
|
|
106
|
+
/**
|
|
107
|
+
* The contact record's fields a list file may carry, by the record's own
|
|
108
|
+
* names — what the contact-capture door's `profile` takes. Written through
|
|
109
|
+
* that door and never by this plugin (see `list-members.server.ts`).
|
|
110
|
+
*/
|
|
111
|
+
export declare const LIST_MEMBER_CONTACT_FIELDS: ReadonlyArray<{
|
|
112
|
+
key: string;
|
|
113
|
+
label: string;
|
|
114
|
+
type: TransferField['type'];
|
|
115
|
+
aliases: readonly string[];
|
|
116
|
+
}>;
|
|
117
|
+
/** The field id of a contact-record column. */
|
|
118
|
+
export declare function contactFieldId(key: string): string;
|
|
119
|
+
/** The contact record's own name for a field id, or `null` for a membership field. */
|
|
120
|
+
export declare function contactFieldKey(fieldId: string): string | null;
|
|
121
|
+
/** Every contact-record field id, in catalog order. */
|
|
122
|
+
export declare const LIST_MEMBER_CONTACT_FIELD_IDS: readonly string[];
|
|
123
|
+
/** The membership fields a file may write on a NEW member. */
|
|
124
|
+
export declare const LIST_MEMBER_WRITTEN_FIELDS: readonly ["email", "name", "declaredSource", "declaredAt"];
|
|
125
|
+
/** One list's members: every field, for the wizard and the export dialog. */
|
|
126
|
+
export declare function listMemberCatalog(): TransferCatalogInput;
|
|
127
|
+
/** A row finds its member by address, or by the member id an export carries. */
|
|
128
|
+
export declare const LIST_MEMBER_MATCH_KEYS: readonly MatchKeySpec[];
|
|
129
|
+
/**
|
|
130
|
+
* Other products' list-export headers. Spellings only — every one of them
|
|
131
|
+
* is read the same way as the platform's own.
|
|
132
|
+
*/
|
|
133
|
+
export declare const LIST_MEMBER_ALIASES: readonly TransferAliasDictionary[];
|
|
134
|
+
/** Why somebody already on the list keeps their membership as it is. */
|
|
135
|
+
export declare const MEMBERSHIP_KEPT_REASON = "Somebody already on this list keeps their membership as it is: an import never re-enrolls them or rewrites what their membership records.";
|
|
136
|
+
/** Why a contact detail is never emptied by a file. */
|
|
137
|
+
export declare const CONTACT_NEVER_CLEARED_REASON = "An import adds and replaces contact details; a blank cell never empties one.";
|
|
138
|
+
/** Why the file holds no say over consent. */
|
|
139
|
+
export declare const CONSENT_FROM_FILE_REASON = "A file never says who agreed to hear from you. People with an opt-in on record keep it; anybody else is added only on your statement of permission, which is stored against your account; people who unsubscribed, bounced, complained or said no are never added.";
|
|
140
|
+
/** The list-member import's rules, shown locked in the wizard with their reasons. */
|
|
141
|
+
export declare const LIST_MEMBER_LOCKED_RULES: readonly TransferLockedRule[];
|
|
142
|
+
/** Why an entry already on the list is left alone. */
|
|
143
|
+
export declare const SUPPRESSION_KEPT_REASON = "An address already suppressed keeps its entry exactly as it is \u2014 the reason and date it was suppressed with are the record of why \u2014 and an import never removes one.";
|
|
144
|
+
/** A site's suppression list: every field. */
|
|
145
|
+
export declare function suppressionCatalog(): TransferCatalogInput;
|
|
146
|
+
/** A suppression is the address. */
|
|
147
|
+
export declare const SUPPRESSION_MATCH_KEYS: readonly MatchKeySpec[];
|
|
148
|
+
/** The suppression import's rule: nothing already on the list is changed. */
|
|
149
|
+
export declare const SUPPRESSION_LOCKED_RULES: readonly TransferLockedRule[];
|
|
150
|
+
/** Other products' suppression-export headers. */
|
|
151
|
+
export declare const SUPPRESSION_ALIASES: readonly TransferAliasDictionary[];
|
|
@@ -0,0 +1,536 @@
|
|
|
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
|
+
*/ /**
|
|
17
|
+
* WHAT THE EMAIL PLUGIN IMPORTS AND EXPORTS (AGL-3529) — the half both the
|
|
18
|
+
* server and the console read: the resource keys, every field, the match
|
|
19
|
+
* keys, the header spellings, the locked rules and the answers of the
|
|
20
|
+
* plugin's own wizard steps.
|
|
21
|
+
*
|
|
22
|
+
* Two resources, both a site's:
|
|
23
|
+
*
|
|
24
|
+
* - `email.list-members` — one list's members, one list at a time
|
|
25
|
+
* (`instances`: `email.list-members:<listId>`): an import is INTO one list
|
|
26
|
+
* and an export is OF one, and the console opens both from the list's page.
|
|
27
|
+
* - `email.suppressions` — the site's own suppression list. An import only
|
|
28
|
+
* ever ADDS: an address already on the list is left exactly as it is, and
|
|
29
|
+
* nothing — the import, its undo — ever removes one.
|
|
30
|
+
*
|
|
31
|
+
* Types only from the transfer core, so the console registrar that reads
|
|
32
|
+
* this costs no transfer code.
|
|
33
|
+
*/ import { LIST_IMPORT_EMAIL_COLUMNS, LIST_IMPORT_NAME_COLUMNS, LIST_IMPORT_OPT_IN_DATE_COLUMNS, LIST_IMPORT_OPT_IN_SOURCE_COLUMNS } from "../list-import.js";
|
|
34
|
+
/**
|
|
35
|
+
* One list's members, imported into and exported from that list: declared
|
|
36
|
+
* with `instances`, and reached per list as `email.list-members:<listId>`, so
|
|
37
|
+
* a job, a person's remembered export fields and the one-running-import rule
|
|
38
|
+
* each belong to one list.
|
|
39
|
+
*/ export const LIST_MEMBERS_RESOURCE = 'email.list-members';
|
|
40
|
+
/**
|
|
41
|
+
* The resource key one list's members move under — the core's instance key
|
|
42
|
+
* (`transferResourceInstanceKey`), spelled out so the list's page names it
|
|
43
|
+
* without loading the transfer core; `email-transfer-catalog.spec.ts` holds
|
|
44
|
+
* the two to the same answer.
|
|
45
|
+
*/ export function listMembersResourceKey(listId) {
|
|
46
|
+
return `${LIST_MEMBERS_RESOURCE}:${listId}`;
|
|
47
|
+
}
|
|
48
|
+
/** The list a list-member resource key names, or `null` for any other key. */ export function listIdOfResourceKey(key) {
|
|
49
|
+
const prefix = `${LIST_MEMBERS_RESOURCE}:`;
|
|
50
|
+
const listId = String(key != null ? key : '').startsWith(prefix) ? String(key).slice(prefix.length) : '';
|
|
51
|
+
return /^[A-Za-z0-9_-]{1,128}$/.test(listId) ? listId : null;
|
|
52
|
+
}
|
|
53
|
+
/** A site's suppression list. */ export const SUPPRESSIONS_RESOURCE = 'email.suppressions';
|
|
54
|
+
/*==========================================
|
|
55
|
+
* THE PLUGIN'S OWN WIZARD STEPS
|
|
56
|
+
*=========================================*/ /**
|
|
57
|
+
* "People already on this list" — after Matching: whether the file may
|
|
58
|
+
* change the contact details of people the workspace already holds.
|
|
59
|
+
*/ export const LIST_EXISTING_STEP_ID = 'email-existing';
|
|
60
|
+
/** "Permission" — after Conflicts: the statement of permission, with the screening. */ export const LIST_CONSENT_STEP_ID = 'email-consent';
|
|
61
|
+
/**
|
|
62
|
+
* The existing-people answer in `extras`, or the default — leave everybody
|
|
63
|
+
* the workspace holds as they are. Anything but a literal `true` is no.
|
|
64
|
+
*/ export function readListExistingAnswer(extras) {
|
|
65
|
+
const value = extras == null ? void 0 : extras[LIST_EXISTING_STEP_ID];
|
|
66
|
+
return {
|
|
67
|
+
updateContacts: !!value && typeof value === 'object' && value['updateContacts'] === true
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The permission answer in `extras`, or `null` when the step was never
|
|
72
|
+
* answered. Anything but a literal `true` is no statement at all.
|
|
73
|
+
*/ export function readListConsentAnswer(extras) {
|
|
74
|
+
const value = extras == null ? void 0 : extras[LIST_CONSENT_STEP_ID];
|
|
75
|
+
if (!value || typeof value !== 'object') return null;
|
|
76
|
+
const attest = value['attest'];
|
|
77
|
+
return typeof attest === 'boolean' ? {
|
|
78
|
+
attest
|
|
79
|
+
} : null;
|
|
80
|
+
}
|
|
81
|
+
/*==========================================
|
|
82
|
+
* LIST MEMBERS
|
|
83
|
+
*=========================================*/ /** A contact-record column's field id: `contact:<the record's own field name>`. */ export const CONTACT_FIELD_PREFIX = 'contact:';
|
|
84
|
+
/**
|
|
85
|
+
* The contact record's fields a list file may carry, by the record's own
|
|
86
|
+
* names — what the contact-capture door's `profile` takes. Written through
|
|
87
|
+
* that door and never by this plugin (see `list-members.server.ts`).
|
|
88
|
+
*/ export const LIST_MEMBER_CONTACT_FIELDS = [
|
|
89
|
+
{
|
|
90
|
+
key: 'firstName',
|
|
91
|
+
label: 'First name',
|
|
92
|
+
type: 'text',
|
|
93
|
+
aliases: [
|
|
94
|
+
'first name',
|
|
95
|
+
'firstname',
|
|
96
|
+
'given name',
|
|
97
|
+
'fname',
|
|
98
|
+
'FNAME'
|
|
99
|
+
]
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
key: 'lastName',
|
|
103
|
+
label: 'Last name',
|
|
104
|
+
type: 'text',
|
|
105
|
+
aliases: [
|
|
106
|
+
'last name',
|
|
107
|
+
'lastname',
|
|
108
|
+
'surname',
|
|
109
|
+
'family name',
|
|
110
|
+
'lname',
|
|
111
|
+
'LNAME'
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
key: 'phone',
|
|
116
|
+
label: 'Phone',
|
|
117
|
+
type: 'phone',
|
|
118
|
+
aliases: [
|
|
119
|
+
'phone',
|
|
120
|
+
'phone number',
|
|
121
|
+
'telephone',
|
|
122
|
+
'PHONE'
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
key: 'mobilePhone',
|
|
127
|
+
label: 'Mobile phone',
|
|
128
|
+
type: 'phone',
|
|
129
|
+
aliases: [
|
|
130
|
+
'mobile',
|
|
131
|
+
'mobile phone',
|
|
132
|
+
'cell',
|
|
133
|
+
'cell phone'
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
key: 'jobTitle',
|
|
138
|
+
label: 'Job title',
|
|
139
|
+
type: 'text',
|
|
140
|
+
aliases: [
|
|
141
|
+
'title',
|
|
142
|
+
'job title',
|
|
143
|
+
'position',
|
|
144
|
+
'role'
|
|
145
|
+
]
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
key: 'companyName',
|
|
149
|
+
label: 'Company',
|
|
150
|
+
type: 'text',
|
|
151
|
+
aliases: [
|
|
152
|
+
'company',
|
|
153
|
+
'company name',
|
|
154
|
+
'organization',
|
|
155
|
+
'employer'
|
|
156
|
+
]
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
key: 'department',
|
|
160
|
+
label: 'Department',
|
|
161
|
+
type: 'text',
|
|
162
|
+
aliases: [
|
|
163
|
+
'department',
|
|
164
|
+
'team'
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
];
|
|
168
|
+
/** The field id of a contact-record column. */ export function contactFieldId(key) {
|
|
169
|
+
return `${CONTACT_FIELD_PREFIX}${key}`;
|
|
170
|
+
}
|
|
171
|
+
/** The contact record's own name for a field id, or `null` for a membership field. */ export function contactFieldKey(fieldId) {
|
|
172
|
+
return fieldId.startsWith(CONTACT_FIELD_PREFIX) ? fieldId.slice(CONTACT_FIELD_PREFIX.length) : null;
|
|
173
|
+
}
|
|
174
|
+
/** Every contact-record field id, in catalog order. */ export const LIST_MEMBER_CONTACT_FIELD_IDS = LIST_MEMBER_CONTACT_FIELDS.map((field)=>contactFieldId(field.key));
|
|
175
|
+
/** The membership fields a file may write on a NEW member. */ export const LIST_MEMBER_WRITTEN_FIELDS = [
|
|
176
|
+
'email',
|
|
177
|
+
'name',
|
|
178
|
+
'declaredSource',
|
|
179
|
+
'declaredAt'
|
|
180
|
+
];
|
|
181
|
+
/** The groups, in picker order. */ const LIST_MEMBER_GROUPS = [
|
|
182
|
+
{
|
|
183
|
+
id: 'membership',
|
|
184
|
+
label: 'Membership'
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: 'evidence',
|
|
188
|
+
label: 'Where the address came from'
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: 'consent',
|
|
192
|
+
label: 'Consent'
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: 'contact',
|
|
196
|
+
label: 'Contact record'
|
|
197
|
+
}
|
|
198
|
+
];
|
|
199
|
+
/** One list's members: every field, for the wizard and the export dialog. */ export function listMemberCatalog() {
|
|
200
|
+
return {
|
|
201
|
+
groups: LIST_MEMBER_GROUPS,
|
|
202
|
+
standard: [
|
|
203
|
+
{
|
|
204
|
+
id: 'email',
|
|
205
|
+
label: 'Email',
|
|
206
|
+
group: 'membership',
|
|
207
|
+
type: 'email',
|
|
208
|
+
required: true,
|
|
209
|
+
matchKey: true,
|
|
210
|
+
aliases: LIST_IMPORT_EMAIL_COLUMNS,
|
|
211
|
+
description: 'Who the member is. A person is on a list once, by address.'
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: 'name',
|
|
215
|
+
label: 'Name',
|
|
216
|
+
group: 'membership',
|
|
217
|
+
type: 'text',
|
|
218
|
+
maxLength: 120,
|
|
219
|
+
aliases: LIST_IMPORT_NAME_COLUMNS,
|
|
220
|
+
description: 'The name the list shows. Composed from the first and last name when only those are mapped.'
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: 'declaredSource',
|
|
224
|
+
label: 'Opt-in source',
|
|
225
|
+
group: 'evidence',
|
|
226
|
+
type: 'text',
|
|
227
|
+
maxLength: 200,
|
|
228
|
+
aliases: LIST_IMPORT_OPT_IN_SOURCE_COLUMNS,
|
|
229
|
+
description: 'Where your file says the person signed up. Kept with your statement of permission, never as their own opt-in.'
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: 'declaredAt',
|
|
233
|
+
label: 'Opt-in date',
|
|
234
|
+
group: 'evidence',
|
|
235
|
+
type: 'text',
|
|
236
|
+
maxLength: 60,
|
|
237
|
+
aliases: LIST_IMPORT_OPT_IN_DATE_COLUMNS,
|
|
238
|
+
description: 'When your file says the person signed up, kept as written.'
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: 'addedAt',
|
|
242
|
+
label: 'Added',
|
|
243
|
+
group: 'membership',
|
|
244
|
+
type: 'datetime',
|
|
245
|
+
readOnly: true
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
id: 'source',
|
|
249
|
+
label: 'Added through',
|
|
250
|
+
group: 'membership',
|
|
251
|
+
type: 'text',
|
|
252
|
+
readOnly: true
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
id: 'via',
|
|
256
|
+
label: 'How',
|
|
257
|
+
group: 'membership',
|
|
258
|
+
type: 'text',
|
|
259
|
+
readOnly: true,
|
|
260
|
+
description: '`rule` for a live list’s match, `manual` for everyone added another way.'
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
id: 'consent',
|
|
264
|
+
label: 'Consent',
|
|
265
|
+
group: 'consent',
|
|
266
|
+
type: 'text',
|
|
267
|
+
readOnly: true,
|
|
268
|
+
description: '`granted`, `declined` or `unrecorded`, as the site the list is worked as reads it.'
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: 'consentBasis',
|
|
272
|
+
label: 'Consent basis',
|
|
273
|
+
group: 'consent',
|
|
274
|
+
type: 'text',
|
|
275
|
+
readOnly: true,
|
|
276
|
+
description: '`contact-opt-in` when the person opted in, `operator-attested` when a member of your team stated permission.'
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
id: 'consentAt',
|
|
280
|
+
label: 'Consent recorded',
|
|
281
|
+
group: 'consent',
|
|
282
|
+
type: 'datetime',
|
|
283
|
+
readOnly: true
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
id: 'consentReason',
|
|
287
|
+
label: 'Consent note',
|
|
288
|
+
group: 'consent',
|
|
289
|
+
type: 'longText',
|
|
290
|
+
readOnly: true
|
|
291
|
+
},
|
|
292
|
+
...LIST_MEMBER_CONTACT_FIELDS.map((field)=>({
|
|
293
|
+
id: contactFieldId(field.key),
|
|
294
|
+
label: field.label,
|
|
295
|
+
group: 'contact',
|
|
296
|
+
type: field.type,
|
|
297
|
+
aliases: field.aliases,
|
|
298
|
+
maxLength: 120
|
|
299
|
+
}))
|
|
300
|
+
]
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
/** A row finds its member by address, or by the member id an export carries. */ export const LIST_MEMBER_MATCH_KEYS = [
|
|
304
|
+
{
|
|
305
|
+
fieldId: 'email',
|
|
306
|
+
normalizer: 'email'
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
fieldId: 'id',
|
|
310
|
+
normalizer: 'aglynId'
|
|
311
|
+
}
|
|
312
|
+
];
|
|
313
|
+
/**
|
|
314
|
+
* Other products' list-export headers. Spellings only — every one of them
|
|
315
|
+
* is read the same way as the platform's own.
|
|
316
|
+
*/ export const LIST_MEMBER_ALIASES = [
|
|
317
|
+
{
|
|
318
|
+
source: 'Mailchimp audience export',
|
|
319
|
+
aliases: {
|
|
320
|
+
email: [
|
|
321
|
+
'Email Address'
|
|
322
|
+
],
|
|
323
|
+
[contactFieldId('firstName')]: [
|
|
324
|
+
'First Name'
|
|
325
|
+
],
|
|
326
|
+
[contactFieldId('lastName')]: [
|
|
327
|
+
'Last Name'
|
|
328
|
+
],
|
|
329
|
+
[contactFieldId('phone')]: [
|
|
330
|
+
'Phone Number'
|
|
331
|
+
],
|
|
332
|
+
declaredAt: [
|
|
333
|
+
'OPTIN_TIME',
|
|
334
|
+
'CONFIRM_TIME'
|
|
335
|
+
]
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
source: 'Klaviyo list export',
|
|
340
|
+
aliases: {
|
|
341
|
+
email: [
|
|
342
|
+
'Email'
|
|
343
|
+
],
|
|
344
|
+
[contactFieldId('firstName')]: [
|
|
345
|
+
'First Name'
|
|
346
|
+
],
|
|
347
|
+
[contactFieldId('lastName')]: [
|
|
348
|
+
'Last Name'
|
|
349
|
+
],
|
|
350
|
+
[contactFieldId('phone')]: [
|
|
351
|
+
'Phone Number'
|
|
352
|
+
],
|
|
353
|
+
[contactFieldId('companyName')]: [
|
|
354
|
+
'Organization'
|
|
355
|
+
],
|
|
356
|
+
[contactFieldId('jobTitle')]: [
|
|
357
|
+
'Title'
|
|
358
|
+
],
|
|
359
|
+
declaredSource: [
|
|
360
|
+
'Source',
|
|
361
|
+
'List Source'
|
|
362
|
+
],
|
|
363
|
+
declaredAt: [
|
|
364
|
+
'Date Added',
|
|
365
|
+
'Consent Timestamp'
|
|
366
|
+
]
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
source: 'Constant Contact export',
|
|
371
|
+
aliases: {
|
|
372
|
+
email: [
|
|
373
|
+
'Email address'
|
|
374
|
+
],
|
|
375
|
+
[contactFieldId('firstName')]: [
|
|
376
|
+
'First name'
|
|
377
|
+
],
|
|
378
|
+
[contactFieldId('lastName')]: [
|
|
379
|
+
'Last name'
|
|
380
|
+
],
|
|
381
|
+
[contactFieldId('companyName')]: [
|
|
382
|
+
'Company name'
|
|
383
|
+
],
|
|
384
|
+
[contactFieldId('jobTitle')]: [
|
|
385
|
+
'Job title'
|
|
386
|
+
],
|
|
387
|
+
[contactFieldId('mobilePhone')]: [
|
|
388
|
+
'Phone - mobile'
|
|
389
|
+
],
|
|
390
|
+
declaredSource: [
|
|
391
|
+
'Source name'
|
|
392
|
+
],
|
|
393
|
+
declaredAt: [
|
|
394
|
+
'Email permission date'
|
|
395
|
+
]
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
];
|
|
399
|
+
/** Why somebody already on the list keeps their membership as it is. */ export const MEMBERSHIP_KEPT_REASON = 'Somebody already on this list keeps their membership as it is: an import never re-enrolls them or rewrites what their membership records.';
|
|
400
|
+
/** Why a contact detail is never emptied by a file. */ export const CONTACT_NEVER_CLEARED_REASON = 'An import adds and replaces contact details; a blank cell never empties one.';
|
|
401
|
+
/** Why the file holds no say over consent. */ export const CONSENT_FROM_FILE_REASON = 'A file never says who agreed to hear from you. People with an opt-in on record keep it; anybody else is added only on your statement of permission, which is stored against your account; people who unsubscribed, bounced, complained or said no are never added.';
|
|
402
|
+
/** The list-member import's rules, shown locked in the wizard with their reasons. */ export const LIST_MEMBER_LOCKED_RULES = [
|
|
403
|
+
{
|
|
404
|
+
fieldId: 'email',
|
|
405
|
+
forced: {
|
|
406
|
+
mode: 'keepExisting'
|
|
407
|
+
},
|
|
408
|
+
reason: 'A member is their address: a row matched by its ID never moves the member to another one.'
|
|
409
|
+
},
|
|
410
|
+
...[
|
|
411
|
+
'name',
|
|
412
|
+
'declaredSource',
|
|
413
|
+
'declaredAt'
|
|
414
|
+
].map((fieldId)=>({
|
|
415
|
+
fieldId,
|
|
416
|
+
forced: {
|
|
417
|
+
mode: 'keepExisting'
|
|
418
|
+
},
|
|
419
|
+
reason: MEMBERSHIP_KEPT_REASON
|
|
420
|
+
})),
|
|
421
|
+
{
|
|
422
|
+
fieldId: 'consent',
|
|
423
|
+
refuseValues: true,
|
|
424
|
+
reason: CONSENT_FROM_FILE_REASON
|
|
425
|
+
},
|
|
426
|
+
...LIST_MEMBER_CONTACT_FIELD_IDS.map((fieldId)=>({
|
|
427
|
+
fieldId,
|
|
428
|
+
forced: {
|
|
429
|
+
blank: 'leave'
|
|
430
|
+
},
|
|
431
|
+
reason: CONTACT_NEVER_CLEARED_REASON
|
|
432
|
+
}))
|
|
433
|
+
];
|
|
434
|
+
/*==========================================
|
|
435
|
+
* SUPPRESSIONS
|
|
436
|
+
*=========================================*/ /** Why an entry already on the list is left alone. */ export const SUPPRESSION_KEPT_REASON = 'An address already suppressed keeps its entry exactly as it is — the reason and date it was suppressed with are the record of why — and an import never removes one.';
|
|
437
|
+
/** A site's suppression list: every field. */ export function suppressionCatalog() {
|
|
438
|
+
return {
|
|
439
|
+
groups: [
|
|
440
|
+
{
|
|
441
|
+
id: 'suppression',
|
|
442
|
+
label: 'Suppression'
|
|
443
|
+
}
|
|
444
|
+
],
|
|
445
|
+
standard: [
|
|
446
|
+
{
|
|
447
|
+
id: 'email',
|
|
448
|
+
label: 'Email',
|
|
449
|
+
group: 'suppression',
|
|
450
|
+
type: 'email',
|
|
451
|
+
required: true,
|
|
452
|
+
matchKey: true,
|
|
453
|
+
aliases: LIST_IMPORT_EMAIL_COLUMNS
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
id: 'note',
|
|
457
|
+
label: 'Note',
|
|
458
|
+
group: 'suppression',
|
|
459
|
+
type: 'text',
|
|
460
|
+
maxLength: 200,
|
|
461
|
+
aliases: [
|
|
462
|
+
'note',
|
|
463
|
+
'notes',
|
|
464
|
+
'comment',
|
|
465
|
+
'why'
|
|
466
|
+
],
|
|
467
|
+
description: 'How the request reached you. Kept on a new entry only.'
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
id: 'reason',
|
|
471
|
+
label: 'Reason',
|
|
472
|
+
group: 'suppression',
|
|
473
|
+
type: 'text',
|
|
474
|
+
readOnly: true,
|
|
475
|
+
description: '`unsubscribe`, `bounce`, `complaint` or `manual`. An imported address is `manual`: a person on your team added it.'
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
id: 'suppressedAt',
|
|
479
|
+
label: 'Since',
|
|
480
|
+
group: 'suppression',
|
|
481
|
+
type: 'datetime',
|
|
482
|
+
readOnly: true
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
id: 'carriedFromHostId',
|
|
486
|
+
label: 'Copied from site',
|
|
487
|
+
group: 'suppression',
|
|
488
|
+
type: 'text',
|
|
489
|
+
readOnly: true
|
|
490
|
+
}
|
|
491
|
+
]
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
/** A suppression is the address. */ export const SUPPRESSION_MATCH_KEYS = [
|
|
495
|
+
{
|
|
496
|
+
fieldId: 'email',
|
|
497
|
+
normalizer: 'email'
|
|
498
|
+
}
|
|
499
|
+
];
|
|
500
|
+
/** The suppression import's rule: nothing already on the list is changed. */ export const SUPPRESSION_LOCKED_RULES = [
|
|
501
|
+
{
|
|
502
|
+
fieldId: 'note',
|
|
503
|
+
forced: {
|
|
504
|
+
mode: 'keepExisting',
|
|
505
|
+
blank: 'leave'
|
|
506
|
+
},
|
|
507
|
+
reason: SUPPRESSION_KEPT_REASON
|
|
508
|
+
}
|
|
509
|
+
];
|
|
510
|
+
/** Other products' suppression-export headers. */ export const SUPPRESSION_ALIASES = [
|
|
511
|
+
{
|
|
512
|
+
source: 'Mailchimp unsubscribed export',
|
|
513
|
+
aliases: {
|
|
514
|
+
email: [
|
|
515
|
+
'Email Address'
|
|
516
|
+
],
|
|
517
|
+
note: [
|
|
518
|
+
'UNSUB_REASON',
|
|
519
|
+
'UNSUB_REASON_OTHER'
|
|
520
|
+
]
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
source: 'Klaviyo suppression export',
|
|
525
|
+
aliases: {
|
|
526
|
+
email: [
|
|
527
|
+
'Email'
|
|
528
|
+
],
|
|
529
|
+
note: [
|
|
530
|
+
'Suppression Reason'
|
|
531
|
+
]
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
];
|
|
535
|
+
|
|
536
|
+
//# sourceMappingURL=email-transfer-catalog.js.map
|