Skip to content

Commit fdbf127

Browse files
committed
Wip: BCC & CC & space separated emails
1 parent 7a2eaf0 commit fdbf127

32 files changed

+121
-625
lines changed

email/libmailmerge/src/cli-testing.ts

Lines changed: 0 additions & 45 deletions
This file was deleted.

email/libmailmerge/src/engines/nunjucks-md/index.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ import { promises as fs } from "fs";
22
import nunjucks from "nunjucks";
33

44
import { renderMarkdownToHtml } from "../../markdown/toHtml";
5-
import { CSVRecord } from "../../util/types";
5+
import { MappedCSVRecord } from "../../util/types";
66
import { TemplateEngineOptions, TemplatePreviews } from "../types";
77
import { TemplateEngine } from "../types";
88
import getTemplateFields from "./getFields";
9-
import { assertIsNunjucksTemplateOptions, NunjucksMarkdownTemplateOptions, NunjucksSidecarMetadata } from "./types";
9+
import {
10+
assertIsNunjucksTemplateOptions,
11+
NunjucksMarkdownTemplateOptions,
12+
NunjucksSidecarMetadata,
13+
} from "./types";
1014

1115
export { default as getNunjucksTemplateFields } from "./getFields";
1216
export * from "./types";
@@ -41,7 +45,10 @@ export default class NunjucksMarkdownEngine extends TemplateEngine {
4145
private async renderMarkdownToHtmlInsideWrapper(markdown: string) {
4246
// Render the MD to HTML
4347
const htmlWrapper = await fs.readFile(this.templateOptions.rootHtmlTemplate, "utf-8");
44-
const htmlWrapperCompiled = nunjucks.compile(htmlWrapper, nunjucks.configure({ autoescape: false }));
48+
const htmlWrapperCompiled = nunjucks.compile(
49+
htmlWrapper,
50+
nunjucks.configure({ autoescape: false }),
51+
);
4552
const html = renderMarkdownToHtml(markdown);
4653

4754
// Wrap the rendered markdown html in the wrapper
@@ -62,7 +69,7 @@ export default class NunjucksMarkdownEngine extends TemplateEngine {
6269
return getTemplateFields(this.loadedTemplate);
6370
}
6471

65-
override async renderPreview(record: CSVRecord) {
72+
override async renderPreview(record: MappedCSVRecord) {
6673
if (!this.loadedTemplate) {
6774
throw new Error("Template not loaded");
6875
}

email/libmailmerge/src/engines/types.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Types shared by all template engines.
33
* @packageDocumentation
44
*/
5-
import { CSVRecord } from "../util/types";
5+
import { MappedCSVRecord } from "../util/types";
66

77
/**
88
* Generic "any" type for template engine options: essentially a dictionary.
@@ -53,7 +53,7 @@ export abstract class TemplateEngine {
5353
* @param record - The record to render the template with, where the keys correspond to the fields extracted in {@link TemplateEngine.extractFields}
5454
* @returns A promise that resolves to an array of {@link TemplatePreviews} objects - check the type for more information
5555
*/
56-
abstract renderPreview(record: CSVRecord): Promise<TemplatePreviews>;
56+
abstract renderPreview(record: MappedCSVRecord): Promise<TemplatePreviews>;
5757

5858
/**
5959
* Given previews generated by {@link TemplateEngine.renderPreview} or this method, re-render a preview.
@@ -68,14 +68,20 @@ export abstract class TemplateEngine {
6868
*
6969
* **IMPORTANT NOTE**: You _must_ rerender _all_ previews, even if only one was edited. This is a replace-all operations, not a patch.
7070
*/
71-
abstract rerenderPreviews(loadedPreviews: TemplatePreviews, associatedRecord: CSVRecord): Promise<TemplatePreviews>;
71+
abstract rerenderPreviews(
72+
loadedPreviews: TemplatePreviews,
73+
associatedRecord: MappedCSVRecord,
74+
): Promise<TemplatePreviews>;
7275

7376
/**
7477
* Given the (re-)rendered previews, generate the HTML to send.
7578
*
7679
* Most likely this means returning one of the previews, but you may want to combine them or do other operations.
7780
*/
78-
abstract getHTMLToSend(loadedPreviews: TemplatePreviews, associatedRecord: CSVRecord): Promise<string>;
81+
abstract getHTMLToSend(
82+
loadedPreviews: TemplatePreviews,
83+
associatedRecord: MappedCSVRecord,
84+
): Promise<string>;
7985
}
8086

8187
/**

email/libmailmerge/src/mailer/defaultMailer.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const defaultMailer = (
2727
html: string,
2828
mailer: Mailer,
2929
attachments: Mail.Options["attachments"] = [],
30+
additionalInfo: { cc: EmailString[]; bcc: EmailString[] } = { cc: [], bcc: [] },
3031
): Promise<void> =>
3132
mailer.sendMail(
3233
Mailer.makeFromLineFromEmail(
@@ -39,4 +40,5 @@ export const defaultMailer = (
3940
subject,
4041
html,
4142
attachments,
43+
additionalInfo,
4244
);

email/libmailmerge/src/mailer/mailer.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export default class Mailer {
4040
subject: string,
4141
html: string,
4242
attachments: Mail.Options["attachments"] = [],
43+
additionalInfo: { cc: EmailString[]; bcc: EmailString[] } = { cc: [], bcc: [] },
4344
text: string = convert(html),
4445
): Promise<void> {
4546
const info = await this.transporter.sendMail({
@@ -49,6 +50,8 @@ export default class Mailer {
4950
text: text, // plain text body
5051
html: html, // html body
5152
attachments,
53+
cc: additionalInfo.cc,
54+
bcc: additionalInfo.bcc,
5255
});
5356

5457
logger.debug(

email/libmailmerge/src/previews/sidecarData.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { join } from "path";
1111
import { TEMPLATE_ENGINES } from "../engines";
1212
import { TemplateEngineOptions, TemplatePreview, TemplatePreviews } from "../engines/types";
1313
import Mailer from "../mailer/mailer";
14-
import { CSVRecord, EmailString } from "../util/types";
14+
import { MappedCSVRecord, EmailString } from "../util/types";
1515
import { SidecarData } from "./types";
1616

1717
const PARTS_SEPARATOR = "__";
@@ -26,8 +26,8 @@ const logger = createLogger("docsoc.sidecar");
2626
* @returns
2727
*/
2828
export const getRecordPreviewPrefix = (
29-
record: CSVRecord,
30-
fileNamer: (record: CSVRecord) => string,
29+
record: MappedCSVRecord,
30+
fileNamer: (record: MappedCSVRecord) => string,
3131
) => `${fileNamer(record)}`;
3232

3333
/**
@@ -40,8 +40,8 @@ export const getRecordPreviewPrefix = (
4040
* // => "file_1__nunjucks__preview1.txt"
4141
*/
4242
export const getRecordPreviewPrefixForIndividual = (
43-
record: CSVRecord,
44-
fileNamer: (record: CSVRecord) => string,
43+
record: MappedCSVRecord,
44+
fileNamer: (record: MappedCSVRecord) => string,
4545
templateEngine: string,
4646
preview: TemplatePreview,
4747
) =>
@@ -58,16 +58,16 @@ export const getRecordPreviewPrefixForIndividual = (
5858
* // => "file_1-metadata.json"
5959
*/
6060
export const getRecordPreviewPrefixForMetadata = (
61-
record: CSVRecord,
62-
fileNamer: (record: CSVRecord) => string,
61+
record: MappedCSVRecord,
62+
fileNamer: (record: MappedCSVRecord) => string,
6363
) => `${getRecordPreviewPrefix(record, fileNamer)}${METADATA_FILE_SUFFIX}`;
6464

6565
type ValidRecordReturn = { valid: false; reason: string } | { valid: true };
6666
/**
6767
* Check a record is valid for use in mailmerge - specifically, that it has a valid email address and a subject.
6868
* @param record __Mapped__ CSV Record to validate
6969
*/
70-
export const validateRecord = (record: CSVRecord): ValidRecordReturn => {
70+
export const validateRecord = (record: MappedCSVRecord): ValidRecordReturn => {
7171
if (!Mailer.validateEmail(record["email"] as string)) {
7272
return {
7373
valid: false,
@@ -96,9 +96,9 @@ export const validateRecord = (record: CSVRecord): ValidRecordReturn => {
9696
* @returns
9797
*/
9898
export async function writeMetadata(
99-
record: CSVRecord,
99+
record: MappedCSVRecord,
100100
sidecarData: SidecarData,
101-
fileNamer: (record: CSVRecord) => string,
101+
fileNamer: (record: MappedCSVRecord) => string,
102102
previewsRoot: string,
103103
): Promise<void> {
104104
const recordState = validateRecord(record);
@@ -116,6 +116,9 @@ export async function writeMetadata(
116116
return Promise.resolve();
117117
}
118118

119+
const parseEmailList = (emailList: string | undefined): EmailString[] =>
120+
emailList ? (emailList.split(/\s/) as EmailString[]) : [];
121+
119122
/**
120123
* Generate sidecar metadata for a record & the previews generated from it.
121124
* It is recommended you then store this info alongside the preview, e.g. in a JSON file
@@ -130,8 +133,8 @@ export async function writeMetadata(
130133
* @returns Sidecar metadata
131134
*/
132135
export function getSidecarMetadata(
133-
fileNamer: (record: CSVRecord) => string,
134-
record: CSVRecord,
136+
fileNamer: (record: MappedCSVRecord) => string,
137+
record: MappedCSVRecord,
135138
templateEngine: TEMPLATE_ENGINES,
136139
templateOptions: TemplateEngineOptions,
137140
attachments: string[],
@@ -155,7 +158,9 @@ export function getSidecarMetadata(
155158
},
156159
})),
157160
email: {
158-
to: record["email"] as EmailString,
161+
to: parseEmailList(record["email"] as string),
162+
cc: parseEmailList(record["cc"] as string),
163+
bcc: parseEmailList(record["bcc"] as string),
159164
subject: record["subject"] as string,
160165
},
161166
attachments,

email/libmailmerge/src/previews/types.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ENGINES_MAP } from "../engines";
22
import { TemplateEngineOptions, TemplatePreview } from "../engines/types";
3-
import { CSVRecord, EmailString } from "../util/types";
3+
import { MappedCSVRecord, EmailString } from "../util/types";
44

55
/**
66
* Outputted to JSON files next to rendered template previews, containing metadata about the preview.
@@ -9,7 +9,7 @@ export interface SidecarData {
99
/** Name of the template rendered (used for logging) */
1010
name: string;
1111
/** Record associated with the template rendered */
12-
record: CSVRecord;
12+
record: MappedCSVRecord;
1313
/** Engine used */
1414
engine: keyof typeof ENGINES_MAP;
1515
/** Options given to the engine */
@@ -24,7 +24,9 @@ export interface SidecarData {
2424
}>;
2525
/** Metadata for emailing */
2626
email: {
27-
to: EmailString;
27+
to: EmailString[];
28+
cc: EmailString[];
29+
bcc: EmailString[];
2830
subject: string;
2931
};
3032
/** Array of paths to attachments to include in the final email, relative to the mailmerge workspace root (top level folder) */

email/libmailmerge/src/send.ts

Lines changed: 0 additions & 98 deletions
This file was deleted.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
11
export const DOCSOC_DEFAULT_FROM_LINE = `"DoCSoc" <[email protected]>`;
2+
3+
/** Expected names of requried email fields in CSVRecords */
4+
export const CSV_DEFAULT_FIELD_NAMES = {
5+
to: "email",
6+
subject: "subject",
7+
cc: "cc",
8+
bcc: "bcc",
9+
};

email/libmailmerge/src/util/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export type EmailString = `${string}@${string}`;
22
export type FromEmail = `"${string}" <${EmailString}>`;
33

4-
export type CSVRecord = Record<string, unknown>;
4+
export type RawCSVRecord = Record<string, unknown>;
5+
export type MappedCSVRecord = Record<string, unknown>;

0 commit comments

Comments
 (0)