<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>hi, it&#39;s mike</title>
    <link>https://mike.puddingtime.org/tags/orgcontacts/</link>
    <description>Recent content on hi, it&#39;s mike</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <managingEditor>mike@puddingtime.org (mike)</managingEditor>
    <webMaster>mike@puddingtime.org (mike)</webMaster>
    <copyright>© 2026, mike</copyright>
    <lastBuildDate>Sun, 16 Apr 2023 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://mike.puddingtime.org/tags/orgcontacts/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>An org-contacts source for lbdb</title>
      <link>https://mike.puddingtime.org/posts/2023-04-16-an-org-contacts-source-for-lbdb/</link>
      <pubDate>Sun, 16 Apr 2023 00:00:00 +0000</pubDate><author>mike@puddingtime.org (mike)</author>
      <guid>https://mike.puddingtime.org/posts/2023-04-16-an-org-contacts-source-for-lbdb/</guid>
      <description>I modified a Perl lbdb backend by ‪@publicvoit@graz.social ‬to use my org-contacts with mutt</description>
      <content:encoded><![CDATA[<p>This is a ruby-based back-end for <a href="https://www.spinnaker.de/lbdb/">The Little Brother&rsquo;s Database (lbdb)</a> that looks at a hard-coded <code>org-contacts</code> file. The idea comes from <a href="https://lists.gnu.org/archive/html/emacs-orgmode/2011-10/msg01059.html">a 2011 Perl implementation by Karl Voit</a>. Because I am not a Perl person, when it didn&rsquo;t work out of the box I converted it to Ruby.</p>
<p>And because I&rsquo;ve chosen to treat org-contacts as <code>TODO</code> items for purposes of remembering who to <code>PING</code>, <code>FOLLOWUP</code>, <code>SKED</code>, etc. it has to take the extra step of stripping those keywords from the returned name. Otherwise, my mails to Joe Grudd would be addressed to <code>FOLLOWUP Joe Grudd</code>.</p>
<p>That&rsquo;s a small bit of inelegance in my plaintext CRM setup:  As I&rsquo;ve figured out more about how <code>org-super-agenda</code> works, I&rsquo;ve had glimpses of how the plaintext CRM metadata could just be content in the <code>:PROPERTIES:</code> drawer, and hence invisible for purposes of tools like this, but a few other pieces of passive automation would have to become some sort of org-mode hook and I&rsquo;d lose the utility of tools like Beorg, which can&rsquo;t provide the automation of native Emacs.</p>
<p>For now, it&rsquo;s one of those &ldquo;this design isn&rsquo;t the cleanest, but it&rsquo;s simple and only creates a few easily solved problems&rdquo; things.</p>
<p>Here&rsquo;s the script itself. I put it in <code>~/bin</code> as <code>orgcontact.rb</code>:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env ruby</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get the query string</span>
</span></span><span class="line"><span class="cl"><span class="n">query</span> <span class="o">=</span> <span class="no">ARGV</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Set the path to your Org-contacts file</span>
</span></span><span class="line"><span class="cl"><span class="n">orgmodefile</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;HOME&#39;</span><span class="o">]</span><span class="si">}</span><span class="s2">/org/contacts.org&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Read in the whole contact file</span>
</span></span><span class="line"><span class="cl"><span class="n">raw_contacts</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">orgmodefile</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">** &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Iterate through each contact</span>
</span></span><span class="line"><span class="cl"><span class="n">raw_contacts</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">contact</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="n">contact</span><span class="o">.</span><span class="n">match?</span><span class="p">(</span><span class="sr">/</span><span class="si">#{</span><span class="n">query</span><span class="si">}</span><span class="sr">/i</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Extract the name and email from the contact</span>
</span></span><span class="line"><span class="cl">    <span class="nb">name</span> <span class="o">=</span> <span class="n">contact</span><span class="o">[</span><span class="sr">/^[^\n]*/</span><span class="o">].</span><span class="n">gsub</span><span class="p">(</span><span class="sr">/(PING|INVITE|WRITE|PINGED|FOLLOWUP|SKED|NOTES|SCHEDULED|TIMEOUT|OK)\s+/i</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span>
</span></span><span class="line"><span class="cl">    <span class="n">email</span> <span class="o">=</span> <span class="n">contact</span><span class="o">[</span><span class="sr">/:EMAIL:\s+(.*)$/</span><span class="p">,</span> <span class="mi">1</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Remove tags from the name</span>
</span></span><span class="line"><span class="cl">    <span class="nb">name</span><span class="o">.</span><span class="n">gsub!</span><span class="p">(</span><span class="sr">/:\S+:/</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">puts</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">email</span><span class="si">}</span><span class="se">\t</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="se">\t</span><span class="s2">(org-contacts)&#34;</span> <span class="k">if</span> <span class="nb">name</span> <span class="o">&amp;&amp;</span> <span class="n">email</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div>
<p>Adding <code>m_org_contacts</code> to the <code>METHODS</code> setting then including a little wrapper in <code>~/.lbdbrc</code> doesn&rsquo;t follow the canonical advice on how to configure an lbdb backend, but it works, and it&rsquo;s one less file to put somewhere:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">METHODS=&#34;m_osx_addressbook m_org_contacts&#34;
</span></span><span class="line"><span class="cl">MODULES_PATH=&#34;$MODULES_PATH $HOME/bin/lbdb&#34;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">m_org_contacts_query() {
</span></span><span class="line"><span class="cl">   ~/bin/orgcontact.rb &#34;$1&#34;
</span></span><span class="line"><span class="cl">}</span></span></code></pre></div>
<p>So, the outcome is just:</p>
<ol>
<li>
<p>Start a new message in mutt and start typing the name/address/etc.</p>
</li>
<li>
<p>lbdb provides a list of matches from a few sources I&rsquo;ve set up: org-contacts and macOS address book</p>
</li>
<li>
<p><code>ENTER</code> to select a candidate</p>
<p>b</p>
</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>Making a plaintext personal CRM with org-contacts</title>
      <link>https://mike.puddingtime.org/posts/2023-04-13-making-a-plaintext-personal-crm-with-org-contacts/</link>
      <pubDate>Thu, 13 Apr 2023 00:00:00 +0000</pubDate><author>mike@puddingtime.org (mike)</author>
      <guid>https://mike.puddingtime.org/posts/2023-04-13-making-a-plaintext-personal-crm-with-org-contacts/</guid>
      <description>I don&amp;rsquo;t like the looks of any of the personal CRM software out there, so I&amp;rsquo;m making a plaintext one.</description>
      <content:encoded><![CDATA[<p>This morning Al and I took our coffee walk, but she had to hop on a call, so for the two-mile walk back home I had some time to think about the habit on my list that popped up today: <code>Social Maintenance</code> and I also happen to have, for assorted reasons, a massive amount of poorly directed nervous energy. I am scattered and my thoughts are darting all over the place, and there&rsquo;s enough jittery energy built up  that the thought of cycling through a bunch of &ldquo;what if I try <em>this</em>&rdquo; stuff is sort of comforting.</p>
<p>I started trying to cultivate a social maintenance habit with the thought in mind that I had no idea what I was really thinking, just that during this current period it is important to me to keep up social contact in ways large and small. Pretty soon thereafter I realized I had an organizational problem on my hands: My address books were kind of a mess. Not very well organized, old data, a ton of contacts with old work addresses, etc. I spent a day straightening that out and got to a place of mostly clean.</p>
<p>The next problem that presented itself was that &ldquo;personal CRM&rdquo; is just an  awful software category. Whenever I see a new contact management app, I think &ldquo;oh, this is surely the one that will let you do something with your existing information, or add useful information,&rdquo; but it never seems to be. The more competent looking entries in the market cost a lot. Searching yields a lot of &ldquo;make one in Trello,&rdquo; &ldquo;make one in Notion,&rdquo; etc.</p>
<p>I just stopped thinking about it and decided &ldquo;do it off the top of your head until something comes up for you.&rdquo;</p>
<p>So this morning, Al was on her call, and I had a <code>Social Maintenance</code> habit popping up on my agenda as a thing I was supposed to do today, and I&rsquo;d been reading about <code>org-contacts</code>, <code>org-vcard</code>, and the ways they can integrate with an org-mode agenda to show birthdays or other anniversaries. By a few blocks later I&rsquo;d thought of how I might wedge CRM-like data into that system with org <code>:PROPERTIES:</code> drawers:</p>
<ul>
<li>desired frequency</li>
<li>date last contacted</li>
<li>notes on the last contact</li>
</ul>
<p>This is all stuff you could do in a spreadsheet, and I think a lot of people do it that way. I have an aversion to spreadsheet applications, though.</p>
<p>By the time we were home, I had the beginnings of a plan: Export my macOS address book to a big vcard file, use <code>org-vcard</code> to import it into a contacts file, then start figuring out the mechanics of adding the fields I needed to drive org agenda views.</p>
<h2 id="getting-my-contacts-into-org-contacts">Getting my contacts into org-contacts</h2>
<p>There&rsquo;s an <code>org-vcard</code> package that theoretically handles the process of moving a vcard file into an org file. The maintainer has announced that they&rsquo;re not going to work on it any longer, and it seems to have problems with macOS Contacts output.</p>
<p>I put together a script (well, ChatGPT and I put together a script) that parses a VCF file and dumps the contacts out into the right format. I just cat&rsquo;d its output into the right file. It is probably best described as a menace to your data. I had so recently scrubbed my contacts that I trusted it enough.</p>
<p>An org-contacts record looks like this:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">\* Joe Grudd :social:
</span></span><span class="line"><span class="cl">:PROPERTIES:
</span></span><span class="line"><span class="cl">:EMAIL: joe@grudd.com
</span></span><span class="line"><span class="cl">:WEBSITE: http://joe.grudd.com
</span></span><span class="line"><span class="cl">:CONTACTED: 2023-04-12
</span></span><span class="line"><span class="cl">:END:</span></span></code></pre></div>
<p>There are a few other fields, like birthday and physical address, too.</p>
<p>On its own, it doesn&rsquo;t do a ton. You can add notes to a :NOTES: property if you like, and you can search the entire file with an <code>org-contacts</code> command that lists results instead of just doing a normal text search operation.</p>
<h2 id="tracking-contact-information">Tracking contact information</h2>
<p>There are a few ways I thought of to come at what I wanted to do, which amounted to:</p>
<ul>
<li>
<p>Keeping track of whom I&rsquo;ve had contact with</p>
</li>
<li>
<p>Keeping track of when I last had contact with someone</p>
</li>
<li>
<p>Keeping track of useful details about people</p>
</li>
<li>
<p>Surfacing people I haven&rsquo;t seen in a while</p>
<p>The <code>NOTES</code> property in a vcard record is fine, but org-mode provides a way to add a log to each record in its own drawer, which changes the record to look like this:</p>
</li>
</ul>
<!--listend-->






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-org" data-lang="org"><span class="line"><span class="cl">\* Joe Grudd :social:
</span></span><span class="line"><span class="cl"><span class="c">:PROPERTIES:
</span></span></span><span class="line"><span class="cl"><span class="cs">:EMAIL: joe@grudd.com
</span></span></span><span class="line"><span class="cl"><span class="cs">:WEBSITE: http://joe.grudd.com
</span></span></span><span class="line"><span class="cl"><span class="cs">:CONTACTED: 2023-04-12
</span></span></span><span class="line"><span class="cl"><span class="c">:END:</span>
</span></span><span class="line"><span class="cl"><span class="c">:LOGBOOK:
</span></span></span><span class="line"><span class="cl"><span class="cs">  - Note taken on [2023-04-12 Wed 11:16] \\
</span></span></span><span class="line"><span class="cl"><span class="cs">    Caught up over IM for the first time in a while. He&#39;s moving to California next month.
</span></span></span><span class="line"><span class="cl"><span class="c">  :END:</span></span></span></code></pre></div>
<p>org-mode pretties all this stuff up, so the <code>LOGBOOK</code> and <code>PROPERTIES</code> drawers aren&rsquo;t always visible.</p>
<p>I also added the <code>CONTACTED</code> field to <code>PROPERTIES</code>. It&rsquo;s just an ISO-8601 date meant to reflect the last time I had some kind of contact, even if it&rsquo;s just a ping.</p>
<p>So at this point, I could just use this as is and it&rsquo;d be no worse than a spreadsheet.</p>
<h2 id="automating-updates">Automating updates</h2>
<p>I wanted a way to quickly note a contact &ldquo;touch&rdquo; so I made a few functions for that:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nb">defun</span> <span class="nv">org-set-contacted-today</span> <span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;Set the CONTACTED property of the current item to today&#39;s date.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="nb">interactive</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="nv">org-set-property</span> <span class="s">&#34;CONTACTED&#34;</span> <span class="p">(</span><span class="nf">format-time-string</span> <span class="s">&#34;%Y-%m-%d&#34;</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">defun</span> <span class="nv">org-set-contacted-date</span> <span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;Set the CONTACTED property of the current item to a chosen date.&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="nb">interactive</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="nb">let</span> <span class="p">((</span><span class="nv">date</span> <span class="p">(</span><span class="nv">org-read-date</span> <span class="no">nil</span> <span class="no">t</span> <span class="no">nil</span> <span class="s">&#34;Enter the date: &#34;</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="nv">org-set-property</span> <span class="s">&#34;CONTACTED&#34;</span> <span class="p">(</span><span class="nf">format-time-string</span> <span class="s">&#34;%Y-%m-%d&#34;</span> <span class="nv">date</span><span class="p">))))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">map!</span> <span class="nb">:mode</span> <span class="nv">org-mode</span>
</span></span><span class="line"><span class="cl">      <span class="nb">:localleader</span>
</span></span><span class="line"><span class="cl">      <span class="nb">:desc</span> <span class="s">&#34;Set CONTACTED property to today&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;c t&#34;</span> <span class="nf">#&#39;</span><span class="nv">org-set-contacted-today</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;c d&#34;</span> <span class="nf">#&#39;</span><span class="nv">org-set-contacted-date</span>
</span></span><span class="line"><span class="cl">      <span class="s">&#34;c z&#34;</span> <span class="nf">#&#39;</span><span class="nv">my/org-remove-todo</span>
</span></span><span class="line"><span class="cl">                <span class="p">)</span></span></span></code></pre></div>
<p>Those two allow me to set the <code>CONTACTED</code> property either to today&rsquo;s date (<code>spc m c t</code>), or by interactively selecting a date (<code>spc m c d</code>). There&rsquo;s a third mapping that lets me z out the TODO status of a contact (<code>spc m c z</code>), which I will get to.</p>
<h2 id="agenda-customization">Agenda customization</h2>
<p>Next up, I wanted some kind of agenda automation &ndash; custom views that&rsquo;d let me see contacts overdue for some kind of ping. I made a few driven by a combination of tags and age of the <code>CONTACTED</code> field.</p>
<p>Here&rsquo;s one of them, driven by a function that finds aged contacts:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nv">add-to-list</span> <span class="ss">&#39;org-agenda-custom-commands</span>
</span></span><span class="line"><span class="cl">             <span class="o">&#39;</span><span class="p">(</span><span class="s">&#34;N&#34;</span> <span class="s">&#34;Professional network last contacted &gt; 90 days ago&#34;</span>
</span></span><span class="line"><span class="cl">               <span class="p">((</span><span class="nv">tags</span> <span class="s">&#34;network&#34;</span>
</span></span><span class="line"><span class="cl">                      <span class="p">((</span><span class="nv">org-agenda-overriding-header</span> <span class="s">&#34;Network contacts, not contacted in the past 90 days&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                       <span class="p">(</span><span class="nv">org-tags-match-list-sublevels</span> <span class="no">t</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                       <span class="p">(</span><span class="nv">org-agenda-skip-function</span>
</span></span><span class="line"><span class="cl">                        <span class="p">(</span><span class="nb">lambda</span> <span class="p">()</span>
</span></span><span class="line"><span class="cl">                          <span class="p">(</span><span class="nb">unless</span> <span class="p">(</span><span class="nv">org-contacted-more-than-days-ago</span> <span class="mi">90</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                            <span class="p">(</span><span class="nb">or</span> <span class="p">(</span><span class="nv">outline-next-heading</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                                <span class="p">(</span><span class="nf">goto-char</span> <span class="p">(</span><span class="nf">point-max</span><span class="p">))))))))</span>
</span></span><span class="line"><span class="cl">                <span class="p">)))</span></span></span></code></pre></div>
<p>So in Doom, I can tap <code>spc oAN</code> and get a list of contacts tagged with <code>network</code> whom I haven&rsquo;t had any contact with for more than 90 days.</p>
<h2 id="setting-priority-by-touch-date">Setting priority by touch date</h2>
<p>I wanted a way to see which contacts were aging and decided to use plain old priorities for that, so a function looks at the difference between today and <code>CONTACTED</code> and prioritizes more aged contacts higher:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nb">defun</span> <span class="nv">my/org-update-priorities-based-on-contacted</span> <span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="nb">interactive</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="nb">save-excursion</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="nf">goto-char</span> <span class="p">(</span><span class="nf">point-min</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="nb">while</span> <span class="p">(</span><span class="nf">re-search-forward</span> <span class="s">&#34;^\\*+ &#34;</span> <span class="no">nil</span> <span class="no">t</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">(</span><span class="nb">let</span> <span class="p">((</span><span class="nv">contacted-date</span> <span class="p">(</span><span class="nv">org-entry-get</span> <span class="p">(</span><span class="nf">point</span><span class="p">)</span> <span class="s">&#34;CONTACTED&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="p">(</span><span class="nv">today</span> <span class="p">(</span><span class="nf">format-time-string</span> <span class="s">&#34;%Y-%m-%d&#34;</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="nb">when</span> <span class="nv">contacted-date</span>
</span></span><span class="line"><span class="cl">          <span class="p">(</span><span class="nb">let</span> <span class="p">((</span><span class="nv">days-ago</span> <span class="p">(</span><span class="nf">-</span> <span class="p">(</span><span class="nv">time-to-days</span> <span class="p">(</span><span class="nf">current-time</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                             <span class="p">(</span><span class="nv">time-to-days</span> <span class="p">(</span><span class="nv">org-time-string-to-time</span> <span class="nv">contacted-date</span><span class="p">)))))</span> <span class="c1">; calculate days since CONTACTED date</span>
</span></span><span class="line"><span class="cl">            <span class="p">(</span><span class="nv">org-set-property</span> <span class="s">&#34;PRIORITY&#34;</span>
</span></span><span class="line"><span class="cl">                               <span class="p">(</span><span class="nb">cond</span>
</span></span><span class="line"><span class="cl">                                <span class="p">((</span><span class="nf">&lt;</span> <span class="nv">days-ago</span> <span class="mi">45</span><span class="p">)</span> <span class="s">&#34;C&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                                <span class="p">((</span><span class="nf">&lt;</span> <span class="nv">days-ago</span> <span class="mi">90</span><span class="p">)</span> <span class="s">&#34;B&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                                <span class="p">(</span><span class="no">t</span> <span class="s">&#34;A&#34;</span><span class="p">)))))))))</span></span></span></code></pre></div>
<p>It&rsquo;s connected to a save hook, so every contact&rsquo;s priority gets recalculated at save:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="nv">add-hook</span> <span class="ss">&#39;after-save-hook</span>
</span></span><span class="line"><span class="cl">          <span class="p">(</span><span class="nb">lambda</span> <span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="p">(</span><span class="nb">when</span> <span class="p">(</span><span class="nf">string-equal</span> <span class="p">(</span><span class="nf">buffer-file-name</span><span class="p">)</span> <span class="s">&#34;~/org/contacts.org&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">              <span class="p">(</span><span class="nv">my/org-update-priorities-based-on-contacted</span> <span class="p">))))</span></span></span></code></pre></div>
<p>That prioritization shows up in the agenda views I set up, with <code>[A]</code> priority contacts getting their own section.</p>
<h2 id="custom-todo-states">Custom TODO states</h2>
<p>Finally, I wanted a way to keep track of where a given contact is, or cue myself on next steps, so I set up custom TODO states just for my <code>contacts.org</code> file:</p>
<p><code>#+TODO: PING(p) PINGED(P!) FOLLOWUP(f) SKED(s) | TIMEOUT(t) OK(o)</code></p>
<p>By putting a <code>!</code> inside the shortcut parens, org-mode will automatically log changes in and out of those states. For now I just have <code>PINGED</code> wired up that way, and <code>TIMEOUT</code> and <code>OK</code>, as <code>DONE</code> equivalents will similarly trigger a log entry.</p>
<p><code>FOLLOWUP</code> and <code>SKED</code> are there as reminders that I need to do something next. <code>TIMEOUT</code> is a way to tell myself I gave it a shot and nothing came of it. <code>OK</code> is just an interim state on the way to no state until the agenda surfaces someone again.</p>
<p>The logging for these state changes looks like this in a given contact entry:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-org" data-lang="org"><span class="line"><span class="cl">\* PINGED Joe Grudd :social:
</span></span><span class="line"><span class="cl"><span class="c">:PROPERTIES:
</span></span></span><span class="line"><span class="cl"><span class="cs">:EMAIL: joe@grudd.com
</span></span></span><span class="line"><span class="cl"><span class="cs">:WEBSITE: http://joe.grudd.com
</span></span></span><span class="line"><span class="cl"><span class="cs">:CONTACTED: 2023-04-12
</span></span></span><span class="line"><span class="cl"><span class="c">:END:</span>
</span></span><span class="line"><span class="cl"><span class="c">:LOGBOOK:
</span></span></span><span class="line"><span class="cl"><span class="cs">- State &#34;PINGED&#34;     from &#34;PING&#34;   [2023-04-11 Tue 20:21]
</span></span></span><span class="line"><span class="cl"><span class="cs">- Note taken on [2023-04-12 Wed 11:16] \\
</span></span></span><span class="line"><span class="cl"><span class="cs">  Caught up over text for the first time in a while. He&#39;s moving to California next month.
</span></span></span><span class="line"><span class="cl"><span class="c">:END:</span></span></span></code></pre></div>
<p>This is also where that last &ldquo;z&rdquo; keybinding comes in:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nb">defun</span> <span class="nv">my/org-remove-todo</span> <span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="nb">interactive</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="nv">org-set-property</span> <span class="s">&#34;TODO&#34;</span> <span class="s">&#34;&#34;</span><span class="p">))</span></span></span></code></pre></div>
<p>With <code>spc m c z</code> I can zero out the TODO state of a given contact without triggering a log entry, keeping a little bit of noise down.</p>
<h2 id="what-else">What else?</h2>
<p>That&rsquo;s the system. I guess the summary is:</p>
<p>&ldquo;I&rsquo;ve used org-contacts to keep my contacts in a plaintext file. By using existing data features and agenda customizations, I get prompts that will help me cultivate my <code>Social Maintenance</code> habit when it&rsquo;s due. With a few custom functions and a save hook, I can use light automation to make the text more dynamic without a lot of day-to-day effort.&rdquo;</p>
<p>And I&rsquo;m a little more clear on what &ldquo;social maintenance&rdquo; can mean, now. I think I&rsquo;d created an ill-defined monster the longer I let it sit there with no shape. As I put this together I got to think about what would be meaningful, and I realized it just makes my day when I get a text from someone asking how it&rsquo;s going, so that&rsquo;s a fine standard to apply.</p>
<p>And yes, it was an interesting ChatGPT exercise. It would have taken me days to suss all this out on my own. I just don&rsquo;t have the elisp. It took much less time just dialoging with the bot, and it let me work much more iteratively if an idea didn&rsquo;t test quite right. I wonder how this would have gone if I&rsquo;d thought of trying it when I was using Obsidian a lot, or if I&rsquo;d been in more of a Rails or Sinatra mood.</p>
<p>I think the whole thing will seem like overkill to some, but I am not good at keeping up with people. I am not going to go all autobiographical to explain it, I&rsquo;m just gonna say that there is what I want to do and there is what I do, and they aren&rsquo;t aligned, and I know enough about myself to know that in the absence of a supporting system my good intentions will not mean anything.</p>
<p>And I&rsquo;ve had a few recent interactions with people I haven&rsquo;t spoken to in a long time. It feels really good to reconnect, even if it&rsquo;s just a few lines of &ldquo;what&rsquo;s up with you?&rdquo; So I&rsquo;ve built a system to help me get more of that.</p>
]]></content:encoded>
    </item>
    <item>
      <title>mutt to org-contacts</title>
      <link>https://mike.puddingtime.org/posts/2023-04-15-mutt-to-org-contacts/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>mike@puddingtime.org (mike)</author>
      <guid>https://mike.puddingtime.org/posts/2023-04-15-mutt-to-org-contacts/</guid>
      <description>A little script to copy address information from mutt messages into an org-contacts file.</description>
      <content:encoded><![CDATA[<p>Part of making the whole <a href="https://mike.puddingtime.org/posts/20230413-making-a-plaintext-personal-crm-with-org-contacts/">plaintext CRM</a>  thing work involves capture: Getting contact information into the database. I made a capture template for <a href="https://beorgapp.com/">beorg</a> to make it easier to capture a contact on mobile, but I have a bunch of recent email contacts I&rsquo;ve wanted to add, too, and no easy way to get them out of mutt.</p>
<p>For all of its talents, mutt doesn&rsquo;t choose to expose any message variables to its macro functionality. If you want to extract a sender&rsquo;s name or email address or whatever, the message has to be piped. I put something like that together to <a href="https://mike.puddingtime.org/posts/2023-04-09-macros-to-score-mail-in-mutt/">add senders to a mutt score file</a>, and there are a few other things out there in the mutt ecosystem, like lbdb, that capture email addresses. lbdb could have been great for this, but its whole point is capture to a database of its own.</p>
<p>I&rsquo;ve been poking around with getting ChatGPT to write little utilities for me, so I put this problem to it, asking for a Ruby script that could pipe a message through formail to extract the needed name and email address.</p>
<p>It took a second prompt &ndash; initially it just wrote the code to process the input without specifying the input &ndash; but it produced something workable to get the two pieces of data out, and I added an org-contacts template as a <code>HERE</code> doc the script writes into a <code>mutt_contacts.org</code> file. I could have sent it straight to <code>contacts.org</code> but prefer to automate into buffer files to keep the chance of conflicts, sync or otherwise, to a minimum.</p>
<p>The accompanying mutt macro looks like this:</p>
<p><code>macro index,pager .oc &quot;|~/.mutt/org_contact.rb\n&quot;</code></p>
<p>I tap <code>.oc</code> when positioned on a message and it pipes into the script.</p>
<p>I&rsquo;d like to extend the macro to include a quick note, but for now it just adds the <code>NOTES</code> todo state and schedules the entry a day out to remind me to put something in there soon: The new entries turn up in my org-mode agenda for processing and re-filing into my <code>contacts.org</code> file.</p>
<p>And it worked well enough: Once I set it up I was able to run through and add nine or ten new contacts in a few seconds, then visit them in org-mode and refile them all.</p>
<p>And yeah &hellip;still holding the line against adding an MUA to Emacs proper. I just don&rsquo;t want to do the whole OfflineIMAP thing or similar, and I am still keeping things relatively simple. Adding <code>org-caldav</code> today was a small step in the wrong direction, but I like having calendar entries in my org agenda, it syncs relatively quickly once it does the initial download, and it has no system dependencies.</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env ruby</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;open3&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;date&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">contacts_file</span> <span class="o">=</span> <span class="s2">&#34;~/org/mutt_contacts.org&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">message_contents</span> <span class="o">=</span> <span class="vg">$stdin</span><span class="o">.</span><span class="n">read</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Define the command to extract headers with formail</span>
</span></span><span class="line"><span class="cl"><span class="n">command</span> <span class="o">=</span> <span class="s1">&#39;formail -X From: -X Sender: -X Reply-To: -x To: -x Cc: -x Bcc:&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Execute the command and capture the output</span>
</span></span><span class="line"><span class="cl"><span class="n">output</span><span class="p">,</span> <span class="n">status</span> <span class="o">=</span> <span class="no">Open3</span><span class="o">.</span><span class="n">capture2</span><span class="p">(</span><span class="n">command</span><span class="p">,</span> <span class="ss">stdin_data</span><span class="p">:</span> <span class="n">message_contents</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">status</span><span class="o">.</span><span class="n">success?</span>
</span></span><span class="line"><span class="cl">  <span class="n">email</span> <span class="o">=</span> <span class="kp">nil</span>
</span></span><span class="line"><span class="cl">  <span class="nb">name</span> <span class="o">=</span> <span class="kp">nil</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Parse the output and extract the email and name from the From, Sender, and Reply-To headers</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">.</span><span class="n">lines</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">line</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="n">line</span>
</span></span><span class="line"><span class="cl">    <span class="k">when</span> <span class="sr">/^From:\s*(.*)$/i</span><span class="p">,</span> <span class="sr">/^Sender:\s*(.*)$/i</span><span class="p">,</span> <span class="sr">/^Reply-To:\s*(.*)$/i</span>
</span></span><span class="line"><span class="cl">      <span class="n">from</span> <span class="o">=</span> <span class="vg">$1</span><span class="o">.</span><span class="n">strip</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1"># Extract the email and name from the From, Sender, or Reply-To header</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="n">from</span> <span class="o">=~</span> <span class="sr">/(.+?)\s*&lt;(.+?)&gt;/</span>
</span></span><span class="line"><span class="cl">        <span class="n">email</span> <span class="o">=</span> <span class="vg">$2</span>
</span></span><span class="line"><span class="cl">        <span class="nb">name</span> <span class="o">=</span> <span class="vg">$1</span><span class="o">.</span><span class="n">strip</span>
</span></span><span class="line"><span class="cl">      <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="n">email</span> <span class="o">=</span> <span class="n">from</span>
</span></span><span class="line"><span class="cl">        <span class="nb">name</span> <span class="o">=</span> <span class="s2">&#34;No Name Found&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1"># Found the email and name, so break the loop</span>
</span></span><span class="line"><span class="cl">      <span class="k">break</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">org_template</span> <span class="o">=</span> <span class="o">&lt;&lt;~</span><span class="no">TEMPLATE</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="o">**</span> <span class="no">NOTES</span> <span class="c1">#{name} :mutt:</span>
</span></span><span class="line"><span class="cl">  <span class="ss">SCHEDULED</span><span class="p">:</span> <span class="c1">#{(Date.today + 1).strftime(&#34;%Y-%m-%d&#34;)}</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:PROPERTIES</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:EMAIL</span><span class="p">:</span> <span class="c1">#{email}</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:PHONE</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:BIRTHDAY</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:CONTACTED</span><span class="p">:</span> <span class="c1">#{Date.today.strftime(&#34;%Y-%m-%d&#34;)}</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:END</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">TEMPLATE</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">File</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">contacts_file</span><span class="p">,</span> <span class="s2">&#34;a&#34;</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span> <span class="n">f</span><span class="o">.</span><span class="n">write</span> <span class="n">org_template</span><span class="p">}</span></span></span></code></pre></div>
]]></content:encoded>
    </item>
  </channel>
</rss>
