@travetto/email-compiler 3.1.21 → 3.1.23

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/email-compiler",
3
- "version": "3.1.21",
3
+ "version": "3.1.23",
4
4
  "description": "Email compiling module",
5
5
  "keywords": [
6
6
  "email",
@@ -27,9 +27,9 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@travetto/base": "^3.1.3",
30
- "@travetto/config": "^3.1.7",
30
+ "@travetto/config": "^3.1.8",
31
31
  "@travetto/di": "^3.1.4",
32
- "@travetto/email": "^3.1.13",
32
+ "@travetto/email": "^3.1.14",
33
33
  "@travetto/image": "^3.1.4",
34
34
  "@types/inline-css": "^3.0.1",
35
35
  "html-entities": "^2.3.3",
@@ -38,7 +38,7 @@
38
38
  "purgecss": "^5.0.0"
39
39
  },
40
40
  "peerDependencies": {
41
- "@travetto/cli": "^3.1.8"
41
+ "@travetto/cli": "^3.1.9"
42
42
  },
43
43
  "peerDependenciesMeta": {
44
44
  "@travetto/cli": {
package/src/compiler.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs/promises';
2
2
 
3
3
  import { FileQueryProvider, TypedObject } from '@travetto/base';
4
- import { MessageCompilationSource, MessageCompiled } from '@travetto/email';
4
+ import { EmailCompileSource, EmailCompiled, EmailCompileContext, MailUtil } from '@travetto/email';
5
5
  import { RootIndex, path } from '@travetto/manifest';
6
6
  import { DynamicFileLoader } from '@travetto/base/src/internal/file-loader';
7
7
 
@@ -15,13 +15,19 @@ const VALID_FILE = (file: string): boolean => /[.](scss|css|png|jpe?g|gif|ya?ml)
15
15
  export class EmailCompiler {
16
16
 
17
17
  /** Load Template */
18
- static async loadTemplate(file: string): Promise<MessageCompilationSource> {
18
+ static async loadTemplate(file: string): Promise<EmailCompileContext> {
19
19
  const entry = RootIndex.getEntry(file);
20
20
  if (!entry) {
21
21
  throw new Error(`Unable to find template for ${file}`);
22
22
  }
23
23
  const root = (await import(entry.outputFile)).default;
24
- const res: MessageCompilationSource = { ...await root.wrap(), file: entry.sourceFile };
24
+ const og: EmailCompileSource = await root.wrap();
25
+ const res = {
26
+ file: entry.sourceFile,
27
+ module: entry.module,
28
+ ...og
29
+ };
30
+
25
31
  const mod = RootIndex.getModule(entry.module)!;
26
32
 
27
33
  const resourcePaths = [
@@ -51,7 +57,7 @@ export class EmailCompiler {
51
57
  /**
52
58
  * Get output files
53
59
  */
54
- static getOutputFiles(file: string): MessageCompiled {
60
+ static getOutputFiles(file: string): EmailCompiled {
55
61
  const entry = RootIndex.getEntry(file)!;
56
62
  const mod = RootIndex.getModule(entry.module)!;
57
63
  return EmailCompileUtil.getOutputs(file, path.join(mod.sourcePath, 'resources'));
@@ -67,12 +73,12 @@ export class EmailCompiler {
67
73
  /**
68
74
  * Write template to file
69
75
  */
70
- static async writeTemplate(file: string, msg: MessageCompiled): Promise<void> {
76
+ static async writeTemplate(file: string, msg: EmailCompiled): Promise<void> {
71
77
  const outs = this.getOutputFiles(file);
72
78
  await Promise.all(TypedObject.keys(outs).map(async k => {
73
79
  if (msg[k]) {
74
80
  await fs.mkdir(path.dirname(outs[k]), { recursive: true });
75
- await fs.writeFile(outs[k], msg[k], { encoding: 'utf8' });
81
+ await fs.writeFile(outs[k], MailUtil.buildBrand(file, msg[k], 'trv email:compile'), { encoding: 'utf8' });
76
82
  } else {
77
83
  await fs.unlink(outs[k]).catch(() => { }); // Remove file if data not provided
78
84
  }
@@ -82,7 +88,7 @@ export class EmailCompiler {
82
88
  /**
83
89
  * Compile a file given a resource provider
84
90
  */
85
- static async compile(file: string, persist: boolean = false): Promise<MessageCompiled> {
91
+ static async compile(file: string, persist: boolean = false): Promise<EmailCompiled> {
86
92
  const src = await this.loadTemplate(file);
87
93
  const compiled = await EmailCompileUtil.compile(src);
88
94
  if (persist) {
@@ -128,6 +134,7 @@ export class EmailCompiler {
128
134
  try {
129
135
  if (EmailCompileUtil.isTemplateFile(file)) {
130
136
  await this.compile(file, true);
137
+ console.log(`Successfully compiled ${1} templates`, { changed: [file] });
131
138
  yield file;
132
139
  } else if (VALID_FILE(file)) {
133
140
  const rootFile = file.replace(/\/resources.*/, '/package.json');
package/src/util.ts CHANGED
@@ -2,11 +2,13 @@ import util from 'util';
2
2
  import { Readable } from 'stream';
3
3
 
4
4
  import { FileResourceProvider, StreamUtil } from '@travetto/base';
5
- import { MessageCompilationImages, MessageCompilationSource, MessageCompilationStyles, MessageCompiled } from '@travetto/email';
5
+ import {
6
+ EmailTemplateImageConfig, EmailTemplateStyleConfig,
7
+ EmailCompiled, EmailCompileContext
8
+ } from '@travetto/email';
6
9
  import { ImageConverter } from '@travetto/image';
7
10
  import { path } from '@travetto/manifest';
8
11
 
9
-
10
12
  type Tokenized = {
11
13
  text: string;
12
14
  tokens: Map<string, string>;
@@ -42,7 +44,7 @@ export class EmailCompileUtil {
42
44
  /**
43
45
  * Get the different parts from the file name
44
46
  */
45
- static getOutputs(file: string, prefix?: string): MessageCompiled {
47
+ static getOutputs(file: string, prefix?: string): EmailCompiled {
46
48
  return {
47
49
  html: this.buildOutputPath(file, '.compiled.html', prefix),
48
50
  subject: this.buildOutputPath(file, '.compiled.subject', prefix),
@@ -132,7 +134,7 @@ export class EmailCompileUtil {
132
134
  /**
133
135
  * Inline image sources
134
136
  */
135
- static async inlineImages(html: string, opts: MessageCompilationImages): Promise<string> {
137
+ static async inlineImages(html: string, opts: EmailTemplateImageConfig): Promise<string> {
136
138
  const { tokens, finalize } = await this.tokenizeResources(html, this.#HTML_CSS_IMAGE_URLS);
137
139
  const pendingImages: [token: string, ext: string, stream: Readable | Promise<Readable>][] = [];
138
140
  const resources = new FileResourceProvider(opts.search ?? []);
@@ -157,6 +159,7 @@ export class EmailCompileUtil {
157
159
  */
158
160
  static handleHtmlEdgeCases(html: string): string {
159
161
  return html
162
+ .replace(/\n{3,100}/msg, '\n\n')
160
163
  .replace(/<(meta|img|link|hr|br)[^>]*>/g, a => a.replace('>', '/>')) // Fix self closing
161
164
  .replace(/&apos;/g, '&#39;') // Fix apostrophes, as outlook hates them
162
165
  .replace(/(background(?:-color)?:\s*)([#0-9a-f]{6,8})([^>.#,]+)>/ig,
@@ -164,14 +167,16 @@ export class EmailCompileUtil {
164
167
  .replace(/<([^>]+vertical-align:\s*(top|bottom|middle)[^>]+)>/g,
165
168
  (a, tag, valign) => tag.indexOf('valign') ? `<${tag}>` : `<${tag} valign="${valign}">`) // Vertically align if it has the style
166
169
  .replace(/<(table[^>]+expand[^>]+width:\s*)(100%\s+!important)([^>]+)>/g,
167
- (a, left, size, right) => `<${left}100%${right}>`); // Drop important as a fix for outlook
170
+ (a, left, size, right) => `<${left}100%${right}>`) // Drop important as a fix for outlook
171
+ .trim()
172
+ .concat('\n');
168
173
  }
169
174
 
170
175
 
171
176
  /**
172
177
  * Apply styles into a given html document
173
178
  */
174
- static async applyStyles(html: string, opts: MessageCompilationStyles): Promise<string> {
179
+ static async applyStyles(html: string, opts: EmailTemplateStyleConfig): Promise<string> {
175
180
  const styles: string[] = [];
176
181
 
177
182
  if (opts.global) {
@@ -200,11 +205,11 @@ export class EmailCompileUtil {
200
205
  return html;
201
206
  }
202
207
 
203
- static async compile(src: MessageCompilationSource): Promise<MessageCompiled> {
204
- const subject = await this.simplifiedText(await src.subject());
205
- const text = await this.simplifiedText(await src.text());
208
+ static async compile(src: EmailCompileContext): Promise<EmailCompiled> {
209
+ const subject = await this.simplifiedText(await src.subject(src));
210
+ const text = await this.simplifiedText(await src.text(src));
206
211
 
207
- let html = await src.html();
212
+ let html = await src.html(src);
208
213
 
209
214
  if (src.styles?.inline !== false) {
210
215
  html = await this.applyStyles(html, src.styles!);
@@ -216,6 +221,7 @@ export class EmailCompileUtil {
216
221
  if (src.images?.inline !== false) {
217
222
  html = await this.inlineImages(html, src.images!);
218
223
  }
224
+
219
225
  return { html, subject, text };
220
226
  }
221
227
  }
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs/promises';
2
2
 
3
- import { path } from '@travetto/manifest';
3
+ import { RootIndex, path } from '@travetto/manifest';
4
4
  import { YamlUtil } from '@travetto/yaml';
5
5
 
6
6
  interface ConfigType {
@@ -22,7 +22,7 @@ interface ConfigType {
22
22
  */
23
23
  export class $EditorConfig {
24
24
 
25
- #configFile = path.resolve('resources/email/dev.yml');
25
+ #configFile: Record<string, string> = {};
26
26
  #defaultConfig = {
27
27
  to: 'my-email@gmail.com',
28
28
  from: 'from-email@gmail.com',
@@ -42,9 +42,10 @@ export class $EditorConfig {
42
42
  /**
43
43
  *
44
44
  */
45
- async get(): Promise<ConfigType> {
45
+ async get(file: string): Promise<ConfigType> {
46
46
  try {
47
- const content = await fs.readFile(this.#configFile, 'utf8');
47
+ const mod = RootIndex.getModuleFromSource(file)!.name;
48
+ const content = await fs.readFile(this.#configFile[mod], 'utf8');
48
49
  return YamlUtil.parse<ConfigType>(content);
49
50
  } catch {
50
51
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@@ -52,13 +53,13 @@ export class $EditorConfig {
52
53
  }
53
54
  }
54
55
 
55
- async getContext(): Promise<Exclude<ConfigType['context'], undefined>> {
56
- const conf = await this.get();
56
+ async getContext(file: string): Promise<Exclude<ConfigType['context'], undefined>> {
57
+ const conf = await this.get(file);
57
58
  return conf.context ?? {};
58
59
  }
59
60
 
60
- async getSenderConfig(): Promise<Exclude<ConfigType['sender'], undefined>> {
61
- const conf = await this.get();
61
+ async getSenderConfig(file: string): Promise<Exclude<ConfigType['sender'], undefined>> {
62
+ const conf = await this.get(file);
62
63
  return conf.sender ?? {};
63
64
  }
64
65
 
@@ -66,13 +67,15 @@ export class $EditorConfig {
66
67
  return YamlUtil.serialize(this.#defaultConfig);
67
68
  }
68
69
 
69
- async ensureConfig(): Promise<string> {
70
- const file = this.#configFile;
71
- if (!(await fs.stat(file).catch(() => { }))) {
72
- await fs.mkdir(path.dirname(file), { recursive: true });
73
- await fs.writeFile(file, this.getDefaultConfig(), { encoding: 'utf8' });
70
+ async ensureConfig(file: string): Promise<string> {
71
+ console.log('Ensuring config', file);
72
+ const mod = RootIndex.getModuleFromSource(file)!;
73
+ const resolved = this.#configFile[mod.name] ??= path.resolve(mod.sourcePath, 'resources/email/dev.yml');
74
+ if (!(await fs.stat(resolved).catch(() => { }))) {
75
+ await fs.mkdir(path.dirname(resolved), { recursive: true });
76
+ await fs.writeFile(resolved, this.getDefaultConfig(), { encoding: 'utf8' });
74
77
  }
75
- return file;
78
+ return resolved;
76
79
  }
77
80
  }
78
81
 
@@ -1,3 +1,5 @@
1
+ import { ShutdownManager } from '@travetto/base';
2
+
1
3
  import { EmailCompilationManager } from './manager';
2
4
  import { EditorSendService } from './send';
3
5
  import { EditorConfig } from './config';
@@ -6,7 +8,7 @@ import { EmailCompiler } from '../../src/compiler';
6
8
  import { EmailCompileUtil } from '../../src/util';
7
9
 
8
10
  type InboundMessage =
9
- { type: 'configure' } |
11
+ { type: 'configure', file: string } |
10
12
  { type: 'redraw', file: string } |
11
13
  { type: 'send', file: string, from?: string, to?: string };
12
14
 
@@ -23,12 +25,10 @@ type OutboundMessage =
23
25
  export class EditorState {
24
26
 
25
27
  #lastFile = '';
26
- #sender: EditorSendService;
27
28
  #template: EmailCompilationManager;
28
29
 
29
30
  constructor(template: EmailCompilationManager) {
30
31
  this.#template = template;
31
- this.#sender = new EditorSendService();
32
32
  }
33
33
 
34
34
  async renderFile(file: string): Promise<void> {
@@ -36,7 +36,7 @@ export class EditorState {
36
36
  if (file) {
37
37
  try {
38
38
  const content = await this.#template.resolveCompiledTemplate(
39
- file, await EditorConfig.getContext()
39
+ file, await EditorConfig.getContext(file)
40
40
  );
41
41
  this.response({
42
42
  type: 'changed',
@@ -60,7 +60,7 @@ export class EditorState {
60
60
  }
61
61
 
62
62
  async onConfigure(msg: InboundMessage & { type: 'configure' }): Promise<void> {
63
- this.response({ type: 'configured', file: await EditorConfig.ensureConfig() });
63
+ this.response({ type: 'configured', file: await EditorConfig.ensureConfig(msg.file) });
64
64
  }
65
65
 
66
66
  async #onRedraw(msg: InboundMessage & { type: 'redraw' }): Promise<void> {
@@ -77,15 +77,15 @@ export class EditorState {
77
77
  }
78
78
 
79
79
  async onSend(msg: InboundMessage & { type: 'send' }): Promise<void> {
80
- const cfg = await EditorConfig.get();
80
+ const cfg = await EditorConfig.get(msg.file);
81
81
  const to = msg.to || cfg.to;
82
82
  const from = msg.from || cfg.from;
83
83
  const content = await this.#template.resolveCompiledTemplate(
84
- msg.file!, await EditorConfig.getContext()
84
+ msg.file, await EditorConfig.getContext(msg.file)
85
85
  );
86
86
 
87
87
  try {
88
- const url = await this.#sender.sendEmail({ from, to, ...content, });
88
+ const url = await EditorSendService.sendEmail(msg.file, { from, to, ...content, });
89
89
  this.response({ type: 'sent', to, file: msg.file, ...url });
90
90
  } catch (err) {
91
91
  if (err && err instanceof Error) {
@@ -107,6 +107,10 @@ export class EditorState {
107
107
  case 'send': this.onSend(msg); break;
108
108
  }
109
109
  });
110
+
111
+ process.on('disconnect', () => ShutdownManager.execute());
112
+ process.send?.('ready');
113
+
110
114
  for await (const f of EmailCompiler.watchCompile()) {
111
115
  await this.renderFile(f);
112
116
  }
@@ -1,9 +1,9 @@
1
1
  import fs from 'fs/promises';
2
2
 
3
- import type { MailTemplateEngine, MessageCompiled } from '@travetto/email';
3
+ import { type MailInterpolator, type EmailCompiled } from '@travetto/email';
4
4
  import { DependencyRegistry } from '@travetto/di';
5
5
  import { TypedObject } from '@travetto/base';
6
- import { MailTemplateEngineTarget } from '@travetto/email/src/internal/types';
6
+ import { MailInterpolatorTarget } from '@travetto/email/src/internal/types';
7
7
 
8
8
  import { EmailCompiler } from '../../src/compiler';
9
9
 
@@ -14,20 +14,20 @@ export class EmailCompilationManager {
14
14
 
15
15
  static async createInstance(): Promise<EmailCompilationManager> {
16
16
  return new EmailCompilationManager(
17
- await DependencyRegistry.getInstance<MailTemplateEngine>(MailTemplateEngineTarget),
17
+ await DependencyRegistry.getInstance<MailInterpolator>(MailInterpolatorTarget),
18
18
  );
19
19
  }
20
20
 
21
- engine: MailTemplateEngine;
21
+ engine: MailInterpolator;
22
22
 
23
- constructor(engine: MailTemplateEngine) {
23
+ constructor(engine: MailInterpolator) {
24
24
  this.engine = engine;
25
25
  }
26
26
 
27
27
  /**
28
28
  * Resolve template
29
29
  */
30
- async resolveTemplateParts(file: string): Promise<MessageCompiled> {
30
+ async resolveTemplateParts(file: string): Promise<EmailCompiled> {
31
31
  const files = EmailCompiler.getOutputFiles(file);
32
32
  const missing = await Promise.all(Object.values(files).map(x => fs.stat(file).catch(() => { })));
33
33
 
@@ -41,21 +41,20 @@ export class EmailCompilationManager {
41
41
  .then(content => [key, content] as const)
42
42
  )
43
43
  );
44
- return TypedObject.fromEntries<keyof MessageCompiled, string>(parts);
44
+ return TypedObject.fromEntries<keyof EmailCompiled, string>(parts);
45
45
  }
46
46
 
47
47
  /**
48
48
  * Render
49
49
  * @param rel
50
50
  */
51
- async resolveCompiledTemplate(rel: string, context: Record<string, unknown>): Promise<MessageCompiled> {
51
+ async resolveCompiledTemplate(rel: string, context: Record<string, unknown>): Promise<EmailCompiled> {
52
+ const { MailUtil } = await import('@travetto/email');
52
53
  const { html, text, subject } = await this.resolveTemplateParts(rel);
53
54
 
54
- return {
55
- html: await this.engine.template(html, context),
56
- text: await this.engine.template(text, context),
57
- subject: await this.engine.template(subject, context),
58
- };
59
- }
55
+ const get = (input: string): Promise<string> =>
56
+ Promise.resolve(this.engine.render(input, context)).then(MailUtil.purgeBrand);
60
57
 
58
+ return { html: await get(html), text: await get(text), subject: await get(subject) };
59
+ }
61
60
  }
@@ -1,22 +1,25 @@
1
- import { MailService, MessageOptions, SentMessage } from '@travetto/email';
1
+ import { MailService, EmailOptions, SentEmail } from '@travetto/email';
2
2
  import { MailTransportTarget } from '@travetto/email/src/internal/types';
3
3
  import { DependencyRegistry } from '@travetto/di';
4
4
 
5
5
  import { EditorConfig } from './config';
6
+ import { RootIndex } from '@travetto/manifest';
6
7
 
7
8
  /**
8
9
  * Util for sending emails
9
10
  */
10
11
  export class EditorSendService {
11
12
 
12
- #svc: MailService;
13
+ static #svc: Record<string, MailService> = {};
13
14
 
14
15
  /**
15
16
  * Get mail service
16
17
  */
17
- async getMailService(): Promise<MailService> {
18
- if (!this.#svc) {
19
- const senderConfig = await EditorConfig.getSenderConfig();
18
+ static async getMailService(file: string): Promise<MailService> {
19
+ const mod = RootIndex.getModuleFromSource(file)!.name;
20
+
21
+ if (!this.#svc[mod]) {
22
+ const senderConfig = await EditorConfig.getSenderConfig(file);
20
23
 
21
24
  if (senderConfig?.host?.includes('ethereal.email')) {
22
25
  const cls = class { };
@@ -39,30 +42,30 @@ ${EditorConfig.getDefaultConfig()}`.trim();
39
42
  throw new Error(errorMessage);
40
43
  }
41
44
 
42
- this.#svc = await DependencyRegistry.getInstance(MailService);
45
+ this.#svc[mod] = await DependencyRegistry.getInstance(MailService);
43
46
  }
44
- return this.#svc;
47
+ return this.#svc[mod];
45
48
  }
46
49
 
47
50
  /**
48
51
  * Resolve template
49
52
  */
50
- async sendEmail(message: MessageOptions): Promise<{
53
+ static async sendEmail(file: string, message: EmailOptions): Promise<{
51
54
  url?: string | false;
52
55
  }> {
53
56
  const to = message.to!;
54
57
  try {
55
58
  console.log('Sending email', { to });
56
59
  // Let the engine template
57
- const svc = await this.getMailService();
60
+ const svc = await this.getMailService(file);
58
61
  if (!svc) {
59
62
  throw new Error('Node mailer support is missing');
60
63
  }
61
64
 
62
- const info = await svc.send<{ host?: string } & SentMessage>(message);
65
+ const info = await svc.send<{ host?: string } & SentEmail>(message);
63
66
  console.log('Sent email', { to });
64
67
 
65
- const senderConfig = await EditorConfig.getSenderConfig();
68
+ const senderConfig = await EditorConfig.getSenderConfig(file);
66
69
  return senderConfig.host?.includes('ethereal.email') ? {
67
70
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
68
71
  url: (await import('nodemailer')).getTestMessageUrl(info as any)
@@ -16,7 +16,8 @@ export class EmailCompileCommand implements CliCommandShape {
16
16
  envInit(): GlobalEnvConfig {
17
17
  return {
18
18
  debug: false,
19
- dynamic: this.watch
19
+ dynamic: this.watch,
20
+ profiles: ['email-dev']
20
21
  };
21
22
  }
22
23
 
@@ -1,6 +1,5 @@
1
- import { GlobalEnvConfig, ShutdownManager } from '@travetto/base';
1
+ import { GlobalEnvConfig } from '@travetto/base';
2
2
  import { CliCommand } from '@travetto/cli';
3
- import { RootIndex } from '@travetto/manifest';
4
3
  import { RootRegistry } from '@travetto/registry';
5
4
 
6
5
  import { EditorState } from './bin/editor';
@@ -13,17 +12,13 @@ export class EmailEditorCommand {
13
12
  envInit(): GlobalEnvConfig {
14
13
  return {
15
14
  envName: 'dev',
16
- resourcePaths: [`${RootIndex.getModule('@travetto/email-compiler')!.sourcePath}/resources`]
15
+ dynamic: true,
16
+ profiles: ['email-dev']
17
17
  };
18
18
  }
19
19
 
20
20
  async main(): Promise<void> {
21
21
  await RootRegistry.init();
22
- const editor = new EditorState(await EmailCompilationManager.createInstance());
23
- await editor.init();
24
- if (process.send) {
25
- process.on('disconnect', () => ShutdownManager.execute());
26
- process.send('ready');
27
- }
22
+ await new EditorState(await EmailCompilationManager.createInstance()).init();
28
23
  }
29
24
  }
@@ -16,7 +16,10 @@ import { EmailCompiler } from '../src/compiler';
16
16
  export class EmailTestCommand implements CliCommandShape {
17
17
 
18
18
  envInit(): GlobalEnvConfig {
19
- return { envName: 'dev' };
19
+ return {
20
+ envName: 'dev',
21
+ profiles: ['email-dev']
22
+ };
20
23
  }
21
24
 
22
25
  async main(file: string, to: string): Promise<void> {
@@ -25,10 +28,9 @@ export class EmailTestCommand implements CliCommandShape {
25
28
  await EmailCompiler.compile(file, true);
26
29
 
27
30
  const mgr = await EmailCompilationManager.createInstance();
28
- const send = new EditorSendService();
29
- const cfg = await EditorConfig.get();
31
+ const cfg = await EditorConfig.get(file);
32
+ const content = await mgr.resolveCompiledTemplate(file, await EditorConfig.getContext(file));
30
33
 
31
- const content = await mgr.resolveCompiledTemplate(file!, await EditorConfig.getContext());
32
- await send.sendEmail({ from: cfg.from, to, ...content, });
34
+ await EditorSendService.sendEmail(file, { from: cfg.from, to, ...content, });
33
35
  }
34
36
  }