nextcloud / nextcloud/mail

Message body completely hidden when text/plain is followed by an unrelated text/html part (e.g., mailing list footers)

Open
#12,655 5 comments 0 reactions 1 assignee View on GitHub

@ChristophWurst is already working on this.

Since Mar 31, 2026.

0. to triage bug stale
Dominant language
JavaScript
Stars
1k
Forks
348
Avg merge
12h 28m
Merged PRs (30d)
91

Description

Steps to reproduce
  1. Send a plain text email to a mailing list or through a gateway that appends an HTML footer/signature.
  2. The resulting email will have a multipart/mixed structure containing a text/plain part (the actual message) and a text/html part (the appended footer).
  3. Open the email in Nextcloud Mail.
  4. Observe that only the HTML footer is rendered. The main message is gone.
Expected behavior

Nextcloud Mail should display both parts, or at least be smart enough to detect if the HTML part is just an unrelated addition (like a footer) rather than an alternative version of the plain text body.

Actual behavior

Nextcloud Mail only renders the text/html part. The main message content is completely lost/hidden from the UI.

Mail app version

5.6.14

Nextcloud version

31.0.14

Mailserver or service

Dovecot imap own server

Operating system

Ubuntu 24.04

PHP engine version

PHP 8.3

Nextcloud memory caching

'memcache.local' => '\OC\Memcache\APCu', 'memcache.distributed' => '\OC\Memcache\Redis',

Web server

Nginx

Database

MariaDB

Additional info
DESCRIPTION

When Nextcloud Mail receives a multipart/mixed email containing a text/plain body and a text/html attachment/footer, it only renders the HTML part and completely discards the plain text message.

This happens very often with mailing list software (like Sympa or Mailman) or corporate antivirus gateways. The original sender sends a pure text/plain email, and the server appends an unsubscribe link or signature in text/html. Because an HTML part exists, the Nextcloud Mail parser prioritizes it and the user only sees the footer, making the actual email completely invisible unless they check the raw source.

Proposed Solution

A robust fix is to check if the text/plain content actually exists within the text/html content. If it doesn't (meaning the HTML is just a discrete footer/banner), both should be concatenated.

I successfully patched my local instance by modifying the getHtmlBody() method in IMAPMessage.php with this logic:

public function getHtmlBody(int $id): string {
    $html = $this->htmlMessage;
    $plainTextTrimmed = trim($this->plainMessage);
    
    if ($plainTextTrimmed !== '') {
        $htmlTextStripped = strip_tags($html);
        
        // Normalize whitespace to prevent false negatives
        $plainNormalized = preg_replace('/\s+/', ' ', $plainTextTrimmed);
        $htmlNormalized = preg_replace('/\s+/', ' ', $htmlTextStripped);
        
        // Take a snippet of the plain text to look for it inside the HTML
        $snippet = mb_substr($plainNormalized, 0, 60);
        
        // If the plain text snippet is NOT in the HTML, the HTML is just an unrelated footer
        if (mb_strpos($htmlNormalized, $snippet) === false) {
            $plainHtml = '<div style="white-space: pre-wrap; font-family: inherit; margin-bottom: 20px;">' . htmlspecialchars($plainTextTrimmed) . '</div>';
            $html = $plainHtml . '<hr style="border: 0; border-bottom: 1px solid #ccc; margin: 20px 0;">' . $html;
        }
    }

    return $this->htmlService->sanitizeHtmlMailBody($html, [
        'id' => $id,
    ], function ($cid) {
        $match = array_filter($this->inlineAttachments,
            static fn ($a) => $a['cid'] === $cid);
        $match = array_shift($match);
        if ($match === null) {
            return null;
        }
        return $match['id'];
    });
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.