<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/assets/rss.xsl" type="text/xsl"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
>
<channel>
	<title>Sacha Chua - category - notmuch</title>
	<atom:link href="https://sachachua.com/blog/category/notmuch/feed/index.xml" rel="self" type="application/rss+xml" />
	<atom:link href="https://sachachua.com/blog/category/notmuch" rel="alternate" type="text/html" />
	<link>https://sachachua.com/blog/category/notmuch/feed/index.xml</link>
	<description>Emacs, sketches, and life</description>
	<lastBuildDate>Fri, 08 Nov 2024 01:48:10 GMT</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>daily</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>11ty</generator>
  <item>
		<title>#EmacsConf backstage: adding notes to Org logbook drawers from e-mails</title>
		<link>https://sachachua.com/blog/2023/10/emacsconf-backstage-adding-notes-to-org-logbook-drawers-from-e-mails/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Sat, 14 Oct 2023 14:25:01 GMT</pubDate>
    <category>emacs</category>
<category>emacsconf</category>
<category>notmuch</category>
		<guid isPermaLink="false">https://sachachua.com/blog/2023/10/emacsconf-backstage-adding-notes-to-org-logbook-drawers-from-e-mails/</guid>
		<description><![CDATA[<p>
Sometimes I want to work with all the talks associated with an email
in my inbox. For example, maybe a speaker said that the draft
schedules are fine, and I want to make a note of that in the
conference Org file.
</p>

<p>
First we start with a function that gets the e-mail addresses for a
talk. Some speakers have different e-mail addresses for public contact
or private contact, and some e-mail us from other addresses.
</p>

<p>
</p><details open=""><summary>emacsconf-mail-get-all-email-addresses: Return all the possible e-mail addresses for TALK.</summary><div class="org-src-container"><pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">emacsconf-mail-get-all-email-addresses</span> (talk)
  <span class="org-doc">"Return all the possible e-mail addresses for TALK."</span>
  (split-string
   (downcase
    (string-join
     (seq-uniq
      (seq-keep
       (<span class="org-keyword">lambda</span> (field) (plist-get talk field))
       <span class="org-highlight-quoted-quote">'</span>(<span class="org-builtin">:email</span> <span class="org-builtin">:public-email</span> <span class="org-builtin">:email-alias</span>)))
     <span class="org-string">","</span>))
   <span class="org-string">" *, *"</span>))
</pre></div></details>
<p></p>

<p>
Then we can use that to find the talks for a given e-mail address.
</p>

<p>
</p><details open=""><summary>emacsconf-mail-talks: Return a list of talks matching EMAIL.</summary><div class="org-src-container"><pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">emacsconf-mail-talks</span> (email)
  <span class="org-doc">"Return a list of talks matching EMAIL."</span>
  (<span class="org-keyword">setq</span> email (downcase (mail-strip-quoted-names email)))
  (seq-filter
   (<span class="org-keyword">lambda</span> (o) (member email (emacsconf-mail-get-all-email-addresses o)))
   (emacsconf-get-talk-info)))
</pre></div></details>
<p></p>

<p>
We can loop over that to add a note for the e-mail.
</p>

<p>
</p><details open=""><summary>emacsconf-mail-add-to-logbook: Add to logbook for all matching talks from this speaker.</summary><div class="org-src-container"><pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">emacsconf-mail-add-to-logbook</span> (email note)
  <span class="org-doc">"Add to logbook for all matching talks from this speaker."</span>
  (<span class="org-keyword">interactive</span>
   (<span class="org-keyword">let*</span> ((email (mail-strip-quoted-names
                  (plist-get (plist-get (notmuch-show-get-message-properties) <span class="org-builtin">:headers</span>)
                             <span class="org-builtin">:From</span>)))
          (talks (emacsconf-mail-talks email)))
     (list
      email
      (read-string (format <span class="org-string">"Note for %s: "</span>
                           (mapconcat (<span class="org-keyword">lambda</span> (o) (plist-get o <span class="org-builtin">:slug</span>))
                                      talks<span class="org-string">", "</span>))))))
  (<span class="org-keyword">save-window-excursion</span>
    (mapc
     (<span class="org-keyword">lambda</span> (talk)
       (emacsconf-add-to-talk-logbook talk note))
     (emacsconf-mail-talks email))))
</pre></div></details>
<p></p>

<p>
The actual addition of notes is handled by these functions. 
</p>

<p>
</p><details open=""><summary>emacsconf-add-to-logbook: Add NOTE as a logbook entry for the current subtree.</summary><div class="org-src-container"><pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">emacsconf-add-to-logbook</span> (note)
  <span class="org-doc">"Add NOTE as a logbook entry for the current subtree."</span>
  (move-marker org-log-note-return-to (point))
  (move-marker org-log-note-marker (point))
  (<span class="org-keyword">with-temp-buffer</span>
    (insert note)
    (<span class="org-keyword">let</span> ((org-log-note-purpose <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">note</span>))
      (org-store-log-note))))
</pre></div></details>
<p></p>

<p>
Then we have a function that looks for the heading for a note and then
adds a logbook entry to it.
</p>

<p>
</p><details open=""><summary>emacsconf-add-to-talk-logbook: Add NOTE as a logbook entry for TALK.</summary><div class="org-src-container"><pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">emacsconf-add-to-talk-logbook</span> (talk note)
  <span class="org-doc">"Add NOTE as a logbook entry for TALK."</span>
  (<span class="org-keyword">interactive</span> (list (emacsconf-complete-talk) (read-string <span class="org-string">"Note: "</span>)))
  (<span class="org-keyword">save-excursion</span>
    (<span class="org-keyword">emacsconf-with-talk-heading</span> talk
      (emacsconf-add-to-logbook note))))
</pre></div></details>
<p></p>

<p>
All together, that makes it easy to use Emacs as a very simple contact
relationship management system where I can take notes based on the
e-mails that come in.
</p>


<figure id="orgec1a590">
<img src="https://sachachua.com/blog/2023/10/emacsconf-backstage-adding-notes-to-org-logbook-drawers-from-e-mails/output-2023-10-14-10:23:29.gif" alt="output-2023-10-14-10:23:29.gif">

<figcaption><span class="figure-number">Figure 1: </span>Logging notes from e-mail</figcaption>
</figure>

<p>
These functions are in <a href="https://git.emacsconf.org/emacsconf-el/tree/emacsconf-mail.el.el">emacsconf-mail.el</a>.
</p>
]]></description>
		</item><item>
		<title>#EmacsConf backstage: reviewing the last message from a speaker</title>
		<link>https://sachachua.com/blog/2023/10/emacsconf-backstage-reviewing-the-last-message-from-a-speaker/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Sat, 07 Oct 2023 22:36:28 GMT</pubDate>
    <category>emacs</category>
<category>emacsconf</category>
<category>notmuch</category>
		<guid isPermaLink="false">https://sachachua.com/blog/2023/10/emacsconf-backstage-reviewing-the-last-message-from-a-speaker/</guid>
		<description><![CDATA[<p>
One of the things I keep an eye out for when organizing EmacsConf is
the most recent time we heard from a speaker. Sometimes life happens
and speakers get too busy to prepare a video, so we might offer to let
them do it live. Sometimes e-mail delivery issues get in the way and
we don't hear from speakers because some server in between has spam
filters set too strong. So I made a function that lists the most
recent e-mail we got from the speaker that includes "emacsconf" in it.
That was a good excuse to learn more about <a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Tabulated-List-Mode.html">tabulated-list-mode</a>.
</p>


<figure id="org68ec3aa">
<img src="https://sachachua.com/blog/2023/10/emacsconf-backstage-reviewing-the-last-message-from-a-speaker/2023-10-07-13-18-04.svg" alt="2023-10-07-13-18-04.svg" class="org-svg">

<figcaption><span class="figure-number">Figure 1: </span>Redacted view of most recent e-mails from speakers</figcaption>
</figure>

<p>
I started by figuring out how to get all the e-mail addresses associated with a talk.
</p>

<p>
</p><details open=""><summary>emacsconf-mail-get-all-email-addresses: Return all the possible e-mail addresses for TALK.</summary><div class="org-src-container"><pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">emacsconf-mail-get-all-email-addresses</span> (talk)
  <span class="org-doc">"Return all the possible e-mail addresses for TALK."</span>
  (split-string
   (downcase
    (string-join
     (seq-uniq
      (seq-keep
       (<span class="org-keyword">lambda</span> (field) (plist-get talk field))
       <span class="org-highlight-quoted-quote">'</span>(<span class="org-builtin">:email</span> <span class="org-builtin">:public-email</span> <span class="org-builtin">:email-alias</span>)))
     <span class="org-string">","</span>))
   <span class="org-string">" *, *"</span>))
</pre></div></details>
<p></p>

<p>
Then I figured out the notmuch search to use to get all messages. Some
people write a lot, so I limited it to just the ones that have
<code>emacsconf</code> as well. Notmuch can return JSON, so that's easy to parse.
</p>

<p>
</p><details open=""><summary>emacsconf-mail-notmuch-tag: Tag to use when searching the Notmuch database for mail.</summary><div class="org-src-container"><pre class="src src-emacs-lisp">(<span class="org-keyword">defvar</span> <span class="org-variable-name">emacsconf-mail-notmuch-tag</span> <span class="org-string">"emacsconf"</span> <span class="org-doc">"Tag to use when searching the Notmuch database for mail."</span>)
</pre></div></details>
<p></p>

<p>
</p><details><summary>emacsconf-mail-notmuch-last-message-for-talk: Return the most recent message from the speakers for TALK.</summary><div class="org-src-container"><pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">emacsconf-mail-notmuch-last-message-for-talk</span> (talk <span class="org-type">&amp;optional</span> subject)
  <span class="org-doc">"Return the most recent message from the speakers for TALK.</span>
<span class="org-doc">Limit to SUBJECT if specified."</span>
  (<span class="org-keyword">let</span> ((message (json-parse-string
                  (shell-command-to-string
                   (format <span class="org-string">"notmuch search &#45;&#45;limit=1 &#45;&#45;format=json \"%s%s\""</span>
                           (mapconcat
                            (<span class="org-keyword">lambda</span> (email) (concat <span class="org-string">"from:"</span> (shell-quote-argument email)))
                            (emacsconf-mail-get-all-email-addresses talk)
                            <span class="org-string">" or "</span>)
                           (emacsconf-surround
                            <span class="org-string">" and "</span>
                            (<span class="org-keyword">and</span> emacsconf-mail-notmuch-tag (shell-quote-argument emacsconf-mail-notmuch-tag))
                            <span class="org-string">""</span> <span class="org-string">""</span>)
                           (emacsconf-surround
                            <span class="org-string">" and subject:"</span>
                            (<span class="org-keyword">and</span> subject (shell-quote-argument subject)) <span class="org-string">""</span> <span class="org-string">""</span>)))
                  <span class="org-builtin">:object-type</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">alist</span>)))
    (cons <span class="org-highlight-quoted-quote">`</span>(email . ,(plist-get talk <span class="org-builtin">:email</span>))
          (<span class="org-keyword">when</span> (&gt; (length message) 0)
            (elt message 0)))))
</pre></div></details>
<p></p>

<p>
Then I could display all the groups of speakers so that it's easy to
check if any of the speakers haven't e-mailed us in a while.
</p>

<p>
</p><details><summary>emacsconf-mail-notmuch-show-latest-messages-from-speakers: Verify that the email addresses in GROUPS have e-mailed recently.</summary><div class="org-src-container"><pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">emacsconf-mail-notmuch-show-latest-messages-from-speakers</span> (groups <span class="org-type">&amp;optional</span> subject)
  <span class="org-doc">"Verify that the email addresses in GROUPS have e-mailed recently.</span>
<span class="org-doc">When called interactively, pop up a report buffer showing the e-mails</span>
<span class="org-doc">and messages by date, with oldest messages on top.</span>
<span class="org-doc">This minimizes the risk of mail delivery issues and radio silence."</span>
  (<span class="org-keyword">interactive</span> (list (emacsconf-mail-groups (seq-filter
                               (<span class="org-keyword">lambda</span> (o) (not (string= (plist-get o <span class="org-builtin">:status</span>) <span class="org-string">"CANCELLED"</span>)))
                               (emacsconf-get-talk-info)))))
  (<span class="org-keyword">let</span> ((results
         (sort (mapcar
                (<span class="org-keyword">lambda</span> (group)
                  (emacsconf-mail-notmuch-last-message-for-talk (cadr group) subject))
                groups)
               (<span class="org-keyword">lambda</span> (a b)
                 (&lt; (<span class="org-keyword">or</span> (alist-get <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">timestamp</span> a) -1)
                    (<span class="org-keyword">or</span> (alist-get <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">timestamp</span> b) -1))))))
    (<span class="org-keyword">when</span> (called-interactively-p <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">any</span>)
      (<span class="org-keyword">with-current-buffer</span> (get-buffer-create <span class="org-string">"*Mail report*"</span>)
        (<span class="org-keyword">let</span> ((inhibit-read-only t))
          (erase-buffer))
        (tabulated-list-mode)
        (<span class="org-keyword">setq</span>
         tabulated-list-entries
         (mapcar
          (<span class="org-keyword">lambda</span> (row)
            (list
             (alist-get <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">thread</span> row)
             (vector
              (alist-get <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">email</span> row)
              (<span class="org-keyword">or</span> (alist-get <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">date_relative</span> row) <span class="org-string">""</span>)
              (<span class="org-keyword">or</span> (alist-get <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">subject</span> row) <span class="org-string">""</span>))))
          results))
        (<span class="org-keyword">setq</span> tabulated-list-format [(<span class="org-string">"Email"</span> 30 t)
                                     (<span class="org-string">"Date"</span> 10 nil)
                                     (<span class="org-string">"Subject"</span> 30 t)])
        (local-set-key (kbd <span class="org-string">"RET"</span>) <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">emacsconf-mail-notmuch-visit-thread-from-summary</span>)
        (tabulated-list-print)
        (tabulated-list-init-header)
        (pop-to-buffer (current-buffer))))
    results))
</pre></div></details>
<p></p>

<p>
If I press <code>RET</code> on a line, I can open the most recent thread. This is
handled by the <code>emacsconf-mail-notmuch-visit-thread-from-summary</code>,
which is simplified by using the thread ID as the tabulated list ID.
</p>


<figure id="org2b2a3f8">
<img src="https://sachachua.com/blog/2023/10/emacsconf-backstage-reviewing-the-last-message-from-a-speaker/2023-10-07-18-21-55.svg" alt="2023-10-07-18-21-55.svg" class="org-svg">

<figcaption><span class="figure-number">Figure 2: </span>Viewing a thread in a different window</figcaption>
</figure>

<p>
</p><details open=""><summary>emacsconf-mail-notmuch-visit-thread-from-summary: Display the thread from the summary.</summary><div class="org-src-container"><pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">emacsconf-mail-notmuch-visit-thread-from-summary</span> ()
  <span class="org-doc">"Display the thread from the summary."</span>
  (<span class="org-keyword">interactive</span>)
  (<span class="org-keyword">let</span> (message-buffer)
    (<span class="org-keyword">save-window-excursion</span>
      (<span class="org-keyword">setq</span> message-buffer (notmuch-show (tabulated-list-get-id))))
    (display-buffer message-buffer t)))
</pre></div></details>
<p></p>

<p>
We haven't heard from a few speakers in a while, so I'll probably
e-mail them this weekend to double-check that I'm not getting delivery
issues with my e-mails to them. If that doesn't get a reply, I might
try other communication methods. If they're just busy, that's cool.
</p>

<p>
It's a lot easier to spot missing or old entries in a table than it is
to try to remember who we haven't heard from recently, so hooray for
<code>tabulated-list-mode</code>!
</p>

<p>
This code is in <a href="https://git.emacsconf.org/emacsconf-el/tree/emacsconf-mail.el">emacsconf-mail.el</a>.
</p>
]]></description>
		</item>
	</channel>
</rss>