@travetto/email 3.1.13 → 3.1.15
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 +29 -28
- package/src/util.ts +20 -3
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ npm install @travetto/email
|
|
|
13
13
|
yarn add @travetto/email
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
A standard API for sending and rendering emails. The mail transport must be defined to allow for mail to be sent properly. Out of the box, the only transport available by default is the [NullTransport](https://github.com/travetto/travetto/tree/main/module/email/src/transport.ts#L15) which will just drop emails. The structure of the API is derived from [nodemailer](https://nodemailer.com/about/), but is compatible with any library that can handle the [
|
|
16
|
+
A standard API for sending and rendering emails. The mail transport must be defined to allow for mail to be sent properly. Out of the box, the only transport available by default is the [NullTransport](https://github.com/travetto/travetto/tree/main/module/email/src/transport.ts#L15) which will just drop emails. The structure of the API is derived from [nodemailer](https://nodemailer.com/about/), but is compatible with any library that can handle the [EmailOptions](https://github.com/travetto/travetto/tree/main/module/email/src/types.ts#L39) input.
|
|
17
17
|
|
|
18
18
|
To expose the necessary email transport, the following pattern is commonly used:
|
|
19
19
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/email",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.15",
|
|
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.4",
|
|
27
|
+
"@travetto/config": "^3.1.9",
|
|
28
|
+
"@travetto/di": "^3.1.5",
|
|
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,53 +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>;
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
// Compilation support, defined here to allow for templates to not have a direct dependency on the compiler
|
|
68
|
+
type BaseTemplateConfig = {
|
|
66
69
|
search?: string[];
|
|
67
|
-
global?: string;
|
|
68
70
|
inline?: boolean;
|
|
69
71
|
};
|
|
70
72
|
|
|
71
|
-
export type
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
export type EmailTemplateStyleConfig = BaseTemplateConfig & { global?: string };
|
|
74
|
+
export type EmailTemplateImageConfig = BaseTemplateConfig & {};
|
|
75
|
+
|
|
76
|
+
export type EmailTemplateConfig = {
|
|
77
|
+
styles?: EmailTemplateStyleConfig;
|
|
78
|
+
images?: EmailTemplateImageConfig;
|
|
74
79
|
};
|
|
75
80
|
|
|
76
|
-
export type
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
html: () => Promise<string> | string;
|
|
81
|
-
text: () => Promise<string> | string;
|
|
82
|
-
subject: () => Promise<string> | string;
|
|
83
|
-
};
|
|
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) => {
|