[RFC] writing HTML email with notmuch

Antoine Beaupré anarcat at orangeseeds.org
Mon Feb 25 15:01:55 PST 2019


I seem to have forgotten to CC the list in my reply, sorry for the
noise.

I elided the attachment here because I sent a newer version in
87r2bvjx02.fsf at curie.anarc.at.

A.

On 2019-02-25 17:11:26, Antoine Beaupré wrote:
> On 2019-02-25 16:18:03, Daniel Kahn Gillmor wrote:
>> Hi Antoine--
>>
>> thanks for an entertaining and thoughtful read.
>>
>> Your elisp rendered really strangely for me in notmuch mode in emacs:
>>
>>      (widen)
>>      (save-excursion
>>        (set-buffer output-buffer-name)
>>        (markdown-add-xhtml-header-and-footer ""))
>>      (insert "
>>  [ multipart/alternative ]
>>   [ text/plain ]
>>   \n")
>>       (end-of-buffer)
>>       (insert "
>>   [ text/html (hidden) ]
>> \n") (insert-buffer output-buffer-name) (insert "
>>  [ text/plain ]
>>      ;; remove Markdown <pre> markings
>>      (goto-char signature-position)
>>
>> (note the []-delimited emacs buttons above)
>>
>> The issue here appears to be that the MIME boundaries in your message (i
>> won't repeat them here because i don't want to break my own message) are
>> getting mixed up/confused somehow with the mime boundaries you include
>> in the elisp.
>>
>> I'd be happy to compare raw files with you at some point if you want to
>> try to make sense of this.  I'm hoping that the error is in the message
>> generation (that your sending MUA garbled your elisp) rather than in the
>> receipt, because if the receiving side is at fault it's a much worse
>> security risk.
>
> Haha... That's actually kind of hilarious.
>
> I can confirm the badness occured on send here. I've attached my
> precious notmuch-config.el, which has that function (and many more!) as
> a real attachement which should workaround those issues.
>
> ;; autocompletion
> (eval-after-load "notmuch-address"
>   '(progn
>      (notmuch-address-message-insinuate)))
>
> ; don't remember what that is
> (add-hook 'notmuch-show-hook 'visual-line-mode)
>
> (defun notmuch-load-bug (bug)
>   "download and show a Debian bug report with notmuch-slurp-debbug"
>   (interactive "MDebian bug number: ")
>   (message "downloading debian bug %s..." bug)
>   (call-process "notmuch-slurp-debbug" nil nil nil bug)
>   (message "running notmuch-poll...")
>   (notmuch-poll)
>   (message "searching for bug %s..." bug)
>   (notmuch-search bug)
>   (message "bug %s loaded, but really you should use upstream notmuch-slurp-debbug" bug))
>
> ;; attachment checks
> ;;
> ;; should be sent upstream, but needs unit tests in test/T310-emacs.sh
> (defcustom notmuch-message-attach-regex
>   "\\b\\(attache\?ment\\|attached\\|attach\\|pi[¨e]ce\s+jointe?\\)\\b"
>   "Pattern of text announcing there should be an attachment.
>
> This is used by `notmuch-message-check-attach' to check email
> bodies for words that might indicate the email should have an
> attachement. If the pattern matches and there is no attachment (a
> `<#part ...>' magic block), notmuch will show a confirmation
> prompt before sending the email.
>
> The default regular expression is deliberately liberal: we prefer
> false positive than forgotten attachments. This should be
> customized for non-english languages and notmuch welcomes
> additions to the pattern for your native language, unless it
> conflicts with common words in other languages."
>   :type '(regexp)
>   :group 'notmuch-send)
>
> (defun notmuch-message-check-attach ()
>   """Check for missing attachments.
>
> This is normally added to `message-send-hook' and is configured
> through `notmuch-message-attach-regex'."""
>   (save-excursion ;; XXX: this fails somehow: point is at the end of the buffer on error
>     (goto-char (point-min))
>     (if (re-search-forward notmuch-message-attach-regex nil t)
>         (progn
>           (goto-char (point-min))
>           (unless (re-search-forward "<#part [^>]*filename=[^>]*>" nil t)
>             (or (y-or-n-p "Email seem to refer to attachment, but nothing attached, send anyways?")
>                 (error "No attachment found, aborting")))))))
>
> (defcustom notmuch-mua-attachment-regexp
>   "\\b\\(attache\?ment\\|attached\\|attach\\|pi[¨e]ce\s+jointe?\\)\\b"
>   "Message body text indicating that an attachment is expected.
>
> This is not used unless `notmuch-mua-attachment-check' is added
> to `notmuch-mua-send-hook'.")
>
> (defun notmuch-mua-attachment-check ()
>   "Signal an error if the message text indicates that an
> attachment is expected but no MML referencing an attachment is
> found.
>
> Typically this is added to `notmuch-mua-send-hook'."
>   (when (and
> 	 ;; When the message mentions attachment...
> 	 (save-excursion
> 	   (message-goto-body)
> 	   (loop while (re-search-forward notmuch-mua-attachment-regexp (point-max) t)
> 		 ;; For every instance of the "attachment" string
> 		 ;; found, examine the text properties. If the text
> 		 ;; has either a `face' or `syntax-table' property
> 		 ;; then it is quoted text and should *not* cause the
> 		 ;; user to be asked about a missing attachment.
> 		 if (let ((props (text-properties-at (match-beginning 0))))
> 		      (not (or (memq 'syntax-table props)
> 			       (memq 'face props))))
> 		 return t
> 		 finally return nil))
> 	 ;; ...but doesn't have a part with a filename...
> 	 (save-excursion
> 	   (message-goto-body)
> 	   (not (re-search-forward "^<#part [^>]*filename=" nil t)))
> 	 ;; ...and that's not okay...
> 	 (not (y-or-n-p "Attachment mentioned, but no attachment - is that okay?")))
>     ;; ...signal an error.
>     (error "Missing attachment")))
>
> (add-hook 'message-send-hook 'notmuch-mua-attachment-check)
> (add-hook 'message-send-hook 'notmuch-draft--mark-deleted)
> ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
> ;;; keymappings
> ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
> (define-key notmuch-show-mode-map "S"
>   (lambda ()
>     "mark message as spam and advance"
>     (interactive)
>     (notmuch-show-tag '("+spam" "-inbox" "-ham" "-unread" "-flagged"))
>     (notmuch-show-next-open-message t)))
>
> (define-key notmuch-search-mode-map "S"
>   (lambda (&optional beg end)
>     "mark message as spam and advance"
>     (interactive (notmuch-search-interactive-region))
>     (notmuch-search-tag (list "+spam" "-inbox" "-ham" "-unread" "-flagged") beg end t)
>     (anarcat/notmuch-search-next-thread)))
>
> (define-key notmuch-show-mode-map "H"
>   (lambda ()
>     "mark message as ham and advance"
>     (interactive)
>     (notmuch-show-tag '("-spam" "-greyspam" "+inbox" "+ham" "+flagged"))
>     (notmuch-show-next-open-message t)))
>
> (define-key notmuch-search-mode-map "H"
>   (lambda (&optional beg end)
>     "mark message as ham and advance"
>     (interactive (notmuch-search-interactive-region))
>     (notmuch-search-tag (list "-spam" "-greyspam" "+inbox" "+ham" "+flagged") beg end t)
>     (anarcat/notmuch-search-next-thread)))
>
> (define-key notmuch-search-mode-map "u"
>   (lambda (&optional beg end)
>     "undelete and advance"
>     (interactive (notmuch-search-interactive-region))
>     (notmuch-search-tag (list "-deleted") beg end t)
>     (anarcat/notmuch-search-next-thread)))
>
> (define-key notmuch-search-mode-map "d"
>   (lambda (&optional beg end)
>     "delete and advance"
>     (interactive (notmuch-search-interactive-region))
>     (notmuch-search-tag (list "+deleted" "-unread") beg end t)
>     (anarcat/notmuch-search-next-thread)))
>
> (define-key notmuch-show-mode-map "d"
>   (lambda ()
>     "delete current message and advance"
>     (interactive)
>     (notmuch-show-tag '("+deleted" "-unread"))
>     (notmuch-show-next-open-message t)))
>
> ;; https://notmuchmail.org/emacstips/#index17h2
> (define-key notmuch-show-mode-map "b"
>   (lambda (&optional address)
>     "Bounce the current message."
>     (interactive "sBounce To: ")
>     (notmuch-show-view-raw-message)
>     (message-resend address)
>     (kill-buffer)))
>
> ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
> ;;; my custom notmuch functions
> ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
> (defun anarcat/notmuch-search-next-thread ()
>   "Skip to next message from region or point
>
> This is necessary because notmuch-search-next-thread just starts
> from point, whereas it seems to me more logical to start from the
> end of the region."
>   ;; move line before the end of region if there is one
>   (unless (= beg end)
>     (goto-char (- end 1)))
>   (notmuch-search-next-thread))
>
> ;; Linking to notmuch messages from org-mode
> ;; https://notmuchmail.org/emacstips/#index23h2
> (require 'org-notmuch nil t)
> (autoload 'gnus-alias-determine-identity "gnus-alias" "" t)
> (add-hook 'message-setup-hook 'gnus-alias-determine-identity)
>
> (defun anarcat/notmuch-html-convert ()
>   """create an HTML part from a Markdown body
>
> This will not work if there are *any* attachments of any form, those should be added after."""
>   (interactive)
>   (save-excursion
>     ;; wrap signature in a <pre>
>     (message-goto-signature)
>     (forward-line -1)
>     ;; save and delete signature which requires special formatting
>     (setq signature (buffer-substring (point) (point-max)))
>     (delete-region (point) (point-max))
>     ;; set region to top of body then end of buffer
>     (end-of-buffer)
>     (message-goto-body)
>     (narrow-to-region (point) (mark))
>     ;; run markdown on region
>     (setq output-buffer-name "*notmuch-markdown-output*")
>     (markdown output-buffer-name)
>     (widen)
>     (save-excursion
>       (set-buffer output-buffer-name)
>       (end-of-buffer)
>       ;; add signature formatted as <pre>
>       (insert "\n<pre>")
>       (insert signature)
>       (insert "</pre>\n")
>       (markdown-add-xhtml-header-and-footer ""))
>     ;; restore signature
>     (message-goto-signature)
>     (insert signature)
>     (message-goto-body)
>     (insert "<#multipart type=alternative>\n")
>     (end-of-buffer)
>     (insert "<#part type=text/html>\n")
>     (insert-buffer output-buffer-name)
>     (end-of-buffer)
>     (insert "<#/multipart>\n")
>     ;; remove Markdown <pre> markings
>     (goto-char signature-position)
>     (while (re-search-forward "^```" nil t)
>       (replace-match ""))))
>
> (message "anarcat's custom notmuch config loaded")
>
>> On Sun 2019-02-24 20:52:40 -0500, Antoine Beaupré wrote:
>>> PPS: I remember reading about someone wanting to declare a text/markdown
>>> mimetype for email, and remembering it was all backwards and weird and I
>>> can't find the reference anymore. If some lazyweb magic person could
>>> forward the link to me I would be grateful.
>>
>> https://tools.ietf.org/html/rfc7763 -- I'm sure Sean Leonard (the author
>> of this informational RFC) would be open to discussion about what's
>> missing or what could be improved.
>
> I remember reading that RFC and not understanding the point of it, to be
> honest. I mean it's great to mark certain content as markdown - I
> sometimes did that when attaching drafts to my editor, but really I
> would be worried about marking (say) this email as text/markdown because
> I'd be afraid most MUAs wouldn't display it correctly.
>
> But this wasn't the article I had in mind - it was something about doing
> the opposite conversion, if my memory is correct.
>
> A.
> -- 
> I've got to design so you can put it together out of garbage cans. In
> part because that's what I started from, but mostly because I dont
> trust the industrial structurethey might decide to suppress us
> weirdos and try to deny us the parts we need.
>                        - Lee Felsenstein

-- 
My passionate sense of social justice and social responsibility has
always contrasted oddly with my pronounced lack of need for direct
contact with other human beings and communities. I am truly a "lone
traveler" and have never belonged to my country, my home, my friends,
or even my immediate family, with my whole heart; in the face of all
these ties, I have never lost a sense of distance and a need for
solitude.
                       - Albert Einstein


More information about the notmuch mailing list