@travetto/email 3.1.12 → 3.1.14
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 +1 -1
- package/package.json +4 -4
- package/src/internal/types.ts +1 -1
- package/src/service.ts +20 -20
- package/src/template.ts +7 -7
- package/src/transport.ts +3 -3
- package/src/types.ts +35 -27
- package/src/util.ts +20 -3
package/README.md
CHANGED
|
@@ -37,4 +37,4 @@ By design, sending an email requires the sender to specify the html, text option
|
|
|
37
37
|
* `resources/<key>.compiled.html`
|
|
38
38
|
* `resources/<key>.compiled.text`
|
|
39
39
|
* `resources/<key>.compiled.subject`
|
|
40
|
-
With `.html` being the only required field. The [Email
|
|
40
|
+
With `.html` being the only required field. The [Email Compilation Support](https://github.com/travetto/travetto/tree/main/module/email-compiler#readme "Email compiling module") module supports this format, and will generate files accordingly. Also, note that `<key>` can include slashes, allowing for nesting folders.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/email",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.14",
|
|
4
4
|
"description": "Email transmission module.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"directory": "module/email"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@travetto/base": "^3.1.
|
|
27
|
-
"@travetto/config": "^3.1.
|
|
28
|
-
"@travetto/di": "^3.1.
|
|
26
|
+
"@travetto/base": "^3.1.3",
|
|
27
|
+
"@travetto/config": "^3.1.8",
|
|
28
|
+
"@travetto/di": "^3.1.4",
|
|
29
29
|
"@types/mustache": "^4.2.2",
|
|
30
30
|
"mustache": "^4.2.0"
|
|
31
31
|
},
|
package/src/internal/types.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export class
|
|
1
|
+
export class MailInterpolatorTarget { }
|
|
2
2
|
export class MailTransportTarget { }
|
package/src/service.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { GlobalEnv } from '@travetto/base';
|
|
2
2
|
import { Injectable } from '@travetto/di';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { EmailCompiled, EmailOptions, SentEmail } from './types';
|
|
5
5
|
import { MailTransport } from './transport';
|
|
6
|
-
import {
|
|
6
|
+
import { MailInterpolator } from './template';
|
|
7
7
|
import { MailUtil } from './util';
|
|
8
8
|
import { EmailResource } from './resource';
|
|
9
9
|
|
|
10
|
-
type MessageWithoutBody = Omit<
|
|
10
|
+
type MessageWithoutBody = Omit<EmailOptions, keyof EmailCompiled>;
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Email service for sending and templating emails
|
|
@@ -15,17 +15,17 @@ type MessageWithoutBody = Omit<MessageOptions, keyof MessageCompiled>;
|
|
|
15
15
|
@Injectable()
|
|
16
16
|
export class MailService {
|
|
17
17
|
|
|
18
|
-
#compiled = new Map<string,
|
|
18
|
+
#compiled = new Map<string, EmailCompiled>();
|
|
19
19
|
#transport: MailTransport;
|
|
20
|
-
#
|
|
20
|
+
#interpolator: MailInterpolator;
|
|
21
21
|
#resources: EmailResource;
|
|
22
22
|
|
|
23
23
|
constructor(
|
|
24
24
|
transport: MailTransport,
|
|
25
|
-
|
|
25
|
+
interpolator: MailInterpolator,
|
|
26
26
|
resources: EmailResource
|
|
27
27
|
) {
|
|
28
|
-
this.#
|
|
28
|
+
this.#interpolator = interpolator;
|
|
29
29
|
this.#transport = transport;
|
|
30
30
|
this.#resources = resources;
|
|
31
31
|
}
|
|
@@ -33,13 +33,13 @@ export class MailService {
|
|
|
33
33
|
/**
|
|
34
34
|
* Get compiled content by key
|
|
35
35
|
*/
|
|
36
|
-
async getCompiled(key: string): Promise<
|
|
36
|
+
async getCompiled(key: string): Promise<EmailCompiled> {
|
|
37
37
|
if (GlobalEnv.dynamic || !this.#compiled.has(key)) {
|
|
38
38
|
const [html, text, subject] = await Promise.all([
|
|
39
39
|
this.#resources.read(`${key}.compiled.html`),
|
|
40
40
|
this.#resources.read(`${key}.compiled.text`),
|
|
41
41
|
this.#resources.read(`${key}.compiled.subject`)
|
|
42
|
-
]);
|
|
42
|
+
].map(x => x.then(MailUtil.purgeBrand)));
|
|
43
43
|
|
|
44
44
|
this.#compiled.set(key, { html, text, subject });
|
|
45
45
|
}
|
|
@@ -52,13 +52,13 @@ export class MailService {
|
|
|
52
52
|
* @param ctx
|
|
53
53
|
* @returns
|
|
54
54
|
*/
|
|
55
|
-
async
|
|
55
|
+
async renderMessage(keyOrMessage: string | EmailCompiled, ctx: Record<string, unknown>): Promise<EmailCompiled> {
|
|
56
56
|
const tpl = (typeof keyOrMessage === 'string' ? await this.getCompiled(keyOrMessage) : keyOrMessage);
|
|
57
57
|
|
|
58
58
|
const [html, text, subject] = await Promise.all([
|
|
59
|
-
this.#
|
|
60
|
-
this.#
|
|
61
|
-
this.#
|
|
59
|
+
this.#interpolator.render(tpl.html, ctx),
|
|
60
|
+
this.#interpolator.render(tpl.text ?? '', ctx),
|
|
61
|
+
this.#interpolator.render(tpl.subject, ctx)
|
|
62
62
|
]);
|
|
63
63
|
|
|
64
64
|
return { html, text, subject };
|
|
@@ -67,15 +67,15 @@ export class MailService {
|
|
|
67
67
|
/**
|
|
68
68
|
* Send a single message
|
|
69
69
|
*/
|
|
70
|
-
async send<S extends
|
|
71
|
-
message: Pick<
|
|
70
|
+
async send<S extends SentEmail = SentEmail>(
|
|
71
|
+
message: Pick<EmailOptions, 'to' | 'from' | 'replyTo'>,
|
|
72
72
|
key: string,
|
|
73
73
|
ctx?: Record<string, unknown>,
|
|
74
74
|
base?: MessageWithoutBody
|
|
75
75
|
): Promise<S>;
|
|
76
|
-
async send<S extends
|
|
77
|
-
async send<S extends
|
|
78
|
-
message:
|
|
76
|
+
async send<S extends SentEmail = SentEmail>(message: EmailOptions): Promise<S>;
|
|
77
|
+
async send<S extends SentEmail = SentEmail>(
|
|
78
|
+
message: EmailOptions | Pick<EmailOptions, 'to' | 'from' | 'replyTo'>,
|
|
79
79
|
key?: string,
|
|
80
80
|
ctx?: Record<string, unknown>,
|
|
81
81
|
base?: MessageWithoutBody
|
|
@@ -83,7 +83,7 @@ export class MailService {
|
|
|
83
83
|
const keyOrMessage = key ?? ('html' in message ? message : '') ?? '';
|
|
84
84
|
const context = ctx ?? (('context' in message) ? message.context : {}) ?? {};
|
|
85
85
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
86
|
-
const compiled = await this.
|
|
86
|
+
const compiled = await this.renderMessage(keyOrMessage as EmailCompiled, context);
|
|
87
87
|
|
|
88
88
|
const final = { ...base, ...message, ...compiled, context };
|
|
89
89
|
|
|
@@ -100,7 +100,7 @@ export class MailService {
|
|
|
100
100
|
/**
|
|
101
101
|
* Send multiple messages.
|
|
102
102
|
*/
|
|
103
|
-
async sendAll<S extends
|
|
103
|
+
async sendAll<S extends SentEmail = SentEmail>(messages: EmailOptions[], base: Partial<EmailOptions> = {}): Promise<S[]> {
|
|
104
104
|
return Promise.all(messages.map(msg => this.send<S>({
|
|
105
105
|
...base,
|
|
106
106
|
...msg,
|
package/src/template.ts
CHANGED
|
@@ -5,24 +5,24 @@ import { Injectable } from '@travetto/di';
|
|
|
5
5
|
import { EmailResource } from './resource';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Mail
|
|
8
|
+
* Mail interpolation engine
|
|
9
9
|
*
|
|
10
|
-
* @concrete ./internal/types:
|
|
10
|
+
* @concrete ./internal/types:MailInterpolatorTarget
|
|
11
11
|
*/
|
|
12
|
-
export interface
|
|
12
|
+
export interface MailInterpolator {
|
|
13
13
|
/**
|
|
14
14
|
* Resolved nested templates
|
|
15
15
|
*/
|
|
16
16
|
resolveNested(template: string): Promise<string>;
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Interpolate a string with a given context
|
|
19
|
+
* Interpolate a string with a given context
|
|
20
20
|
*/
|
|
21
|
-
|
|
21
|
+
render(text: string, ctx: Record<string, unknown>): Promise<string> | string;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
@Injectable()
|
|
25
|
-
export class
|
|
25
|
+
export class MustacheInterpolator implements MailInterpolator {
|
|
26
26
|
|
|
27
27
|
resources = new EmailResource();
|
|
28
28
|
|
|
@@ -45,7 +45,7 @@ export class MustacheTemplateEngine implements MailTemplateEngine {
|
|
|
45
45
|
/**
|
|
46
46
|
* Interpolate text with data
|
|
47
47
|
*/
|
|
48
|
-
|
|
48
|
+
render(text: string, data: Record<string, unknown>): string {
|
|
49
49
|
return mustache.render(text, data);
|
|
50
50
|
}
|
|
51
51
|
}
|
package/src/transport.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EmailOptions, SentEmail } from './types';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Default mail transport
|
|
@@ -6,14 +6,14 @@ import { MessageOptions, SentMessage } from './types';
|
|
|
6
6
|
* @concrete ./internal/types:MailTransportTarget
|
|
7
7
|
*/
|
|
8
8
|
export interface MailTransport {
|
|
9
|
-
send<S extends
|
|
9
|
+
send<S extends SentEmail = SentEmail>(mail: EmailOptions): Promise<S>;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Transport that consumes messages without sending
|
|
14
14
|
*/
|
|
15
15
|
export class NullTransport implements MailTransport {
|
|
16
|
-
async send<S extends
|
|
16
|
+
async send<S extends SentEmail = SentEmail>(mail: EmailOptions): Promise<S> {
|
|
17
17
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
18
18
|
return {} as S;
|
|
19
19
|
}
|
package/src/types.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { Url } from 'url';
|
|
|
4
4
|
/**
|
|
5
5
|
* An address
|
|
6
6
|
*/
|
|
7
|
-
export interface
|
|
7
|
+
export interface EmailAddress {
|
|
8
8
|
name: string;
|
|
9
9
|
address: string;
|
|
10
10
|
}
|
|
@@ -20,7 +20,7 @@ interface AttachmentLike {
|
|
|
20
20
|
/**
|
|
21
21
|
* A full attachment
|
|
22
22
|
*/
|
|
23
|
-
export interface
|
|
23
|
+
export interface EmailAttachment extends AttachmentLike {
|
|
24
24
|
filename?: string | false;
|
|
25
25
|
cid?: string;
|
|
26
26
|
encoding?: string;
|
|
@@ -31,46 +31,54 @@ export interface Attachment extends AttachmentLike {
|
|
|
31
31
|
raw?: string | Buffer | Readable | AttachmentLike;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
type EmailContentType = 'html' | 'text' | 'subject';
|
|
35
|
+
|
|
34
36
|
/**
|
|
35
37
|
* Full message options
|
|
36
38
|
*/
|
|
37
|
-
export interface
|
|
39
|
+
export interface EmailOptions {
|
|
38
40
|
html: string;
|
|
39
41
|
text?: string;
|
|
40
|
-
subject
|
|
42
|
+
subject: string;
|
|
41
43
|
context?: Record<string, unknown>; // For templating
|
|
42
44
|
|
|
43
|
-
from?: string |
|
|
44
|
-
sender?: string |
|
|
45
|
-
to?: string |
|
|
46
|
-
cc?: string |
|
|
47
|
-
bcc?: string |
|
|
48
|
-
replyTo?: string |
|
|
49
|
-
inReplyTo?: string |
|
|
45
|
+
from?: string | EmailAddress;
|
|
46
|
+
sender?: string | EmailAddress;
|
|
47
|
+
to?: string | EmailAddress | (string | EmailAddress)[];
|
|
48
|
+
cc?: string | EmailAddress | (string | EmailAddress)[];
|
|
49
|
+
bcc?: string | EmailAddress | (string | EmailAddress)[];
|
|
50
|
+
replyTo?: string | EmailAddress;
|
|
51
|
+
inReplyTo?: string | EmailAddress;
|
|
50
52
|
references?: string | string[];
|
|
51
53
|
headers?: Record<string, string | string[]>;
|
|
52
|
-
attachments?:
|
|
53
|
-
alternatives?:
|
|
54
|
+
attachments?: EmailAttachment[];
|
|
55
|
+
alternatives?: EmailAttachment[];
|
|
54
56
|
messageId?: string;
|
|
55
57
|
date?: Date | string;
|
|
56
58
|
encoding?: string;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
export type
|
|
61
|
+
export type SentEmail = {
|
|
60
62
|
messageId?: string;
|
|
61
63
|
};
|
|
62
64
|
|
|
63
|
-
export type
|
|
65
|
+
export type EmailCompiled = Record<EmailContentType, string>;
|
|
66
|
+
|
|
67
|
+
// Compilation support, defined here to allow for templates to not have a direct dependency on the compiler
|
|
68
|
+
type BaseTemplateConfig = {
|
|
69
|
+
search?: string[];
|
|
70
|
+
inline?: boolean;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type EmailTemplateStyleConfig = BaseTemplateConfig & { global?: string };
|
|
74
|
+
export type EmailTemplateImageConfig = BaseTemplateConfig & {};
|
|
75
|
+
|
|
76
|
+
export type EmailTemplateConfig = {
|
|
77
|
+
styles?: EmailTemplateStyleConfig;
|
|
78
|
+
images?: EmailTemplateImageConfig;
|
|
79
|
+
};
|
|
64
80
|
|
|
65
|
-
export type
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
global?: string;
|
|
70
|
-
};
|
|
71
|
-
inlineImages?: boolean;
|
|
72
|
-
inlineStyles?: boolean;
|
|
73
|
-
html: () => Promise<string> | string;
|
|
74
|
-
text: () => Promise<string> | string;
|
|
75
|
-
subject: () => Promise<string> | string;
|
|
76
|
-
};
|
|
81
|
+
export type EmailTemplateLocation = { file: string, module: string };
|
|
82
|
+
export type EmailRenderer = (ctx: EmailTemplateLocation & EmailTemplateConfig) => Promise<string> | string;
|
|
83
|
+
export type EmailCompileSource = EmailTemplateConfig & Record<EmailContentType, EmailRenderer>;
|
|
84
|
+
export type EmailCompileContext = EmailTemplateLocation & EmailCompileSource;
|
package/src/util.ts
CHANGED
|
@@ -1,17 +1,34 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RootIndex } from '@travetto/manifest';
|
|
2
|
+
|
|
3
|
+
import { EmailAttachment } from './types';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Utilities for email
|
|
5
7
|
*/
|
|
6
8
|
export class MailUtil {
|
|
9
|
+
/** Remove brand from text */
|
|
10
|
+
static purgeBrand(text: string): string {
|
|
11
|
+
return text.replace(/<!-- WARNING:[^\n]*\n/m, '');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Add brand to text */
|
|
15
|
+
static buildBrand(file: string, content: string, compile?: string): string {
|
|
16
|
+
const out = [
|
|
17
|
+
'WARNING: Do not modify.',
|
|
18
|
+
`File is generated from "${file.replace(RootIndex.manifest.workspacePath, '.')}"`,
|
|
19
|
+
compile ? `Run \`${compile.replaceAll('\n', ' ')}\` to regenerate` : ''
|
|
20
|
+
];
|
|
21
|
+
return `<!-- ${out.join(' ').trim()} -->\n${content}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
7
24
|
/**
|
|
8
25
|
* Extract images from html as attachments
|
|
9
26
|
*
|
|
10
27
|
* @param html
|
|
11
28
|
*/
|
|
12
|
-
static async extractImageAttachments(html: string): Promise<{ html: string, attachments:
|
|
29
|
+
static async extractImageAttachments(html: string): Promise<{ html: string, attachments: EmailAttachment[] }> {
|
|
13
30
|
let idx = 0;
|
|
14
|
-
const attachments:
|
|
31
|
+
const attachments: EmailAttachment[] = [];
|
|
15
32
|
const contentMap = new Map<string, string>();
|
|
16
33
|
|
|
17
34
|
html = html.replace(/data:(image\/[^;]+);base64,([^"']+)/g, (__, contentType, content) => {
|