From 82c1f359830bd18a50371987a7fd51d24bd74d0a Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 7 Jul 2026 10:18:29 -0700 Subject: [PATCH 1/2] fix(gmail): stop wrapping draft/send HTML body in per-paragraph

tags --- apps/sim/tools/gmail/utils.test.ts | 10 +++++----- apps/sim/tools/gmail/utils.ts | 13 ++++++------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/apps/sim/tools/gmail/utils.test.ts b/apps/sim/tools/gmail/utils.test.ts index 826968261b5..5cab7fd0d46 100644 --- a/apps/sim/tools/gmail/utils.test.ts +++ b/apps/sim/tools/gmail/utils.test.ts @@ -91,10 +91,10 @@ describe('escapeHtml', () => { }) describe('plainTextToHtml', () => { - it('renders blank lines as paragraph breaks and single newlines as
', () => { + it('preserves line breaks and blank lines via white-space: pre-wrap', () => { const html = plainTextToHtml('Hi Janice,\n\nHope you are well.\nSecond line.') - expect(html).toContain('

Hi Janice,

') - expect(html).toContain('

Hope you are well.
Second line.

') + expect(html).toContain('white-space: pre-wrap') + expect(html).toContain('Hi Janice,\n\nHope you are well.\nSecond line.') }) it('escapes HTML in the source text', () => { @@ -150,7 +150,7 @@ describe('buildSimpleEmailMessage', () => { expect(plainIdx).toBeGreaterThan(-1) expect(htmlIdx).toBeGreaterThan(plainIdx) expect(decodePart(decoded, 'text/plain')).toBe('Hi Janice,\n\nQuick question.') - expect(decodePart(decoded, 'text/html')).toContain('

Hi Janice,

') + expect(decodePart(decoded, 'text/html')).toContain('Hi Janice,\n\nQuick question.') }) it('encodes bodies as base64 so UTF-8 (emoji, accents) round-trips cleanly', () => { @@ -242,7 +242,7 @@ describe('buildMimeMessage', () => { expect(message).toMatch(/Content-Type: multipart\/alternative; boundary="([^"]+)"/) expect(message).toContain('Content-Disposition: attachment; filename="note.txt"') expect(decodePart(message, 'text/plain')).toBe('Hello') - expect(decodePart(message, 'text/html')).toContain('

Hello

') + expect(decodePart(message, 'text/html')).toContain('Hello') }) it('emits multipart/alternative without multipart/mixed when no attachments', () => { diff --git a/apps/sim/tools/gmail/utils.ts b/apps/sim/tools/gmail/utils.ts index f913e84ddcc..533be35c081 100644 --- a/apps/sim/tools/gmail/utils.ts +++ b/apps/sim/tools/gmail/utils.ts @@ -339,17 +339,16 @@ export function escapeHtml(value: string): string { /** * Convert a plain-text body to an HTML body that flows naturally in Gmail. - * Blank lines become paragraph breaks; single newlines become `
`. + * Uses a single `white-space: pre-wrap` block so line breaks and blank lines + * are preserved without per-paragraph `

` margins — those margins are what + * Gmail's "Remove formatting" button strips, so avoiding them keeps drafts + * looking like a plain, unformatted email by default. * This avoids the narrow hard-wrapped rendering Gmail uses for `text/plain`. */ export function plainTextToHtml(body: string): string { const normalized = body.replace(/\r\n/g, '\n').replace(/\r/g, '\n') - const paragraphs = normalized.split(/\n{2,}/) - const htmlParagraphs = paragraphs.map((paragraph) => { - const escaped = escapeHtml(paragraph).replace(/\n/g, '
') - return `

${escaped}

` - }) - return `${htmlParagraphs.join('')}` + const escaped = escapeHtml(normalized) + return `
${escaped}
` } /** From e3a2a5e67209c51e904421da0c751d9fc23ba7bc Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 7 Jul 2026 10:20:39 -0700 Subject: [PATCH 2/2] fix(gmail): use
instead of CSS white-space for line breaks white-space: pre-wrap has inconsistent email-client support (including Gmail for non-Google accounts per caniemail.com);
is the client-agnostic standard for plain-text-to-HTML line breaks. --- apps/sim/tools/gmail/utils.test.ts | 8 ++++---- apps/sim/tools/gmail/utils.ts | 14 ++++++++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/sim/tools/gmail/utils.test.ts b/apps/sim/tools/gmail/utils.test.ts index 5cab7fd0d46..e21eaddff30 100644 --- a/apps/sim/tools/gmail/utils.test.ts +++ b/apps/sim/tools/gmail/utils.test.ts @@ -91,10 +91,10 @@ describe('escapeHtml', () => { }) describe('plainTextToHtml', () => { - it('preserves line breaks and blank lines via white-space: pre-wrap', () => { + it('converts newlines to
without paragraph margins', () => { const html = plainTextToHtml('Hi Janice,\n\nHope you are well.\nSecond line.') - expect(html).toContain('white-space: pre-wrap') - expect(html).toContain('Hi Janice,\n\nHope you are well.\nSecond line.') + expect(html).not.toContain('

') + expect(html).toContain('Hi Janice,

Hope you are well.
Second line.') }) it('escapes HTML in the source text', () => { @@ -150,7 +150,7 @@ describe('buildSimpleEmailMessage', () => { expect(plainIdx).toBeGreaterThan(-1) expect(htmlIdx).toBeGreaterThan(plainIdx) expect(decodePart(decoded, 'text/plain')).toBe('Hi Janice,\n\nQuick question.') - expect(decodePart(decoded, 'text/html')).toContain('Hi Janice,\n\nQuick question.') + expect(decodePart(decoded, 'text/html')).toContain('Hi Janice,

Quick question.') }) it('encodes bodies as base64 so UTF-8 (emoji, accents) round-trips cleanly', () => { diff --git a/apps/sim/tools/gmail/utils.ts b/apps/sim/tools/gmail/utils.ts index 533be35c081..1c9ba38e3ea 100644 --- a/apps/sim/tools/gmail/utils.ts +++ b/apps/sim/tools/gmail/utils.ts @@ -339,16 +339,18 @@ export function escapeHtml(value: string): string { /** * Convert a plain-text body to an HTML body that flows naturally in Gmail. - * Uses a single `white-space: pre-wrap` block so line breaks and blank lines - * are preserved without per-paragraph `

` margins — those margins are what - * Gmail's "Remove formatting" button strips, so avoiding them keeps drafts - * looking like a plain, unformatted email by default. + * Newlines become `
` rather than per-paragraph `

` tags or a CSS + * `white-space` rule — `

` margins are what Gmail's "Remove formatting" + * button strips, and `white-space` has inconsistent support across email + * clients (including Gmail for non-Google accounts). Plain `
` line + * breaks are the standard, client-agnostic way to preserve plain-text + * formatting in an HTML alternative part. * This avoids the narrow hard-wrapped rendering Gmail uses for `text/plain`. */ export function plainTextToHtml(body: string): string { const normalized = body.replace(/\r\n/g, '\n').replace(/\r/g, '\n') - const escaped = escapeHtml(normalized) - return `

${escaped}
` + const escaped = escapeHtml(normalized).replace(/\n/g, '
') + return `${escaped}` } /**