@open-mercato/channel-resend 0.8.1-develop.7296.1.2111d779db → 0.9.0

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.
@@ -38,7 +38,7 @@ class ResendChannelAdapter {
38
38
  async sendMessage(input) {
39
39
  const credentials = resendCredentialsSchema.parse(input.credentials);
40
40
  const meta = input.metadata ?? {};
41
- const to = Array.isArray(meta.to) ? meta.to : [];
41
+ const to = toAddressList(meta.to).map(sanitizeHeaderValue);
42
42
  if (to.length === 0) {
43
43
  return { externalMessageId: "", status: "failed", error: "[internal] Email send requires at least one recipient" };
44
44
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/channel_resend/lib/adapter.ts"],
4
- "sourcesContent": ["import { Resend } from 'resend'\nimport type {\n ChannelAdapter,\n ChannelNativeContent,\n ConvertOutboundInput,\n GetMessageStatusInput,\n InboundMessage,\n MessageStatus,\n NormalizedInboundMessage,\n SendMessageInput,\n SendMessageResult,\n VerifyWebhookInput,\n} from '@open-mercato/core/modules/communication_channels/lib/adapter'\nimport {\n htmlToText,\n sanitizeHeaderValue,\n stringOrUndefined,\n toAddressList,\n} from '@open-mercato/core/modules/communication_channels/lib/email-mime'\nimport { resendCapabilities } from '../capabilities'\nimport { resendCredentialsSchema } from './credentials'\n\ntype ResendAttachment = {\n filename: string\n content: string\n contentType?: string\n}\n\ntype ResendSendResult = {\n data?: { id?: string | null } | null\n error?: unknown\n}\n\nfunction resolveResendErrorMessage(result: unknown): string | null {\n const value = result as ResendSendResult\n if (typeof value.error === 'string') return value.error\n if (value.error && typeof value.error === 'object' && 'message' in value.error) {\n const message = (value.error as { message?: unknown }).message\n return typeof message === 'string' ? message : null\n }\n return null\n}\n\nfunction attachmentsFromMeta(value: unknown): ResendAttachment[] | undefined {\n if (!Array.isArray(value)) return undefined\n const attachments = value.flatMap((item): ResendAttachment[] => {\n if (!item || typeof item !== 'object') return []\n const record = item as Record<string, unknown>\n const filename = stringOrUndefined(record.filename)\n const content = stringOrUndefined(record.content)\n if (!filename || !content) return []\n const contentType = stringOrUndefined(record.contentType)\n return [{ filename, content, ...(contentType ? { contentType } : {}) }]\n })\n return attachments.length ? attachments : undefined\n}\n\nclass ResendChannelAdapter implements ChannelAdapter {\n readonly providerKey = 'resend'\n readonly channelType = 'email'\n readonly capabilities = resendCapabilities\n\n async sendMessage(input: SendMessageInput): Promise<SendMessageResult> {\n const credentials = resendCredentialsSchema.parse(input.credentials)\n const meta = (input.metadata ?? {}) as Record<string, unknown>\n const to = Array.isArray(meta.to) ? (meta.to as string[]) : []\n if (to.length === 0) {\n return { externalMessageId: '', status: 'failed', error: '[internal] Email send requires at least one recipient' }\n }\n const subject = stringOrUndefined(meta.subject)\n if (!subject) {\n return { externalMessageId: '', status: 'failed', error: '[internal] Email send requires a subject' }\n }\n\n const client = new Resend(credentials.apiKey)\n const resendAttachments = attachmentsFromMeta(meta.attachments)\n const basePayload = {\n from: stringOrUndefined(meta.from) ?? credentials.fromAddress,\n to,\n subject,\n ...(stringOrUndefined(meta.replyTo) ? { replyTo: stringOrUndefined(meta.replyTo) } : {}),\n ...(resendAttachments?.length ? { attachments: resendAttachments } : {}),\n }\n const payload: Parameters<typeof client.emails.send>[0] = input.content.html\n ? { ...basePayload, html: input.content.html, ...(input.content.text ? { text: input.content.text } : {}) }\n : { ...basePayload, text: input.content.text ?? '' }\n const result = await client.emails.send(payload)\n const errorMessage = resolveResendErrorMessage(result)\n if (errorMessage) {\n return { externalMessageId: '', status: 'failed', error: `RESEND_SEND_FAILED: ${errorMessage}` }\n }\n const sentId = (result as ResendSendResult).data?.id\n return {\n externalMessageId: sentId || `resend:${Date.now()}`,\n conversationId: input.conversationId,\n status: 'sent',\n }\n }\n\n async verifyWebhook(_input: VerifyWebhookInput): Promise<InboundMessage> {\n return { raw: {}, eventType: 'other', metadata: { reason: 'resend-system-email-outbound-only' } }\n }\n\n async getStatus(_input: GetMessageStatusInput): Promise<MessageStatus> {\n return { status: 'sent' }\n }\n\n async normalizeInbound(_raw: InboundMessage): Promise<NormalizedInboundMessage> {\n throw new Error('[internal] Resend system email adapter is outbound-only')\n }\n\n async convertOutbound(input: ConvertOutboundInput): Promise<ChannelNativeContent> {\n const meta = (input.channelMetadata ?? {}) as Record<string, unknown>\n const to = toAddressList(meta.to).map(sanitizeHeaderValue)\n const subject = stringOrUndefined(meta.subject)\n const html = input.bodyFormat === 'html' ? input.body : undefined\n const text = input.bodyFormat === 'html' ? htmlToText(input.body) : input.body\n return {\n content: {\n text,\n html,\n bodyFormat: input.bodyFormat,\n },\n metadata: {\n to,\n subject,\n from: stringOrUndefined(meta.from),\n replyTo: stringOrUndefined(meta.replyTo),\n attachments: attachmentsFromMeta(meta.attachments),\n },\n }\n }\n}\n\nconst adapter = new ResendChannelAdapter()\n\nexport function getResendChannelAdapter(): ChannelAdapter {\n return adapter\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Resend } from 'resend'\nimport type {\n ChannelAdapter,\n ChannelNativeContent,\n ConvertOutboundInput,\n GetMessageStatusInput,\n InboundMessage,\n MessageStatus,\n NormalizedInboundMessage,\n SendMessageInput,\n SendMessageResult,\n VerifyWebhookInput,\n} from '@open-mercato/core/modules/communication_channels/lib/adapter'\nimport {\n htmlToText,\n sanitizeHeaderValue,\n stringOrUndefined,\n toAddressList,\n} from '@open-mercato/core/modules/communication_channels/lib/email-mime'\nimport { resendCapabilities } from '../capabilities'\nimport { resendCredentialsSchema } from './credentials'\n\ntype ResendAttachment = {\n filename: string\n content: string\n contentType?: string\n}\n\ntype ResendSendResult = {\n data?: { id?: string | null } | null\n error?: unknown\n}\n\nfunction resolveResendErrorMessage(result: unknown): string | null {\n const value = result as ResendSendResult\n if (typeof value.error === 'string') return value.error\n if (value.error && typeof value.error === 'object' && 'message' in value.error) {\n const message = (value.error as { message?: unknown }).message\n return typeof message === 'string' ? message : null\n }\n return null\n}\n\nfunction attachmentsFromMeta(value: unknown): ResendAttachment[] | undefined {\n if (!Array.isArray(value)) return undefined\n const attachments = value.flatMap((item): ResendAttachment[] => {\n if (!item || typeof item !== 'object') return []\n const record = item as Record<string, unknown>\n const filename = stringOrUndefined(record.filename)\n const content = stringOrUndefined(record.content)\n if (!filename || !content) return []\n const contentType = stringOrUndefined(record.contentType)\n return [{ filename, content, ...(contentType ? { contentType } : {}) }]\n })\n return attachments.length ? attachments : undefined\n}\n\nclass ResendChannelAdapter implements ChannelAdapter {\n readonly providerKey = 'resend'\n readonly channelType = 'email'\n readonly capabilities = resendCapabilities\n\n async sendMessage(input: SendMessageInput): Promise<SendMessageResult> {\n const credentials = resendCredentialsSchema.parse(input.credentials)\n const meta = (input.metadata ?? {}) as Record<string, unknown>\n const to = toAddressList(meta.to).map(sanitizeHeaderValue)\n if (to.length === 0) {\n return { externalMessageId: '', status: 'failed', error: '[internal] Email send requires at least one recipient' }\n }\n const subject = stringOrUndefined(meta.subject)\n if (!subject) {\n return { externalMessageId: '', status: 'failed', error: '[internal] Email send requires a subject' }\n }\n\n const client = new Resend(credentials.apiKey)\n const resendAttachments = attachmentsFromMeta(meta.attachments)\n const basePayload = {\n from: stringOrUndefined(meta.from) ?? credentials.fromAddress,\n to,\n subject,\n ...(stringOrUndefined(meta.replyTo) ? { replyTo: stringOrUndefined(meta.replyTo) } : {}),\n ...(resendAttachments?.length ? { attachments: resendAttachments } : {}),\n }\n const payload: Parameters<typeof client.emails.send>[0] = input.content.html\n ? { ...basePayload, html: input.content.html, ...(input.content.text ? { text: input.content.text } : {}) }\n : { ...basePayload, text: input.content.text ?? '' }\n const result = await client.emails.send(payload)\n const errorMessage = resolveResendErrorMessage(result)\n if (errorMessage) {\n return { externalMessageId: '', status: 'failed', error: `RESEND_SEND_FAILED: ${errorMessage}` }\n }\n const sentId = (result as ResendSendResult).data?.id\n return {\n externalMessageId: sentId || `resend:${Date.now()}`,\n conversationId: input.conversationId,\n status: 'sent',\n }\n }\n\n async verifyWebhook(_input: VerifyWebhookInput): Promise<InboundMessage> {\n return { raw: {}, eventType: 'other', metadata: { reason: 'resend-system-email-outbound-only' } }\n }\n\n async getStatus(_input: GetMessageStatusInput): Promise<MessageStatus> {\n return { status: 'sent' }\n }\n\n async normalizeInbound(_raw: InboundMessage): Promise<NormalizedInboundMessage> {\n throw new Error('[internal] Resend system email adapter is outbound-only')\n }\n\n async convertOutbound(input: ConvertOutboundInput): Promise<ChannelNativeContent> {\n const meta = (input.channelMetadata ?? {}) as Record<string, unknown>\n const to = toAddressList(meta.to).map(sanitizeHeaderValue)\n const subject = stringOrUndefined(meta.subject)\n const html = input.bodyFormat === 'html' ? input.body : undefined\n const text = input.bodyFormat === 'html' ? htmlToText(input.body) : input.body\n return {\n content: {\n text,\n html,\n bodyFormat: input.bodyFormat,\n },\n metadata: {\n to,\n subject,\n from: stringOrUndefined(meta.from),\n replyTo: stringOrUndefined(meta.replyTo),\n attachments: attachmentsFromMeta(meta.attachments),\n },\n }\n }\n}\n\nconst adapter = new ResendChannelAdapter()\n\nexport function getResendChannelAdapter(): ChannelAdapter {\n return adapter\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/channel-resend",
3
- "version": "0.8.1-develop.7296.1.2111d779db",
3
+ "version": "0.9.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -50,21 +50,21 @@
50
50
  }
51
51
  },
52
52
  "dependencies": {
53
- "@open-mercato/core": "0.8.1-develop.7296.1.2111d779db",
53
+ "@open-mercato/core": "0.9.0",
54
54
  "resend": "^6.24.0"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "@mikro-orm/postgresql": "^7.0.14",
58
- "@open-mercato/ai-assistant": "0.8.1-develop.7296.1.2111d779db",
59
- "@open-mercato/shared": "0.8.1-develop.7296.1.2111d779db",
60
- "@open-mercato/ui": "0.8.1-develop.7296.1.2111d779db",
58
+ "@open-mercato/ai-assistant": "0.9.0",
59
+ "@open-mercato/shared": "0.9.0",
60
+ "@open-mercato/ui": "0.9.0",
61
61
  "react": "^19.0.0",
62
62
  "react-dom": "^19.0.0"
63
63
  },
64
64
  "devDependencies": {
65
- "@open-mercato/ai-assistant": "0.8.1-develop.7296.1.2111d779db",
66
- "@open-mercato/shared": "0.8.1-develop.7296.1.2111d779db",
67
- "@open-mercato/ui": "0.8.1-develop.7296.1.2111d779db",
65
+ "@open-mercato/ai-assistant": "0.9.0",
66
+ "@open-mercato/shared": "0.9.0",
67
+ "@open-mercato/ui": "0.9.0",
68
68
  "@types/jest": "^30.0.0",
69
69
  "@types/react": "^19.2.18",
70
70
  "@types/react-dom": "^19.2.5",
@@ -83,6 +83,5 @@
83
83
  "type": "git",
84
84
  "url": "https://github.com/open-mercato/open-mercato",
85
85
  "directory": "packages/channel-resend"
86
- },
87
- "stableVersion": "0.8.0"
86
+ }
88
87
  }
@@ -51,6 +51,52 @@ describe('ResendChannelAdapter', () => {
51
51
  }))
52
52
  })
53
53
 
54
+ it('accepts the raw recipient string a direct sendMessage caller passes', async () => {
55
+ const adapter = getResendChannelAdapter()
56
+
57
+ const result = await adapter.sendMessage({
58
+ content: { text: 'Test send' },
59
+ credentials: { apiKey: 'key', fromAddress: 'from@example.com' },
60
+ scope: { tenantId: 'tenant', organizationId: 'org' },
61
+ metadata: { to: 'user@example.com', subject: 'Test send', testSend: true },
62
+ })
63
+
64
+ expect(result).toEqual(expect.objectContaining({ status: 'sent', externalMessageId: 'email-1' }))
65
+ expect(sendMock).toHaveBeenCalledWith(expect.objectContaining({ to: ['user@example.com'] }))
66
+ })
67
+
68
+ it('splits a comma or semicolon separated recipient string', async () => {
69
+ const adapter = getResendChannelAdapter()
70
+
71
+ await adapter.sendMessage({
72
+ content: { text: 'Hello' },
73
+ credentials: { apiKey: 'key', fromAddress: 'from@example.com' },
74
+ scope: { tenantId: 'tenant', organizationId: 'org' },
75
+ metadata: { to: 'a@example.com, b@example.com;c@example.com', subject: 'Hello' },
76
+ })
77
+
78
+ expect(sendMock).toHaveBeenCalledWith(expect.objectContaining({
79
+ to: ['a@example.com', 'b@example.com', 'c@example.com'],
80
+ }))
81
+ })
82
+
83
+ it('rejects an empty recipient string without calling Resend', async () => {
84
+ const adapter = getResendChannelAdapter()
85
+
86
+ const result = await adapter.sendMessage({
87
+ content: { text: 'Hello' },
88
+ credentials: { apiKey: 'key', fromAddress: 'from@example.com' },
89
+ scope: { tenantId: 'tenant', organizationId: 'org' },
90
+ metadata: { to: ' ', subject: 'Hello' },
91
+ })
92
+
93
+ expect(result).toEqual(expect.objectContaining({
94
+ status: 'failed',
95
+ error: '[internal] Email send requires at least one recipient',
96
+ }))
97
+ expect(sendMock).not.toHaveBeenCalled()
98
+ })
99
+
54
100
  it('returns a failed result when Resend reports an error', async () => {
55
101
  sendMock.mockResolvedValueOnce({ error: { message: 'bad domain' } })
56
102
  const adapter = getResendChannelAdapter()
@@ -63,7 +63,7 @@ class ResendChannelAdapter implements ChannelAdapter {
63
63
  async sendMessage(input: SendMessageInput): Promise<SendMessageResult> {
64
64
  const credentials = resendCredentialsSchema.parse(input.credentials)
65
65
  const meta = (input.metadata ?? {}) as Record<string, unknown>
66
- const to = Array.isArray(meta.to) ? (meta.to as string[]) : []
66
+ const to = toAddressList(meta.to).map(sanitizeHeaderValue)
67
67
  if (to.length === 0) {
68
68
  return { externalMessageId: '', status: 'failed', error: '[internal] Email send requires at least one recipient' }
69
69
  }