@travetto/email 3.1.5 → 3.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/service.ts +51 -31
package/package.json
CHANGED
package/src/service.ts
CHANGED
|
@@ -7,13 +7,15 @@ import { MailTemplateEngine } from './template';
|
|
|
7
7
|
import { MailUtil } from './util';
|
|
8
8
|
import { EmailResource } from './resource';
|
|
9
9
|
|
|
10
|
+
type MessageWithoutBody = Omit<MessageOptions, 'html' | 'text' | 'subject'>;
|
|
11
|
+
|
|
10
12
|
/**
|
|
11
13
|
* Email service for sending and templating emails
|
|
12
14
|
*/
|
|
13
15
|
@Injectable()
|
|
14
16
|
export class MailService {
|
|
15
17
|
|
|
16
|
-
#compiled = new Map<string,
|
|
18
|
+
#compiled = new Map<string, { html: string, subject: string, text?: string }>();
|
|
17
19
|
#transport: MailTransport;
|
|
18
20
|
#tplEngine: MailTemplateEngine;
|
|
19
21
|
#resources: EmailResource;
|
|
@@ -28,6 +30,21 @@ export class MailService {
|
|
|
28
30
|
this.#resources = resources;
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Force content into alternative slots, satisfies node mailer
|
|
35
|
+
*/
|
|
36
|
+
#forceContentToAlternative(msg: MessageOptions): MessageOptions {
|
|
37
|
+
for (const [key, mime] of [['text', 'text/plain'], ['html', 'text/html']] as const) {
|
|
38
|
+
if (msg[key]) {
|
|
39
|
+
(msg.alternatives ??= []).push({
|
|
40
|
+
content: msg[key], contentDisposition: 'inline', contentTransferEncoding: '7bit', contentType: `${mime}; charset=utf-8`
|
|
41
|
+
});
|
|
42
|
+
delete msg[key];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return msg;
|
|
46
|
+
}
|
|
47
|
+
|
|
31
48
|
/**
|
|
32
49
|
* Send multiple messages.
|
|
33
50
|
*/
|
|
@@ -45,10 +62,9 @@ export class MailService {
|
|
|
45
62
|
}
|
|
46
63
|
|
|
47
64
|
/**
|
|
48
|
-
*
|
|
65
|
+
* Get compiled content by key
|
|
49
66
|
*/
|
|
50
|
-
async
|
|
51
|
-
// Bypass cache if in dynamic mode
|
|
67
|
+
async getCompiled(key: string): Promise<{ html: string, text?: string, subject: string }> {
|
|
52
68
|
if (GlobalEnv.dynamic || !this.#compiled.has(key)) {
|
|
53
69
|
const [html, text, subject] = await Promise.all([
|
|
54
70
|
this.#resources.read(`${key}.compiled.html`),
|
|
@@ -58,43 +74,47 @@ export class MailService {
|
|
|
58
74
|
|
|
59
75
|
this.#compiled.set(key, { html, text, subject });
|
|
60
76
|
}
|
|
61
|
-
return this
|
|
77
|
+
return this.#compiled.get(key)!;
|
|
62
78
|
}
|
|
63
79
|
|
|
64
80
|
/**
|
|
65
|
-
*
|
|
81
|
+
* Build message from key/context
|
|
82
|
+
* @param key
|
|
83
|
+
* @param ctx
|
|
84
|
+
* @returns
|
|
66
85
|
*/
|
|
67
|
-
async
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
Object.assign(msg, { html, text, subject });
|
|
77
|
-
}
|
|
86
|
+
async buildMessage(key: string | MessageOptions, ctx?: Record<string, unknown>): Promise<MessageOptions> {
|
|
87
|
+
const tpl = (typeof key === 'string' ? await this.getCompiled(key) : key);
|
|
88
|
+
ctx ??= (typeof key === 'string' ? {} : key.context ?? {});
|
|
89
|
+
const [rawHtml, text, subject] = await Promise.all([
|
|
90
|
+
tpl.html ? this.#tplEngine!.template(tpl.html, ctx) : undefined,
|
|
91
|
+
tpl.text ? this.#tplEngine!.template(tpl.text, ctx) : undefined,
|
|
92
|
+
tpl.subject ? this.#tplEngine!.template(tpl.subject, ctx) : undefined
|
|
93
|
+
]);
|
|
78
94
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
delete msg.text;
|
|
95
|
+
const msg: MessageOptions = {
|
|
96
|
+
html: rawHtml ?? '',
|
|
97
|
+
text,
|
|
98
|
+
subject
|
|
84
99
|
}
|
|
85
100
|
|
|
86
|
-
// Force html to the end per the mime spec
|
|
87
101
|
if (msg.html) {
|
|
88
102
|
const { html, attachments } = await MailUtil.extractImageAttachments(msg.html);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// NOTE: The leading space on the content type is to force node mailer to not do anything fancy with
|
|
92
|
-
content: html, contentDisposition: 'inline', contentTransferEncoding: '7bit', contentType: ' text/html; charset=utf-8'
|
|
93
|
-
});
|
|
94
|
-
// @ts-expect-error
|
|
95
|
-
delete msg.html; // This is a hack to fix nodemailer
|
|
103
|
+
msg.html = html;
|
|
104
|
+
msg.attachments = attachments;
|
|
96
105
|
}
|
|
97
106
|
|
|
98
|
-
return
|
|
107
|
+
return msg;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Send a single message
|
|
112
|
+
*/
|
|
113
|
+
async send<S extends SentMessage = SentMessage>(key: string, base?: MessageWithoutBody): Promise<S>;
|
|
114
|
+
async send<S extends SentMessage = SentMessage>(message: MessageOptions): Promise<S>;
|
|
115
|
+
async send<S extends SentMessage = SentMessage>(keyOrMessage: MessageOptions | string, base?: MessageWithoutBody): Promise<S> {
|
|
116
|
+
let msg = await this.buildMessage(keyOrMessage);
|
|
117
|
+
msg = this.#forceContentToAlternative(msg);
|
|
118
|
+
return this.#transport.send<S>({ ...base, ...msg });
|
|
99
119
|
}
|
|
100
120
|
}
|