Skip to content

Commit 954fdd8

Browse files
authored
fix(gmail): stop wrapping draft/send HTML body in per-paragraph <p> tags (#5479)
* fix(gmail): stop wrapping draft/send HTML body in per-paragraph <p> tags * fix(gmail): use <br> 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); <br> is the client-agnostic standard for plain-text-to-HTML line breaks.
1 parent 03462b9 commit 954fdd8

2 files changed

Lines changed: 13 additions & 12 deletions

File tree

apps/sim/tools/gmail/utils.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,10 @@ describe('escapeHtml', () => {
9191
})
9292

9393
describe('plainTextToHtml', () => {
94-
it('renders blank lines as paragraph breaks and single newlines as <br>', () => {
94+
it('converts newlines to <br> without paragraph margins', () => {
9595
const html = plainTextToHtml('Hi Janice,\n\nHope you are well.\nSecond line.')
96-
expect(html).toContain('<p>Hi Janice,</p>')
97-
expect(html).toContain('<p>Hope you are well.<br>Second line.</p>')
96+
expect(html).not.toContain('<p>')
97+
expect(html).toContain('Hi Janice,<br><br>Hope you are well.<br>Second line.')
9898
})
9999

100100
it('escapes HTML in the source text', () => {
@@ -150,7 +150,7 @@ describe('buildSimpleEmailMessage', () => {
150150
expect(plainIdx).toBeGreaterThan(-1)
151151
expect(htmlIdx).toBeGreaterThan(plainIdx)
152152
expect(decodePart(decoded, 'text/plain')).toBe('Hi Janice,\n\nQuick question.')
153-
expect(decodePart(decoded, 'text/html')).toContain('<p>Hi Janice,</p>')
153+
expect(decodePart(decoded, 'text/html')).toContain('Hi Janice,<br><br>Quick question.')
154154
})
155155

156156
it('encodes bodies as base64 so UTF-8 (emoji, accents) round-trips cleanly', () => {
@@ -242,7 +242,7 @@ describe('buildMimeMessage', () => {
242242
expect(message).toMatch(/Content-Type: multipart\/alternative; boundary="([^"]+)"/)
243243
expect(message).toContain('Content-Disposition: attachment; filename="note.txt"')
244244
expect(decodePart(message, 'text/plain')).toBe('Hello')
245-
expect(decodePart(message, 'text/html')).toContain('<p>Hello</p>')
245+
expect(decodePart(message, 'text/html')).toContain('Hello')
246246
})
247247

248248
it('emits multipart/alternative without multipart/mixed when no attachments', () => {

apps/sim/tools/gmail/utils.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -339,17 +339,18 @@ export function escapeHtml(value: string): string {
339339

340340
/**
341341
* Convert a plain-text body to an HTML body that flows naturally in Gmail.
342-
* Blank lines become paragraph breaks; single newlines become `<br>`.
342+
* Newlines become `<br>` rather than per-paragraph `<p>` tags or a CSS
343+
* `white-space` rule — `<p>` margins are what Gmail's "Remove formatting"
344+
* button strips, and `white-space` has inconsistent support across email
345+
* clients (including Gmail for non-Google accounts). Plain `<br>` line
346+
* breaks are the standard, client-agnostic way to preserve plain-text
347+
* formatting in an HTML alternative part.
343348
* This avoids the narrow hard-wrapped rendering Gmail uses for `text/plain`.
344349
*/
345350
export function plainTextToHtml(body: string): string {
346351
const normalized = body.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
347-
const paragraphs = normalized.split(/\n{2,}/)
348-
const htmlParagraphs = paragraphs.map((paragraph) => {
349-
const escaped = escapeHtml(paragraph).replace(/\n/g, '<br>')
350-
return `<p>${escaped}</p>`
351-
})
352-
return `<!DOCTYPE html><html><body>${htmlParagraphs.join('')}</body></html>`
352+
const escaped = escapeHtml(normalized).replace(/\n/g, '<br>')
353+
return `<!DOCTYPE html><html><body>${escaped}</body></html>`
353354
}
354355

355356
/**

0 commit comments

Comments
 (0)