diff --git a/.changeset/fix-cid-attachment-url.md b/.changeset/fix-cid-attachment-url.md deleted file mode 100644 index aec7c6f..0000000 --- a/.changeset/fix-cid-attachment-url.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@maildev/smtp': patch -'@maildev/api': patch ---- - -Fix the URL that `replaceCidReferences` rewrites inline (CID) attachments to. It -built `/email/:id/attachment/:file`, which matches no route: the attachment is -served from `/api/email/:id/attachment/:file`, under the configured base path. -The URL is now root-relative and includes both parts, so it resolves at the root -and under a subpath such as `/mail`. A base path that resolves to the root (`/`) -adds no prefix, so it can no longer produce a protocol-relative `//api/email/...`. diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index 671c616..b5887ad 100644 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -457,11 +457,11 @@ export class APIServer extends EventEmitter { `${apiPath}/email/:id/html`, async (request, reply) => { const { id } = request.params + const baseUrl = request.headers.host ?? '' try { if (this.smtp) { - // No host: the HTML is rendered for a browser already on this origin - const html = await this.smtp.getEmailHtml(id, { basePath }) + const html = await this.smtp.getEmailHtml(id, { baseUrl }) if (!html) { return reply.status(404).send({ error: 'Email has no HTML content' }) } diff --git a/packages/smtp/src/__tests__/sanitize.test.ts b/packages/smtp/src/__tests__/sanitize.test.ts index 015f580..3d94460 100644 --- a/packages/smtp/src/__tests__/sanitize.test.ts +++ b/packages/smtp/src/__tests__/sanitize.test.ts @@ -73,7 +73,7 @@ describe('replaceCidReferences', () => { const result = replaceCidReferences(html, 'email1', attachments) - expect(result).toContain('/api/email/email1/attachment/abc.png') + expect(result).toContain('/email/email1/attachment/abc.png') expect(result).not.toContain('cid:') }) @@ -85,7 +85,7 @@ describe('replaceCidReferences', () => { const result = replaceCidReferences(html, 'email1', attachments) - expect(result).toContain('/api/email/email1/attachment/abc.png') + expect(result).toContain('/email/email1/attachment/abc.png') }) it('should include baseUrl when provided', () => { @@ -96,44 +96,7 @@ describe('replaceCidReferences', () => { const result = replaceCidReferences(html, 'email1', attachments, 'example.com') - expect(result).toContain('//example.com/api/email/email1/attachment/abc.png') - }) - - it('should include basePath when MailDev is mounted under a subpath', () => { - const html = '' - const attachments = [ - { contentId: 'image123', generatedFileName: 'abc.png' }, - ] - - const result = replaceCidReferences(html, 'email1', attachments, undefined, '/mail') - - expect(result).toContain('src="/mail/api/email/email1/attachment/abc.png"') - }) - - it('should normalize a basePath given with or without slashes', () => { - const html = '' - const attachments = [ - { contentId: 'image123', generatedFileName: 'abc.png' }, - ] - - for (const basePath of ['mail', '/mail', '/mail/']) { - const result = replaceCidReferences(html, 'email1', attachments, undefined, basePath) - expect(result).toContain('src="/mail/api/email/email1/attachment/abc.png"') - } - }) - - it('should treat basePath "/" as no prefix', () => { - const html = '' - const attachments = [{ contentId: 'image123', generatedFileName: 'abc.png' }] - const result = replaceCidReferences(html, 'email1', attachments, undefined, '/') - expect(result).toContain('src="/api/email/email1/attachment/abc.png"') - }) - - it('should combine baseUrl and basePath', () => { - const html = '' - const attachments = [{ contentId: 'image123', generatedFileName: 'abc.png' }] - const result = replaceCidReferences(html, 'email1', attachments, 'example.com', '/mail') - expect(result).toContain('src="//example.com/mail/api/email/email1/attachment/abc.png"') + expect(result).toContain('//example.com/email/email1/attachment/abc.png') }) it('should replace multiple cid references', () => { diff --git a/packages/smtp/src/sanitize.ts b/packages/smtp/src/sanitize.ts index a50db3a..fa54a29 100644 --- a/packages/smtp/src/sanitize.ts +++ b/packages/smtp/src/sanitize.ts @@ -45,16 +45,14 @@ export function sanitizeHtml(html: string | undefined): string | undefined { * @param html - HTML content with cid: references * @param emailId - ID of the email * @param attachments - Array of attachments with contentId - * @param baseUrl - Optional host to prefix as `//host/...`; unset gives a root-relative URL - * @param basePath - Optional pathname the API is mounted under, e.g. `/mail` + * @param baseUrl - Optional base URL for attachment URLs * @returns HTML with cid: references replaced with actual URLs */ export function replaceCidReferences( html: string, emailId: string, attachments: Array<{ contentId?: string; generatedFileName: string }>, - baseUrl?: string, - basePath?: string + baseUrl?: string ): string { if (!attachments || attachments.length === 0) { return html @@ -74,7 +72,7 @@ export function replaceCidReferences( 'g' ) - const url = buildAttachmentUrl(emailId, attachment.generatedFileName, baseUrl, basePath) + const url = buildAttachmentUrl(emailId, attachment.generatedFileName, baseUrl) const replacement = `src="${url}"` result = result.replace(regex, replacement) @@ -84,19 +82,15 @@ export function replaceCidReferences( } /** - * Build URL for an attachment. The API serves these from `${basePath}/api/email/...`, - * so both parts belong here or the src 404s. + * Build URL for an attachment */ function buildAttachmentUrl( emailId: string, filename: string, - baseUrl?: string, - basePath?: string + baseUrl?: string ): string { const base = baseUrl ? `//${baseUrl}` : '' - const segments = basePath ? basePath.replace(/^\/+|\/+$/g, '') : '' - const prefix = segments ? `/${segments}` : '' - return `${base}${prefix}/api/email/${emailId}/attachment/${encodeURIComponent(filename)}` + return `${base}/email/${emailId}/attachment/${encodeURIComponent(filename)}` } /** diff --git a/packages/smtp/src/server.ts b/packages/smtp/src/server.ts index c933860..15f0cb5 100644 --- a/packages/smtp/src/server.ts +++ b/packages/smtp/src/server.ts @@ -258,7 +258,7 @@ export class SMTPServer extends EventEmitter { // Replace CID references with actual URLs if (email.attachments && email.attachments.length > 0) { - html = replaceCidReferences(html, id, email.attachments, options.baseUrl, options.basePath) + html = replaceCidReferences(html, id, email.attachments, options.baseUrl) } return html diff --git a/packages/smtp/src/types.ts b/packages/smtp/src/types.ts index 1b81d6b..427e956 100644 --- a/packages/smtp/src/types.ts +++ b/packages/smtp/src/types.ts @@ -218,10 +218,8 @@ export interface RelayResult { * HTML rendering options for email */ export interface HTMLRenderOptions { - /** Host to prefix attachment URLs with; unset gives a root-relative URL */ + /** Base URL for attachment URLs */ baseUrl?: string - /** Pathname the API is mounted under, e.g. `/mail` */ - basePath?: string } /**