@travetto/email 4.0.0-rc.0 → 4.0.0-rc.2

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 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 [EmailOptions](https://github.com/travetto/travetto/tree/main/module/email/src/types.ts#L39) input.
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#L43) input.
17
17
 
18
18
  To expose the necessary email transport, the following pattern is commonly used:
19
19
 
package/__index__.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from './src/service';
2
2
  export * from './src/types';
3
3
  export * from './src/config';
4
+ export * from './src/resource';
4
5
  export * from './src/transport';
5
6
  export * from './src/util';
6
7
  export * from './src/template';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/email",
3
- "version": "4.0.0-rc.0",
3
+ "version": "4.0.0-rc.2",
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": "^4.0.0-rc.0",
27
- "@travetto/config": "^4.0.0-rc.0",
28
- "@travetto/di": "^4.0.0-rc.0",
26
+ "@travetto/base": "^4.0.0-rc.2",
27
+ "@travetto/config": "^4.0.0-rc.2",
28
+ "@travetto/di": "^4.0.0-rc.2",
29
29
  "@types/mustache": "^4.2.5",
30
30
  "mustache": "^4.2.0"
31
31
  },
@@ -0,0 +1,19 @@
1
+ import { AppError, Env, FileLoader } from '@travetto/base';
2
+ import { RuntimeIndex } from '@travetto/manifest';
3
+
4
+ /** Build a resource loader that looks into a module and it's dependencies */
5
+ export class EmailResourceLoader extends FileLoader {
6
+ constructor(module: string, globalResources?: string[]) {
7
+ const mod = RuntimeIndex.getModule(module);
8
+ if (!mod) {
9
+ throw new AppError(`Unknown module - ${module}`, 'notfound', { module });
10
+ }
11
+ super([
12
+ ...Env.TRV_RESOURCES.list ?? [],
13
+ `${module}#resources`,
14
+ ...RuntimeIndex.getDependentModules(mod, 'children').map(x => `${x.name}#resources`),
15
+ '@@#resources',
16
+ ...globalResources ?? []
17
+ ]);
18
+ }
19
+ }
package/src/service.ts CHANGED
@@ -90,6 +90,17 @@ export class MailService {
90
90
  final.attachments = [...attachments, ...(final.attachments ?? [])];
91
91
  }
92
92
 
93
+ // Disable threading if desired, provide a unique message id and a unique reply-to
94
+ if ('disableThreading' in message && message.disableThreading) {
95
+ const id = MailUtil.buildUniqueMessageId(message);
96
+ final.headers = {
97
+ 'In-Reply-To': id,
98
+ References: id,
99
+ 'X-Message-Id': id,
100
+ ...final.headers
101
+ };
102
+ }
103
+
93
104
  return this.#transport.send<S>(final);
94
105
  }
95
106
 
package/src/types.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { FileLoader } from '@travetto/base';
1
2
  import { Readable } from 'node:stream';
2
3
  import { Url } from 'node:url';
3
4
 
@@ -33,6 +34,9 @@ export interface EmailAttachment extends AttachmentLike {
33
34
 
34
35
  type EmailContentType = 'html' | 'text' | 'subject';
35
36
 
37
+ export type EmailIdentity = string | EmailAddress;
38
+ export type EmailIdentityList = EmailIdentity | EmailIdentity[];
39
+
36
40
  /**
37
41
  * Full message options
38
42
  */
@@ -42,13 +46,15 @@ export interface EmailOptions {
42
46
  subject: string;
43
47
  context?: Record<string, unknown>; // For templating
44
48
 
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;
49
+ disableThreading?: boolean;
50
+
51
+ from?: EmailIdentity;
52
+ sender?: EmailIdentity;
53
+ to?: EmailIdentityList;
54
+ cc?: EmailIdentityList;
55
+ bcc?: EmailIdentityList;
56
+ replyTo?: EmailIdentity;
57
+ inReplyTo?: EmailIdentity;
52
58
  references?: string | string[];
53
59
  headers?: Record<string, string | string[]>;
54
60
  attachments?: EmailAttachment[];
@@ -65,20 +71,15 @@ export type SentEmail = {
65
71
  export type EmailCompiled = Record<EmailContentType, string>;
66
72
 
67
73
  // Compilation support, defined here to allow for templates to not have a direct dependency on the compiler
68
- type BaseTemplateConfig = {
69
- search?: string[] | readonly string[];
70
- inline?: boolean;
74
+ export type EmailTemplateResource = {
75
+ loader: FileLoader;
76
+ inlineStyle?: boolean;
77
+ inlineImages?: boolean;
78
+ globalStyles?: string;
71
79
  };
72
80
 
73
- export type EmailTemplateStyleConfig = BaseTemplateConfig & { global?: string };
74
- export type EmailTemplateImageConfig = BaseTemplateConfig & {};
75
-
76
- export type EmailTemplateConfig = {
77
- styles?: EmailTemplateStyleConfig;
78
- images?: EmailTemplateImageConfig;
79
- };
81
+ type EmailTemplateContent = Record<EmailContentType, () => (Promise<string> | string)>;
80
82
 
81
83
  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;
84
+ export type EmailTemplateModule = EmailTemplateResource & EmailTemplateContent;
85
+ export type EmailTemplateImport = { prepare(loc: EmailTemplateLocation): Promise<EmailTemplateModule> };
package/src/util.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { RuntimeContext } from '@travetto/manifest';
2
2
 
3
- import { EmailAttachment } from './types';
3
+ import { EmailAttachment, EmailIdentity, EmailIdentityList, EmailOptions } from './types';
4
+ import { Util } from '@travetto/base';
4
5
 
5
6
  /**
6
7
  * Utilities for email
@@ -57,4 +58,27 @@ export class MailUtil {
57
58
  html, attachments
58
59
  };
59
60
  }
61
+
62
+ /**
63
+ * Get the primary email, if set from an email identity or identity list
64
+ */
65
+ static getPrimaryEmail(src?: EmailIdentity | EmailIdentityList): string | undefined {
66
+ if (!src) {
67
+ return;
68
+ }
69
+ if (Array.isArray(src)) {
70
+ src = src[0];
71
+ }
72
+ return (typeof src === 'string') ? src : src.address;
73
+ }
74
+
75
+ /**
76
+ * Build a unique message id
77
+ */
78
+ static buildUniqueMessageId(message: EmailOptions): string {
79
+ const from = this.getPrimaryEmail(message.from)!;
80
+ const to = this.getPrimaryEmail(message.to)!;
81
+ const uid = Util.shortHash(`${to}${from}${message.subject}${Date.now()}`).substring(0, 12);
82
+ return `<${uid}@${from.split('@')[1]}>`;
83
+ }
60
84
  }