Extending the plaintext CRM to mail contacts

· 613 words · 3 minute read

I did some menu cleanup and refactor today to get my plaintext CRM into a slightly more mnemonic state. Here are the mappings, which are readable enough:

(map! :mode org
      :leader
     (:prefix-map ("C" . "CRM")
        :desc "Schedule Contact" "s" #'org-schedule-heading
        :desc "Clear TODO states" "z" #'my/org-remove-todo
        :desc "Update CONTACTED to today" "t" #'org-set-contacted-today
        :desc "Update CONTACTED to ..." "d" #'org-set-contacted-date
        :desc "SCHEDULE a date" "S" #'(lambda () (interactive) (my/org-set-heading-state-and-time "" 30 's))
        :desc "Mail this contact" "m" #'my-org-contacts-email)
     (:prefix-map ("C r". "Remember to ...")
       :desc "... write within 7 days" "w" #'(lambda () (interactive) (my/org-set-heading-state-and-time "WRITE" 7 'd))
       :desc "... followup in 3 days" "f" #'(lambda () (interactive) (my/org-set-heading-state-and-time "FOLLOWUP" 3 's))
       :desc "... ping within 7 days" "p" #'(lambda () (interactive) (my/org-set-heading-state-and-time "PING" 3 'd))
       :desc "... invite within 3 days" "i" #'(lambda () (interactive) (my/org-set-heading-state-and-time "INVITE" 3 'd)))
)

It’s a bunch of “tap the leader key, tap “C” for “CRM,” then do some common stuff,” like setting deadlines to write someone, or update the :CONTACTED: property, or just set the SCHEDULED: date on a record. This is the my/org-set-heading-state-and-time function:

(defun my/org-set-heading-state-and-time (state days &optional time-type)
  "Sets the TODO state and deadline or scheduled date of the current heading.
   STATE is the new TODO state to set, and DAYS is the number
   of days from the current date to set the new time. If TIME-TYPE
   is 'd', sets a deadline; if 's', sets a scheduled date; otherwise,
   prompts the user for the time type. Removes any existing schedules
   or deadlines before setting the new time."
  (interactive (list "WRITE" 7 nil))
  (org-entry-put nil "TODO" state)
  (when (org-entry-get nil "DEADLINE")
    (org-entry-delete nil "DEADLINE"))
  (when (org-entry-get nil "SCHEDULED")
    (org-entry-delete nil "SCHEDULED"))
  (let ((new-time (format-time-string "<%Y-%m-%d %a>"
                                      (time-add (current-time) (days-to-time days)))))
    (cond ((equal time-type 'd)
           (org-deadline nil new-time))
          ((equal time-type 's)
           (org-schedule nil new-time))
          (t
           (setq time-type (completing-read "Set time type (d/s): "))
           (my/org-set-heading-state-and-time state days (if (string= time-type "d") 'd 's))))))

It’s written with the menu system in mind. It’d be too clunky to use interactively – too many possible states to remember, etc. but as part of a bunch of canned menu options you can get to with one or two taps it saves a bunch of typing and cursor motion for common operations.

Then I thought, “it’d be handy to just visit a record and have an option to compose a mail,” so my-org-contacts-email was born:

(defun my-org-contacts-email ()
  "Open am email message to the email address in the EMAIL property of the current org-contacts heading."
  (interactive)
  (when (eq major-mode 'org-mode)
    (let ((email (org-entry-get (point) "EMAIL")))
      (if email
          (progn
            (unless (featurep 'mu4e)
              (mu4e))
            (mu4e-compose-new)
            (message-goto-to)
            (insert email)
            (message-goto-body)
            (insert "\n\n"))
        (message "No email address found.")))))

It has some behavioral issues I need to straighten out, but if mu4e is running and I tap SPC C m while positioned over a contact, it opens and pre-addresses a new message in mu4e.

I think it’s beyond the ken of org-caldav, but I suppose a call out to AppleScript could create similar functionality for scheduling things with a contact.

I really like the Doom menu system (and I suppose I’d like Spacemacs’ as well). The last time I was all-in on org mode I had so much trouble with all the Emacs chords that I ended up setting up org-drill to periodically train. With Doom’s menus, there are decent mnemonics up front, then visual reminders along the way. It still takes time to learn everything, but you get reminders and you can stop to study the menu if you forget. I don’t know how many times I have mashed CTRL g when I lost track of my fingers during a complex vanilla Emacs sequence.