@teleporthq/teleport-plugin-next-workflows 0.44.9 → 0.44.11

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.
Files changed (82) hide show
  1. package/__tests__/ai-error-contract.test.ts +10 -0
  2. package/__tests__/continue-on-error.test.ts +27 -0
  3. package/__tests__/email-node-recipients.test.ts +288 -0
  4. package/__tests__/security-runtime-shadow.test.ts +2 -1
  5. package/__tests__/sent-email-log-module.test.ts +54 -0
  6. package/dist/cjs/executor-generator.d.ts.map +1 -1
  7. package/dist/cjs/executor-generator.js +4 -1
  8. package/dist/cjs/executor-generator.js.map +1 -1
  9. package/dist/cjs/nodes/email/email-mailersend.d.ts.map +1 -1
  10. package/dist/cjs/nodes/email/email-mailersend.js +55 -48
  11. package/dist/cjs/nodes/email/email-mailersend.js.map +1 -1
  12. package/dist/cjs/nodes/email/email-mailgun.d.ts.map +1 -1
  13. package/dist/cjs/nodes/email/email-mailgun.js +44 -32
  14. package/dist/cjs/nodes/email/email-mailgun.js.map +1 -1
  15. package/dist/cjs/nodes/email/email-postmark.d.ts.map +1 -1
  16. package/dist/cjs/nodes/email/email-postmark.js +44 -31
  17. package/dist/cjs/nodes/email/email-postmark.js.map +1 -1
  18. package/dist/cjs/nodes/email/email-recipients.d.ts +28 -0
  19. package/dist/cjs/nodes/email/email-recipients.d.ts.map +1 -0
  20. package/dist/cjs/nodes/email/email-recipients.js +74 -0
  21. package/dist/cjs/nodes/email/email-recipients.js.map +1 -0
  22. package/dist/cjs/nodes/email/email-resend.d.ts.map +1 -1
  23. package/dist/cjs/nodes/email/email-resend.js +42 -28
  24. package/dist/cjs/nodes/email/email-resend.js.map +1 -1
  25. package/dist/cjs/nodes/email/email-sendgrid.d.ts.map +1 -1
  26. package/dist/cjs/nodes/email/email-sendgrid.js +56 -41
  27. package/dist/cjs/nodes/email/email-sendgrid.js.map +1 -1
  28. package/dist/cjs/nodes/general/general-custom-js.d.ts.map +1 -1
  29. package/dist/cjs/nodes/general/general-custom-js.js +2 -1
  30. package/dist/cjs/nodes/general/general-custom-js.js.map +1 -1
  31. package/dist/cjs/security-scanner.d.ts +1 -1
  32. package/dist/cjs/security-scanner.d.ts.map +1 -1
  33. package/dist/cjs/security-scanner.js +1 -0
  34. package/dist/cjs/security-scanner.js.map +1 -1
  35. package/dist/cjs/sent-email-log/sent-email-log-code.d.ts.map +1 -1
  36. package/dist/cjs/sent-email-log/sent-email-log-code.js +17 -1
  37. package/dist/cjs/sent-email-log/sent-email-log-code.js.map +1 -1
  38. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  39. package/dist/esm/executor-generator.d.ts.map +1 -1
  40. package/dist/esm/executor-generator.js +4 -1
  41. package/dist/esm/executor-generator.js.map +1 -1
  42. package/dist/esm/nodes/email/email-mailersend.d.ts.map +1 -1
  43. package/dist/esm/nodes/email/email-mailersend.js +55 -48
  44. package/dist/esm/nodes/email/email-mailersend.js.map +1 -1
  45. package/dist/esm/nodes/email/email-mailgun.d.ts.map +1 -1
  46. package/dist/esm/nodes/email/email-mailgun.js +44 -32
  47. package/dist/esm/nodes/email/email-mailgun.js.map +1 -1
  48. package/dist/esm/nodes/email/email-postmark.d.ts.map +1 -1
  49. package/dist/esm/nodes/email/email-postmark.js +44 -31
  50. package/dist/esm/nodes/email/email-postmark.js.map +1 -1
  51. package/dist/esm/nodes/email/email-recipients.d.ts +28 -0
  52. package/dist/esm/nodes/email/email-recipients.d.ts.map +1 -0
  53. package/dist/esm/nodes/email/email-recipients.js +71 -0
  54. package/dist/esm/nodes/email/email-recipients.js.map +1 -0
  55. package/dist/esm/nodes/email/email-resend.d.ts.map +1 -1
  56. package/dist/esm/nodes/email/email-resend.js +42 -28
  57. package/dist/esm/nodes/email/email-resend.js.map +1 -1
  58. package/dist/esm/nodes/email/email-sendgrid.d.ts.map +1 -1
  59. package/dist/esm/nodes/email/email-sendgrid.js +56 -41
  60. package/dist/esm/nodes/email/email-sendgrid.js.map +1 -1
  61. package/dist/esm/nodes/general/general-custom-js.d.ts.map +1 -1
  62. package/dist/esm/nodes/general/general-custom-js.js +2 -1
  63. package/dist/esm/nodes/general/general-custom-js.js.map +1 -1
  64. package/dist/esm/security-scanner.d.ts +1 -1
  65. package/dist/esm/security-scanner.d.ts.map +1 -1
  66. package/dist/esm/security-scanner.js +1 -0
  67. package/dist/esm/security-scanner.js.map +1 -1
  68. package/dist/esm/sent-email-log/sent-email-log-code.d.ts.map +1 -1
  69. package/dist/esm/sent-email-log/sent-email-log-code.js +17 -1
  70. package/dist/esm/sent-email-log/sent-email-log-code.js.map +1 -1
  71. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  72. package/package.json +4 -4
  73. package/src/executor-generator.ts +4 -1
  74. package/src/nodes/email/email-mailersend.ts +72 -55
  75. package/src/nodes/email/email-mailgun.ts +51 -36
  76. package/src/nodes/email/email-postmark.ts +51 -35
  77. package/src/nodes/email/email-recipients.ts +86 -0
  78. package/src/nodes/email/email-resend.ts +49 -31
  79. package/src/nodes/email/email-sendgrid.ts +63 -44
  80. package/src/nodes/general/general-custom-js.ts +2 -1
  81. package/src/security-scanner.ts +1 -0
  82. package/src/sent-email-log/sent-email-log-code.ts +17 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teleporthq/teleport-plugin-next-workflows",
3
- "version": "0.44.9",
3
+ "version": "0.44.11",
4
4
  "description": "A plugin for workflow code generation in Next.js projects.",
5
5
  "author": "teleportHQ",
6
6
  "license": "MIT",
@@ -66,8 +66,8 @@
66
66
  },
67
67
  "dependencies": {
68
68
  "@babel/types": "^7.5.5",
69
- "@teleporthq/teleport-shared": "^0.44.7",
70
- "@teleporthq/teleport-types": "^0.44.0"
69
+ "@teleporthq/teleport-shared": "^0.44.11",
70
+ "@teleporthq/teleport-types": "^0.44.11"
71
71
  },
72
- "gitHead": "873247c0350141f525275e7c31a6e7dbf183ce4f"
72
+ "gitHead": "1947ff6b13b1b35ece621e04d28507185a3220ff"
73
73
  }
@@ -892,9 +892,12 @@ async function settlePendingNodePromises(context) {
892
892
  // ({ error: '...' } / { success: false }) or through the AI-node contract
893
893
  // ({ error: true, message, code } — provider/auth failures). Both must halt
894
894
  // the workflow; treating error:true as success used to let the pipeline limp
895
- // into downstream NOT NULL violations.
895
+ // into downstream NOT NULL violations. A result that says success: true is
896
+ // the node's own verdict: an email node that reached some of its recipients
897
+ // reports success with the refused addresses under error, and goes on.
896
898
  function isFatalNodeResult(result) {
897
899
  if (!result) return false;
900
+ if (result.success === true) return false;
898
901
  return result.success === false ||
899
902
  (typeof result.error === 'string' && result.error) ||
900
903
  result.error === true;
@@ -1,80 +1,94 @@
1
1
  import { NodeHandlerGenerator, handlerToString } from '../types'
2
+ import { assertHandlerHasNoModuleRefs } from '../analytics/commerce-tracking'
3
+ import { EMAIL_RECIPIENTS_HELPER_SOURCE, EmailDeliveriesPlan } from './email-recipients'
4
+
5
+ // Ambient, not imported: a serialized handler has no module scope, so
6
+ // generateHandler appends the recipients helper's source (beside this file).
7
+ declare function __emailRecipients(to: unknown): unknown[]
8
+ declare function __emailDeliveries(plan: EmailDeliveriesPlan): Promise<unknown>
2
9
 
3
10
  async function email_mailersend(config: any, context: Record<string, unknown>) {
4
11
  const apiKey = config.apiKey
5
12
  const from = config.from
6
13
  const fromName = config.fromName || ''
7
- const to = config.to
8
14
  const subject = config.subject
9
15
  const body = config.body
10
16
  const replyTo = config.replyTo
11
17
  const cc = config.cc
12
18
  const bcc = config.bcc
19
+ const recipients = __emailRecipients(config.to)
13
20
 
14
- try {
15
- const __nodeRequire =
16
- typeof __non_webpack_require__ !== 'undefined' ? __non_webpack_require__ : require
17
- const mailersendPkg = __nodeRequire('mailersend')
18
- const MailerSend = mailersendPkg.MailerSend
19
- const EmailParams = mailersendPkg.EmailParams
20
- const Sender = mailersendPkg.Sender
21
- const Recipient = mailersendPkg.Recipient
21
+ async function sendTo(to: any) {
22
+ try {
23
+ const __nodeRequire =
24
+ typeof __non_webpack_require__ !== 'undefined' ? __non_webpack_require__ : require
25
+ const mailersendPkg = __nodeRequire('mailersend')
26
+ const MailerSend = mailersendPkg.MailerSend
27
+ const EmailParams = mailersendPkg.EmailParams
28
+ const Sender = mailersendPkg.Sender
29
+ const Recipient = mailersendPkg.Recipient
22
30
 
23
- const mailerSend = new MailerSend({ apiKey })
24
- const sentFrom = new Sender(from, fromName)
31
+ const mailerSend = new MailerSend({ apiKey })
32
+ const sentFrom = new Sender(from, fromName)
25
33
 
26
- const toList = Array.isArray(to) ? to : [to]
27
- const recipients = toList.map(function (email: any) {
28
- if (typeof email === 'object' && email.email) {
29
- return new Recipient(email.email, email.name || '')
30
- }
31
- return new Recipient(email, '')
32
- })
34
+ const recipient =
35
+ typeof to === 'object' && to.email
36
+ ? new Recipient(to.email, to.name || '')
37
+ : new Recipient(to, '')
33
38
 
34
- const emailParams = new EmailParams()
35
- .setFrom(sentFrom)
36
- .setTo(recipients)
37
- .setSubject(subject)
38
- .setHtml(body)
39
+ const emailParams = new EmailParams()
40
+ .setFrom(sentFrom)
41
+ .setTo([recipient])
42
+ .setSubject(subject)
43
+ .setHtml(body)
39
44
 
40
- if (replyTo) {
41
- emailParams.setReplyTo(new Sender(replyTo))
42
- }
45
+ if (replyTo) {
46
+ emailParams.setReplyTo(new Sender(replyTo))
47
+ }
43
48
 
44
- if (cc) {
45
- const ccList = Array.isArray(cc) ? cc : [cc]
46
- emailParams.setCc(
47
- ccList.map(function (email: any) {
48
- return new Recipient(typeof email === 'object' ? email.email : email, '')
49
- })
50
- )
51
- }
49
+ if (cc) {
50
+ const ccList = Array.isArray(cc) ? cc : [cc]
51
+ emailParams.setCc(
52
+ ccList.map(function (email: any) {
53
+ return new Recipient(typeof email === 'object' ? email.email : email, '')
54
+ })
55
+ )
56
+ }
52
57
 
53
- if (bcc) {
54
- const bccList = Array.isArray(bcc) ? bcc : [bcc]
55
- emailParams.setBcc(
56
- bccList.map(function (email: any) {
57
- return new Recipient(typeof email === 'object' ? email.email : email, '')
58
- })
59
- )
60
- }
58
+ if (bcc) {
59
+ const bccList = Array.isArray(bcc) ? bcc : [bcc]
60
+ emailParams.setBcc(
61
+ bccList.map(function (email: any) {
62
+ return new Recipient(typeof email === 'object' ? email.email : email, '')
63
+ })
64
+ )
65
+ }
61
66
 
62
- const response = await mailerSend.email.send(emailParams)
63
- const statusCode = response && response.statusCode
67
+ const response = await mailerSend.email.send(emailParams)
68
+ const statusCode = response && response.statusCode
64
69
 
65
- if (statusCode === 202 || statusCode === 200) {
66
- const messageId = (response.headers && response.headers['x-message-id']) || ''
67
- return { messageId, success: true }
68
- }
70
+ if (statusCode === 202 || statusCode === 200) {
71
+ const messageId = (response.headers && response.headers['x-message-id']) || ''
72
+ return { messageId, success: true }
73
+ }
69
74
 
70
- return {
71
- messageId: '',
72
- success: false,
73
- error: (response.body && response.body.message) || 'Failed to send email',
75
+ return {
76
+ messageId: '',
77
+ success: false,
78
+ error: (response.body && response.body.message) || 'Failed to send email',
79
+ }
80
+ } catch (err: unknown) {
81
+ return { messageId: '', success: false, error: (err as Error).message }
74
82
  }
75
- } catch (err: unknown) {
76
- return { messageId: '', success: false, error: (err as Error).message }
77
83
  }
84
+
85
+ if (recipients.length === 0) {
86
+ return { messageId: '', success: false, error: 'No recipient' }
87
+ }
88
+ if (recipients.length === 1) {
89
+ return sendTo(recipients[0])
90
+ }
91
+ return __emailDeliveries({ recipients, send: sendTo, idKey: 'messageId' })
78
92
  }
79
93
  export const emailMailersend: NodeHandlerGenerator = {
80
94
  nodeType: 'email-mailersend',
@@ -83,6 +97,9 @@ export const emailMailersend: NodeHandlerGenerator = {
83
97
  mailersend: '^2.0.0',
84
98
  },
85
99
  generateHandler(): string {
86
- return handlerToString(email_mailersend)
100
+ return assertHandlerHasNoModuleRefs(
101
+ handlerToString(email_mailersend) + '\n' + EMAIL_RECIPIENTS_HELPER_SOURCE,
102
+ 'email-mailersend'
103
+ )
87
104
  },
88
105
  }
@@ -1,63 +1,74 @@
1
1
  import { EmailSender } from '@teleporthq/teleport-shared'
2
2
  import { NodeHandlerGenerator, handlerToString } from '../types'
3
3
  import { assertHandlerHasNoModuleRefs } from '../analytics/commerce-tracking'
4
+ import { EMAIL_RECIPIENTS_HELPER_SOURCE, EmailDeliveriesPlan } from './email-recipients'
4
5
 
5
6
  // Ambient, not imported: a serialized handler has no module scope, so
6
- // generateHandler appends the helper's source (EmailSender, teleport-shared).
7
+ // generateHandler appends the helpers' source (EmailSender from teleport-shared,
8
+ // the recipients helper beside this file).
7
9
  declare function __emailSender(sender: EmailSender.EmailSenderInput): EmailSender.EmailSenderParts
10
+ declare function __emailRecipients(to: unknown): unknown[]
11
+ declare function __emailDeliveries(plan: EmailDeliveriesPlan): Promise<unknown>
8
12
 
9
13
  async function email_mailgun(config: any, context: Record<string, unknown>) {
10
14
  const apiKey = config.apiKey
11
15
  const domain = config.domain
12
16
  const from = config.from
13
17
  const fromName = config.fromName || ''
14
- const to = config.to
15
18
  const subject = config.subject
16
19
  const body = config.body
17
20
  const replyTo = config.replyTo
18
21
  const cc = config.cc
19
22
  const bcc = config.bcc
23
+ const recipients = __emailRecipients(config.to)
20
24
 
21
- try {
22
- const __nodeRequire =
23
- typeof __non_webpack_require__ !== 'undefined' ? __non_webpack_require__ : require
24
- const Mailgun = __nodeRequire('mailgun.js')
25
- const FormData = __nodeRequire('form-data')
26
- const mailgun = new Mailgun(FormData)
25
+ async function sendTo(to: unknown) {
26
+ try {
27
+ const __nodeRequire =
28
+ typeof __non_webpack_require__ !== 'undefined' ? __non_webpack_require__ : require
29
+ const Mailgun = __nodeRequire('mailgun.js')
30
+ const FormData = __nodeRequire('form-data')
31
+ const mailgun = new Mailgun(FormData)
27
32
 
28
- const clientOpts: Record<string, any> = { username: 'api', key: apiKey }
29
- if (config.region === 'eu') {
30
- clientOpts.url = 'https://api.eu.mailgun.net'
31
- }
32
- const mg = mailgun.client(clientOpts)
33
+ const clientOpts: Record<string, any> = { username: 'api', key: apiKey }
34
+ if (config.region === 'eu') {
35
+ clientOpts.url = 'https://api.eu.mailgun.net'
36
+ }
37
+ const mg = mailgun.client(clientOpts)
33
38
 
34
- const fromField = __emailSender({ from, name: fromName }).header
35
- const toField = Array.isArray(to) ? to : [to]
39
+ const msgPayload: Record<string, any> = {
40
+ from: __emailSender({ from, name: fromName }).header,
41
+ to: [to],
42
+ subject,
43
+ html: body,
44
+ }
36
45
 
37
- const msgPayload: Record<string, any> = {
38
- from: fromField,
39
- to: toField,
40
- subject,
41
- html: body,
42
- }
46
+ if (replyTo) {
47
+ msgPayload['h:Reply-To'] = replyTo
48
+ }
49
+ if (cc) {
50
+ msgPayload.cc = Array.isArray(cc) ? cc.join(', ') : cc
51
+ }
52
+ if (bcc) {
53
+ msgPayload.bcc = Array.isArray(bcc) ? bcc.join(', ') : bcc
54
+ }
43
55
 
44
- if (replyTo) {
45
- msgPayload['h:Reply-To'] = replyTo
46
- }
47
- if (cc) {
48
- msgPayload.cc = Array.isArray(cc) ? cc.join(', ') : cc
49
- }
50
- if (bcc) {
51
- msgPayload.bcc = Array.isArray(bcc) ? bcc.join(', ') : bcc
52
- }
56
+ const result = await mg.messages.create(domain, msgPayload)
53
57
 
54
- const result = await mg.messages.create(domain, msgPayload)
58
+ return { messageId: result.id || '', success: true }
59
+ } catch (err: unknown) {
60
+ const error = err as any
61
+ return { messageId: '', success: false, error: error.message || 'Failed to send email' }
62
+ }
63
+ }
55
64
 
56
- return { messageId: result.id || '', success: true }
57
- } catch (err: unknown) {
58
- const error = err as any
59
- return { messageId: '', success: false, error: error.message || 'Failed to send email' }
65
+ if (recipients.length === 0) {
66
+ return { messageId: '', success: false, error: 'No recipient' }
67
+ }
68
+ if (recipients.length === 1) {
69
+ return sendTo(recipients[0])
60
70
  }
71
+ return __emailDeliveries({ recipients, send: sendTo, idKey: 'messageId' })
61
72
  }
62
73
  export const emailMailgun: NodeHandlerGenerator = {
63
74
  nodeType: 'email-mailgun',
@@ -68,7 +79,11 @@ export const emailMailgun: NodeHandlerGenerator = {
68
79
  },
69
80
  generateHandler(): string {
70
81
  return assertHandlerHasNoModuleRefs(
71
- handlerToString(email_mailgun) + '\n' + EmailSender.generateEmailSenderHelperCode(),
82
+ handlerToString(email_mailgun) +
83
+ '\n' +
84
+ EmailSender.generateEmailSenderHelperCode() +
85
+ '\n' +
86
+ EMAIL_RECIPIENTS_HELPER_SOURCE,
72
87
  'email-mailgun'
73
88
  )
74
89
  },
@@ -1,16 +1,19 @@
1
1
  import { EmailSender } from '@teleporthq/teleport-shared'
2
2
  import { NodeHandlerGenerator, handlerToString } from '../types'
3
3
  import { assertHandlerHasNoModuleRefs } from '../analytics/commerce-tracking'
4
+ import { EMAIL_RECIPIENTS_HELPER_SOURCE, EmailDeliveriesPlan } from './email-recipients'
4
5
 
5
6
  // Ambient, not imported: a serialized handler has no module scope, so
6
- // generateHandler appends the helper's source (EmailSender, teleport-shared).
7
+ // generateHandler appends the helpers' source (EmailSender from teleport-shared,
8
+ // the recipients helper beside this file).
7
9
  declare function __emailSender(sender: EmailSender.EmailSenderInput): EmailSender.EmailSenderParts
10
+ declare function __emailRecipients(to: unknown): unknown[]
11
+ declare function __emailDeliveries(plan: EmailDeliveriesPlan): Promise<unknown>
8
12
 
9
13
  async function email_postmark(config: any, context: Record<string, unknown>) {
10
14
  const serverToken = config.serverToken
11
15
  const from = config.from
12
16
  const fromName = config.fromName
13
- const to = config.to
14
17
  const subject = config.subject
15
18
  const body = config.body
16
19
  const replyTo = config.replyTo
@@ -18,45 +21,54 @@ async function email_postmark(config: any, context: Record<string, unknown>) {
18
21
  const bcc = config.bcc
19
22
  const tag = config.tag
20
23
  const trackOpens = config.trackOpens
24
+ const recipients = __emailRecipients(config.to)
21
25
 
22
- try {
23
- const __nodeRequire =
24
- typeof __non_webpack_require__ !== 'undefined' ? __non_webpack_require__ : require
25
- const postmark = __nodeRequire('postmark')
26
- const client = new postmark.ServerClient(serverToken)
26
+ async function sendTo(to: unknown) {
27
+ try {
28
+ const __nodeRequire =
29
+ typeof __non_webpack_require__ !== 'undefined' ? __non_webpack_require__ : require
30
+ const postmark = __nodeRequire('postmark')
31
+ const client = new postmark.ServerClient(serverToken)
27
32
 
28
- const toField = Array.isArray(to) ? to.join(', ') : to
33
+ const emailPayload: Record<string, any> = {
34
+ From: __emailSender({ from, name: fromName }).header,
35
+ To: to,
36
+ Subject: subject,
37
+ HtmlBody: body,
38
+ }
29
39
 
30
- const emailPayload: Record<string, any> = {
31
- From: __emailSender({ from, name: fromName }).header,
32
- To: toField,
33
- Subject: subject,
34
- HtmlBody: body,
35
- }
40
+ if (replyTo) {
41
+ emailPayload.ReplyTo = replyTo
42
+ }
43
+ if (cc) {
44
+ emailPayload.Cc = Array.isArray(cc) ? cc.join(', ') : cc
45
+ }
46
+ if (bcc) {
47
+ emailPayload.Bcc = Array.isArray(bcc) ? bcc.join(', ') : bcc
48
+ }
49
+ if (tag) {
50
+ emailPayload.Tag = tag
51
+ }
52
+ if (trackOpens !== undefined) {
53
+ emailPayload.TrackOpens = trackOpens
54
+ }
36
55
 
37
- if (replyTo) {
38
- emailPayload.ReplyTo = replyTo
39
- }
40
- if (cc) {
41
- emailPayload.Cc = Array.isArray(cc) ? cc.join(', ') : cc
42
- }
43
- if (bcc) {
44
- emailPayload.Bcc = Array.isArray(bcc) ? bcc.join(', ') : bcc
45
- }
46
- if (tag) {
47
- emailPayload.Tag = tag
48
- }
49
- if (trackOpens !== undefined) {
50
- emailPayload.TrackOpens = trackOpens
51
- }
56
+ const result = await client.sendEmail(emailPayload)
52
57
 
53
- const result = await client.sendEmail(emailPayload)
58
+ return { messageId: result.MessageID || '', success: true }
59
+ } catch (err: unknown) {
60
+ const error = err as any
61
+ return { messageId: '', success: false, error: error.message || 'Failed to send email' }
62
+ }
63
+ }
54
64
 
55
- return { messageId: result.MessageID || '', success: true }
56
- } catch (err: unknown) {
57
- const error = err as any
58
- return { messageId: '', success: false, error: error.message || 'Failed to send email' }
65
+ if (recipients.length === 0) {
66
+ return { messageId: '', success: false, error: 'No recipient' }
67
+ }
68
+ if (recipients.length === 1) {
69
+ return sendTo(recipients[0])
59
70
  }
71
+ return __emailDeliveries({ recipients, send: sendTo, idKey: 'messageId' })
60
72
  }
61
73
  export const emailPostmark: NodeHandlerGenerator = {
62
74
  nodeType: 'email-postmark',
@@ -66,7 +78,11 @@ export const emailPostmark: NodeHandlerGenerator = {
66
78
  },
67
79
  generateHandler(): string {
68
80
  return assertHandlerHasNoModuleRefs(
69
- handlerToString(email_postmark) + '\n' + EmailSender.generateEmailSenderHelperCode(),
81
+ handlerToString(email_postmark) +
82
+ '\n' +
83
+ EmailSender.generateEmailSenderHelperCode() +
84
+ '\n' +
85
+ EMAIL_RECIPIENTS_HELPER_SOURCE,
70
86
  'email-postmark'
71
87
  )
72
88
  },
@@ -0,0 +1,86 @@
1
+ /**
2
+ * How a workflow email node reaches SEVERAL recipients: one provider call per
3
+ * address, so one refused address (a sender that only delivers to the account
4
+ * owner, a provider account still pending approval) never blocks the others,
5
+ * and the sent-email ledger can say exactly who got the email.
6
+ *
7
+ * Appended to the five `email-*` handlers the way `__emailSender` is: a
8
+ * serialized handler has no module scope, so each declares these ambiently
9
+ * and `generateHandler` concatenates this source. A STRING LITERAL with
10
+ * ONE-parameter functions, for the reasons `commerce-tracking.ts` gives (a
11
+ * consumer's minifier inlines a single-use `.toString()`, and
12
+ * `resolveHandlerEntryName` picks a minified entry point by its 2-param arity).
13
+ */
14
+
15
+ export interface EmailDelivery {
16
+ to: string
17
+ success: boolean
18
+ id?: string
19
+ messageId?: string
20
+ error?: string
21
+ }
22
+
23
+ export interface EmailDeliveriesPlan {
24
+ recipients: unknown[]
25
+ send: (recipient: unknown) => Promise<unknown>
26
+ /** The key the handler reports the provider's message id under. */
27
+ idKey: 'id' | 'messageId'
28
+ }
29
+
30
+ export const EMAIL_RECIPIENTS_HELPER_SOURCE = `
31
+ // The address of one recipient entry: a string, or MailerSend's { email, name }.
32
+ function __emailAddressOf(entry) {
33
+ if (typeof entry === 'string') return entry.trim();
34
+ if (entry && typeof entry === 'object' && typeof entry.email === 'string') return entry.email.trim();
35
+ return '';
36
+ }
37
+
38
+ // A node's \`to\` as a list with no empty entries and no address twice
39
+ // (case-insensitive); each entry keeps the shape it arrived in.
40
+ function __emailRecipients(to) {
41
+ var entries = Array.isArray(to) ? to : [to];
42
+ var seen = {};
43
+ var recipients = [];
44
+ for (var i = 0; i < entries.length; i++) {
45
+ var key = __emailAddressOf(entries[i]).toLowerCase();
46
+ if (!key || seen[key]) continue;
47
+ seen[key] = true;
48
+ recipients.push(entries[i]);
49
+ }
50
+ return recipients;
51
+ }
52
+
53
+ // Sends to each recipient in turn through plan.send (one recipient per call)
54
+ // and reports the whole: the first delivered id, success when at least one
55
+ // was delivered, the refused addresses with their reasons, and one delivery
56
+ // per recipient.
57
+ async function __emailDeliveries(plan) {
58
+ var deliveries = [];
59
+ for (var i = 0; i < plan.recipients.length; i++) {
60
+ var recipient = plan.recipients[i];
61
+ var outcome;
62
+ try {
63
+ outcome = await plan.send(recipient);
64
+ } catch (err) {
65
+ outcome = { success: false, error: (err && err.message) || String(err) };
66
+ }
67
+ var delivery = { to: __emailAddressOf(recipient), success: outcome.success === true };
68
+ if (delivery.success) {
69
+ delivery[plan.idKey] = outcome[plan.idKey] || '';
70
+ } else {
71
+ delivery.error = outcome.error || 'Failed to send email';
72
+ }
73
+ deliveries.push(delivery);
74
+ }
75
+ var delivered = deliveries.filter(function (d) { return d.success; });
76
+ var refused = deliveries.filter(function (d) { return !d.success; });
77
+ var result = {};
78
+ result[plan.idKey] = delivered.length ? delivered[0][plan.idKey] : '';
79
+ result.success = delivered.length > 0;
80
+ if (refused.length) {
81
+ result.error = 'refused for ' + refused.map(function (d) { return d.to + ' (' + d.error + ')'; }).join('; ');
82
+ }
83
+ result.deliveries = deliveries;
84
+ return result;
85
+ }
86
+ `
@@ -1,57 +1,71 @@
1
1
  import { EmailSender } from '@teleporthq/teleport-shared'
2
2
  import { NodeHandlerGenerator, handlerToString } from '../types'
3
3
  import { assertHandlerHasNoModuleRefs } from '../analytics/commerce-tracking'
4
+ import { EMAIL_RECIPIENTS_HELPER_SOURCE, EmailDeliveriesPlan } from './email-recipients'
4
5
 
5
6
  // Ambient, not imported: a serialized handler has no module scope, so
6
- // generateHandler appends the helper's source (EmailSender, teleport-shared).
7
+ // generateHandler appends the helpers' source (EmailSender from teleport-shared,
8
+ // the recipients helper beside this file).
7
9
  declare function __emailSender(sender: EmailSender.EmailSenderInput): EmailSender.EmailSenderParts
10
+ declare function __emailRecipients(to: unknown): unknown[]
11
+ declare function __emailDeliveries(plan: EmailDeliveriesPlan): Promise<unknown>
8
12
 
9
13
  async function email_resend(config: any, context: Record<string, unknown>) {
10
14
  const apiKey = config.apiKey
11
15
  const from = config.from
12
16
  const fromName = config.fromName
13
- const to = config.to
14
17
  const subject = config.subject
15
18
  const body = config.body
16
19
  const replyTo = config.replyTo
17
20
  const cc = config.cc
18
21
  const bcc = config.bcc
22
+ const recipients = __emailRecipients(config.to)
19
23
 
20
- try {
21
- const __nodeRequire =
22
- typeof __non_webpack_require__ !== 'undefined' ? __non_webpack_require__ : require
23
- const Resend = __nodeRequire('resend').Resend
24
- const resend = new Resend(apiKey)
24
+ async function sendTo(to: unknown) {
25
+ try {
26
+ const __nodeRequire =
27
+ typeof __non_webpack_require__ !== 'undefined' ? __non_webpack_require__ : require
28
+ const Resend = __nodeRequire('resend').Resend
29
+ const resend = new Resend(apiKey)
25
30
 
26
- const payload: Record<string, any> = {
27
- from: __emailSender({ from, name: fromName }).header,
28
- to: Array.isArray(to) ? to : [to],
29
- subject,
30
- html: body,
31
- }
31
+ const payload: Record<string, any> = {
32
+ from: __emailSender({ from, name: fromName }).header,
33
+ to: [to],
34
+ subject,
35
+ html: body,
36
+ }
32
37
 
33
- if (replyTo) {
34
- // Resend SDK input uses the camelCase `replyTo` key (it maps to the
35
- // on-the-wire `reply_to` internally); a snake_case key is silently dropped.
36
- payload.replyTo = replyTo
37
- }
38
- if (cc) {
39
- payload.cc = Array.isArray(cc) ? cc : [cc]
40
- }
41
- if (bcc) {
42
- payload.bcc = Array.isArray(bcc) ? bcc : [bcc]
43
- }
38
+ if (replyTo) {
39
+ // Resend SDK input uses the camelCase `replyTo` key (it maps to the
40
+ // on-the-wire `reply_to` internally); a snake_case key is silently dropped.
41
+ payload.replyTo = replyTo
42
+ }
43
+ if (cc) {
44
+ payload.cc = Array.isArray(cc) ? cc : [cc]
45
+ }
46
+ if (bcc) {
47
+ payload.bcc = Array.isArray(bcc) ? bcc : [bcc]
48
+ }
44
49
 
45
- const data = await resend.emails.send(payload)
50
+ const data = await resend.emails.send(payload)
46
51
 
47
- if (data.error) {
48
- return { id: '', success: false, error: data.error.message || 'Failed to send email' }
52
+ if (data.error) {
53
+ return { id: '', success: false, error: data.error.message || 'Failed to send email' }
54
+ }
55
+
56
+ return { id: (data.data && data.data.id) || '', success: true }
57
+ } catch (err: unknown) {
58
+ return { id: '', success: false, error: (err as Error).message }
49
59
  }
60
+ }
50
61
 
51
- return { id: (data.data && data.data.id) || '', success: true }
52
- } catch (err: unknown) {
53
- return { id: '', success: false, error: (err as Error).message }
62
+ if (recipients.length === 0) {
63
+ return { id: '', success: false, error: 'No recipient' }
64
+ }
65
+ if (recipients.length === 1) {
66
+ return sendTo(recipients[0])
54
67
  }
68
+ return __emailDeliveries({ recipients, send: sendTo, idKey: 'id' })
55
69
  }
56
70
  export const emailResend: NodeHandlerGenerator = {
57
71
  nodeType: 'email-resend',
@@ -61,7 +75,11 @@ export const emailResend: NodeHandlerGenerator = {
61
75
  },
62
76
  generateHandler(): string {
63
77
  return assertHandlerHasNoModuleRefs(
64
- handlerToString(email_resend) + '\n' + EmailSender.generateEmailSenderHelperCode(),
78
+ handlerToString(email_resend) +
79
+ '\n' +
80
+ EmailSender.generateEmailSenderHelperCode() +
81
+ '\n' +
82
+ EMAIL_RECIPIENTS_HELPER_SOURCE,
65
83
  'email-resend'
66
84
  )
67
85
  },