Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for composing 'multipart/related' emails #3197

Merged
merged 7 commits into from
Jan 27, 2022

Conversation

dcpurton
Copy link
Collaborator

@dcpurton dcpurton commented Jan 15, 2022

What does this PR do?

Adds support for composing multipart/related emails.

Example

run ./neomutt -n -F multipart-related.rc

Needs:

multipart-related.rc

# vim: syn=neomuttrc

# filename: multipart-related.rc

# run from build directory:
#   ./neomutt -n -F multipart-related.rc

# key in <F1> through <F6> to construct the email

unset record
unset signature

set attach_format = "%u%D%I %t%4n %T%.40d%> [%.15m/%.15M, %.6e%?C?, %C?, %s]"
set editor = "echo '![Neomutt Logo](cid:neomutt-256.png)' >> %s"
set from = "john.doe@example.com"
set help = no
set postpone = no
set postponed = "postponed.mbox"
set realname = "John Doe"
set recall=no
set spoolfile = "inbox.mbox"

macro index   <F1> "<mail>John Doe <john.doe@example.com><enter>test<enter>"
macro compose <F2> "<attach-file>contrib/logo/neomutt-256.png<enter>"
macro compose <F3> "<rename-attachment><enter><toggle-disposition>"
macro compose <F4> "<attach-file>html-part.html<enter>"
macro compose <F5> "<toggle-disposition><tag-entry>1<enter><tag-entry><group-alternatives>"
macro compose <F6> "<tag-entry>4<enter><tag-entry><group-related>"

html-part.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="generator" content="pandoc">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
  <title> </title>
</head>
<body>
<div style="max-width: 15cm;">
<p style="margin: 1ex 0; padding: 0;"><img src="cid:neomutt-256.png" alt="Neomutt Logo"></p>
</div>
</body>
</html>

Final email structure

  I     1 <no description>                                                   [multipart/related, 7bit, 0K]
  I     2 ├─><no description>                                            [multipart/alternative, 7bit, 0K]
- I     3 │ ├─>/tmp/neomutt-hostname-XXXX-XXXXXX-XXXXXXXXX              [text/plain, 7bit, us-ascii, 0.1K] 
  I     4 │ └─>html-part.html                                          [text/html, 8bit, iso-8859-1, 0.4K] 
  I     5 └─>neomutt-256.png                                                      [image/png, base64, 19K] 

Behaviour at receiving end

MUA PGP: Clear PGP: Signed PGP: Signed and Encrypted
Gmail (Android) ✔️ ✔️ n/a
Gmail (Android, Exchange) ✔️¹ ❌² n/a
Gmail (iOS) ✔️ ✔️ n/a
Gmail (Web) ✔️ ✔️ n/a
Mail App (Windows) ✔️ ✔️ n/a
Outlook (Android) ✔️ ✔️ n/a
Outlook (Web) ✔️ ✔️ n/a
Outlook (iOS) ✔️ ✔️ n/a
Outlook (Web) ✔️ ✔️ n/a
Outlook (Windows) ✔️ ✔️ n/a
Proton (Android) ✔️ ❌³ ❌³
Proton (iOS) ✔️ ✔️⁴ ✔️⁴
Proton (Web) ✔️ ✔️ ✔️
Thunderbird (Linux) ✔️ ✔️ ✔️

¹ Also displays embedded image as attachment (but emails from other clients display like this too).
² Embedded image is not displayed and isn't available in attachment list. (Also fails on messages from Thunderbird.)
³ Embedded image is not displayed, but is available in attachment list. (Also fails on messages from Thunderbird.)
⁴ Thinks there is remote content for some reason.

Notes on constructing emails

  • Attachments in the multipart/related group should probably have inline disposition. (Microsoft Outlook shows also shows them as a separate attachment if they have disposition attachment, but Gmail doesn't.) So ensure embedded images have the header Content-Disposition: inline; filename="image.png". The attachment needs to be given a d_filename (with <rename-attachment>) for the filename field to be included.
  • Actual attachments should be placed outside of the multipart/related group.

@flatcap flatcap added type:discuss Your views/opinions are requested type:enhancement Feature Request labels Jan 15, 2022
@flatcap
Copy link
Member

flatcap commented Jan 15, 2022

It works!

@dcpurton
Copy link
Collaborator Author

dcpurton commented Jan 16, 2022

Using a Neomutt macro to pipe the primary attachment (in Markdown format) to a sufficiently complex script does allow you to construct and send a multipart/alternative email wrapped in a multipart/related group if required.

Consider initial email structure like this:

- I     1 /tmp/neomutt-hostname-XXXX-XXXXXX-XXXXXXXXXX                  [text/plain, 7bit, us-ascii, 0.1K]
  A     2 image.png                                                               [image/png, base64, 19K]
  A     3 attachment.pdf                                                   [application/pdf, quoted, 7.1K]

image.png can be referenced in the main file as:

![Alt Text](cid:image.png)

We can send the message with a single macro:

macro compose y "<first-entry><enter-command>set wait_key=no<enter><pipe-entry>msg2html.sh<enter>
  <attach-file>/tmp/neomutt-alternative.html<enter><toggle-unlink><toggle-disposition><tag-entry>
  <first-entry><tag-entry><group-alternatives><enter-command>set wait_key=yes<enter>
  <enter-command>source /tmp/neomutt.rc<enter><send-message>"

msg2html.sh produces /tmp/neomutt-alternative.html using pandoc and parses the Markdown looking for (cid:filename) and creates /tmp/neomutt.rc with the commands to tag the appropriate files and create the related/multipart group. If no (cid:filename) is found, then leave /tmp/neomutt.rc empty.

In the above case /tmp/neomutt.rc would contain:

push "<group-related>"
push "<search>image.png<enter><rename-attachment>image.png<enter><toggle-disposition><tag-entry>"
push "<tag-entry>"

The final email structure before sending will be:

  I     1 <no description>                                                   [multipart/related, 7bit, 0K]
  I     2 ├─><no description>                                            [multipart/alternative, 7bit, 0K]
- I     3 │ ├─>/tmp/neomutt-hostname-XXXX-XXXXXX-XXXXXXXXXX             [text/plain, 7bit, us-ascii, 0.1K]
- I     4 │ └─>/tmp/neomutt-alternative.html                                [text/html, 8bit, utf-8, 0.6K]
  I     5 └─>image.png                                                            [image/png, base64, 19K]
  A     6 attachment.pdf                                                   [application/pdf, quoted, 7.1K]

At send time Neomutt then wraps all this in a multipart/mixed group and signs/encrypts it if required.

And provided you haven't forgot to attach anything, misspelled anything, or omitted the cid: … it will all work fine at the receiving end.

@dcpurton dcpurton changed the title Add support for 'multipart/related' emails Add support for composing 'multipart/related' emails Jan 17, 2022
@dcpurton dcpurton force-pushed the devel/multipart-related branch 2 times, most recently from adffaee to 0a0f353 Compare January 18, 2022 06:58
@dcpurton dcpurton marked this pull request as ready for review January 19, 2022 12:36
@dcpurton dcpurton requested a review from flatcap January 19, 2022 12:40
Copy link
Member

@flatcap flatcap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Rebased
  • Clang-formatted
  • A couple of minor tweaks to the docs

Looking good

send/header.c Show resolved Hide resolved
compose/functions.c Outdated Show resolved Hide resolved
compose/functions.c Outdated Show resolved Hide resolved
email/parse.c Show resolved Hide resolved
email/parse.c Outdated Show resolved Hide resolved
@flatcap
Copy link
Member

flatcap commented Jan 26, 2022

Rebased over a bunch of huge Compose-function renames.

@dcpurton dcpurton force-pushed the devel/multipart-related branch 3 times, most recently from 14fa3a2 to a2c6cec Compare January 27, 2022 13:24
Copy link
Member

@flatcap flatcap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! It was going so well... and you blew it :-)
It doesn't work; the Content-ID that gets sent is garbage.

However, I've pushed some fixes.

In mutt_write_mime_header(), you id = cont->attribute; but you actually wanted cont->value

Just below that, pl_conts gets freed: mutt_param_free(&pl_conts); which means that id is no longer valid. I've added a strdup() and free() to work around this.

I've added a mutt_buffer_dealloc() to check_cid() to cover the possibility of an error.

Finally, in the docs it says: "an HTML part ... needs to contain ... <img src="cid:content-id">"
I've removed the reference to alt="Alt Text" as that's not required.

@dcpurton
Copy link
Collaborator Author

Oh! It was going so well... and you blew it :-) It doesn't work; the Content-ID that gets sent is garbage.

Oops! It definitely worked at one stage.

However, I've pushed some fixes.

Great.

Finally, in the docs it says: "an HTML part ... needs to contain ... <img src="cid:content-id">" I've removed the reference to alt="Alt Text" as that's not required.

Fine.

@flatcap flatcap merged commit 77b59af into master Jan 27, 2022
@flatcap flatcap deleted the devel/multipart-related branch January 31, 2022 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type:discuss Your views/opinions are requested type:enhancement Feature Request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants