@toa.io/extensions.mail 1.0.0-alpha.146 → 1.0.0-alpha.149

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.
@@ -5,21 +5,23 @@ operations:
5
5
  send:
6
6
  input:
7
7
  properties:
8
+ from: { type: string }
8
9
  to: { type: string }
9
10
  subject: { type: string }
10
11
  text: { type: string }
11
- template: { type: string }
12
- data: { type: object, default: { } }
13
- required: [to]
14
- oneOf:
15
- - required: [subject, text]
16
- - required: [template]
12
+ required: [from, to, subject, text]
17
13
 
18
14
  configuration:
19
15
  schema:
20
16
  properties:
21
- provider: { type: string, enum: [Console, Resend] }
22
- from: { type: string, format: email }
23
- templates: { type: string, format: uri }
24
- options: { type: object } # provider-specific options
25
- required: [provider, from]
17
+ provider:
18
+ type: string
19
+ enum: [Console, Resend]
20
+ domains:
21
+ description: Allowed sender domains
22
+ type: array
23
+ items: { type: string }
24
+ options:
25
+ description: Provider specific options
26
+ type: object
27
+ required: [provider, domains]
@@ -1,4 +1,4 @@
1
- const { assert } = require('node:assert')
1
+ const assert = require('node:assert')
2
2
  const { Resend: Client } = require('resend')
3
3
 
4
4
  class Resend {
@@ -1,14 +1,10 @@
1
1
  const assert = require('node:assert')
2
+ const { Err } = require('error-value')
2
3
  const providers = require('./providers')
3
- const { load: parse } = require('cheerio')
4
4
 
5
5
  class Effect {
6
- /**
7
- * Base url for rendering
8
- */
9
- base
10
- from
11
6
  provider
7
+ config
12
8
  logs
13
9
 
14
10
  mount (context) {
@@ -17,27 +13,18 @@ class Effect {
17
13
  const Provider = providers[context.configuration.provider]
18
14
 
19
15
  this.provider = new Provider(context)
20
- this.from = context.configuration.from
21
- this.base = context.configuration.templates
16
+ this.config = context.configuration
22
17
  this.logs = context.logs
23
18
  }
24
19
 
25
- async execute ({ to, subject, text, template, data }) {
26
- if (to.endsWith('.null')) {
27
- this.logs.debug('Mail skipped', { to, template })
20
+ async execute (message) {
21
+ if (this.invalidSender(message.from))
22
+ return ERR_INVALID_SENDER
28
23
 
29
- return
30
- }
31
-
32
- const properties = template === undefined ? { text } : await this.html(template, data)
33
-
34
- if (subject !== undefined)
35
- properties.subject = subject
24
+ if (message.to.endsWith('.null')) {
25
+ this.logs.debug('Mail skipped', { to: message.to })
36
26
 
37
- const message = {
38
- ...properties,
39
- to,
40
- from: this.from
27
+ return
41
28
  }
42
29
 
43
30
  this.logs.debug('Sending mail', message)
@@ -45,39 +32,17 @@ class Effect {
45
32
  await this.provider.send(message)
46
33
  }
47
34
 
48
- async html (template, data) {
49
- const { title, html } = await this.render(template, data)
50
-
51
- /** @type {toa.extensions.mail.Message} */
52
- return { subject: title, html }
53
- }
54
-
55
- async render (template, data) {
56
- assert.ok(this.base !== undefined, 'Base url for rendering is not set, cannot send HTML mail')
57
-
58
- const url = new URL(`./${template}/`, this.base).href
59
-
60
- this.logs.debug('Requesting render', { url, data })
61
-
62
- const response = await fetch(url, {
63
- method: 'POST',
64
- headers: {
65
- 'content-type': 'application/json'
66
- },
67
- body: JSON.stringify(data)
68
- })
35
+ invalidSender (from) {
36
+ const domain = from.split('@')[1]
37
+ const invalid = !this.config.domains.includes(domain)
69
38
 
70
- const type = response.headers.get('content-type')
39
+ if (invalid)
40
+ this.logs.debug('Sender domain not allowed', { domain })
71
41
 
72
- assert.ok(response.status >= 200 && response.status < 300, `Failed to render ${response.status}`)
73
- assert.ok(type === 'text/html', `Rendering reply must be text/html, ${type} received`)
74
-
75
- const html = await response.text()
76
- const $ = parse(html)
77
- const title = $('title').text()
78
-
79
- return { title, html }
42
+ return invalid
80
43
  }
81
44
  }
82
45
 
46
+ const ERR_INVALID_SENDER = new Err('INVALID_SENDER')
47
+
83
48
  exports.Effect = Effect
@@ -1,49 +1,50 @@
1
1
  Feature: Sending an email
2
2
 
3
- Scenario: Sending an email
3
+ Scenario: Sending text email
4
4
  Given the `mail.agent` configuration:
5
5
  """yaml
6
- templates: http://localhost:8088/emails/
7
6
  provider: Console
8
- from: noreply@nobody.null
7
+ domains: [nobody.null]
9
8
  """
10
9
  And the service is running
11
10
  And the spam is running
12
- And rendering is sending:
13
- """html
14
- <head>
15
- <title>Hello!</title>
16
- </head>
17
- <body>
18
- <p>This is a beautiful email</p>
19
- </body>
20
- """
21
11
  When `spam.send` is called:
22
12
  """yaml
13
+ from: alice@nobody.null
23
14
  to: no@one
24
- template: hello
25
- """
26
- When `spam.send` is called:
27
- """yaml
28
- to: some@one
29
- template: bye
30
- data:
31
- name: Some One
15
+ subject: Test
16
+ text: hello!
32
17
  """
33
- Then go check the email or logs
34
-
35
- Scenario: Sending text email
36
- Given the `mail.agent` configuration:
18
+ And `spam.send` is called:
37
19
  """yaml
38
- provider: Console
39
- from: noreply@nobody.null
20
+ from: bob@nobody.null
21
+ to: no@one
22
+ subject: Test
23
+ text: hello!
40
24
  """
41
- And the service is running
42
- And the spam is running
43
- When `spam.send` is called:
25
+ And `spam.send` is called:
44
26
  """yaml
27
+ from: charlie@somebody.null
45
28
  to: no@one
46
29
  subject: Test
47
- text: hello!
30
+ text: This won't work, domain is not allowed
48
31
  """
49
32
  Then go check the email or logs
33
+
34
+ # Scenario: Sending text email via Resend
35
+ # Given the `mail.agent` configuration:
36
+ # """yaml
37
+ # provider: Resend
38
+ # domains: [resend.dev]
39
+ # options:
40
+ # key: << PUT KEY HERE >>
41
+ # """
42
+ # And the service is running
43
+ # And the spam is running
44
+ # When `spam.send` is called:
45
+ # """yaml
46
+ # from: onboarding@resend.dev
47
+ # to: tema.gurtovoy@gmail.com
48
+ # subject: Sent via Resend
49
+ # text: Ok.
50
+ # """
@@ -3,16 +3,11 @@ name: spam
3
3
  operations:
4
4
  send:
5
5
  input:
6
- type: object
7
6
  properties:
7
+ from: { type: string }
8
8
  to: { type: string }
9
9
  subject: { type: string }
10
10
  text: { type: string }
11
- template: { type: string }
12
- data: { type: object }
13
- required: [to]
14
- oneOf:
15
- - required: [subject, text]
16
- - required: [template]
11
+ required: [from, to, subject, text]
17
12
 
18
13
  mail: ~
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/extensions.mail",
3
- "version": "1.0.0-alpha.146",
3
+ "version": "1.0.0-alpha.149",
4
4
  "description": "Toa Mail",
5
5
  "author": "temich <tema.gurtovoy@gmail.com>",
6
6
  "homepage": "https://github.com/toa-io/toa#readme",
@@ -26,8 +26,8 @@
26
26
  "@toa.io/core": "1.0.0-alpha.136",
27
27
  "@toa.io/generic": "1.0.0-alpha.93",
28
28
  "@toa.io/schemas": "1.0.0-alpha.143",
29
- "cheerio": "1.0.0",
29
+ "error-value": "0.4.4",
30
30
  "resend": "4.1.2"
31
31
  },
32
- "gitHead": "4e201b67db78998cbdca33c5abf7bf3493b85ff0"
32
+ "gitHead": "add1742266b0203944346a95ca6e9564b9725276"
33
33
  }