<?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 - tag - crm</title>
	<atom:link href="https://sachachua.com/blog/tag/crm/feed/index.xml" rel="self" type="application/rss+xml" />
	<atom:link href="https://sachachua.com/blog/tag/crm" rel="alternate" type="text/html" />
	<link>https://sachachua.com/blog/tag/crm/feed/index.xml</link>
	<description>Emacs, sketches, and life</description>
  
	<lastBuildDate>Mon, 06 Jul 2026 16:20:27 GMT</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>daily</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>11ty</generator>
  <item>
		<title>BBDB: Print birthdates</title>
		<link>https://sachachua.com/blog/2007/01/bbdb-print-birthdates/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Tue, 16 Jan 2007 00:30:00 GMT</pubDate>
    <category>bbdb</category>
<category>emacs</category>
		<guid isPermaLink="false">https://sachachua.com/blog/?p=4142</guid>
		<description><![CDATA[<p>This snippet goes through all the records in my Big Brother Database,<br>
prints out birthdate and a link to the record, and then sorts the<br>
results.</p>
<pre class="example">
(defun sacha/bbdb-insert-birthdates ()
  "Insert a list of birthdates, sorted by month.
For best effect, dates should be of the form yyyy.mm.dd."
  (insert
   (with-temp-buffer
     (mapcar
      (lambda (rec)
        (when (bbdb-record-getprop rec 'birthdate)
          (insert
           (if (string-match "..\\...$" (bbdb-record-getprop rec 'birthdate))
               (match-string 0 (bbdb-record-getprop rec 'birthdate))
             (bbdb-record-getprop rec 'birthdate))
           " | "
           (planner-make-link
            (concat "bbdb://"
                    (planner-replace-regexp-in-string
                     " " "." (bbdb-record-name rec)))
            (bbdb-record-name rec))
           "\n")))
      (bbdb-records))
     (sort-lines nil (point-min) (point-max))
     (buffer-string)))
  nil)
</pre>

<p>Random Emacs symbol: find-tag-noselect &#8211; Command: Find tag (in current tags table) whose name contains TAGNAME.</p>

<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2007%2F01%2Fbbdb-print-birthdates%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item><item>
		<title>Emacs BBDB: Prioritize exact matches</title>
		<link>https://sachachua.com/blog/2006/09/emacs-bbdb-prioritize-exact-matches/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Fri, 29 Sep 2006 19:54:00 GMT</pubDate>
    <category>bbdb</category>
<category>emacs</category>
		<guid isPermaLink="false">https://sachachua.com/blog/?p=3890</guid>
		<description><![CDATA[<p>I often include people's names in my notes on other people, such as<br>
when I'm tracking who introduced me to whom. The following code<br>
modifies BBDB's behavior to put exact matches for name, company, or<br>
network address above matches for notes.</p>
<pre class="example">
(defun sacha/bbdb (string elidep)
  "Display all entries in the BBDB matching the regexp STRING
in either the name(s), company, network address, or notes.
Prioritize non-note matches."
  (interactive
   (list (bbdb-search-prompt "Search records %m regexp: ")
         current-prefix-arg))
  (let* ((bbdb-display-layout (bbdb-grovel-elide-arg elidep))
         (notes (cons '* string))
         (records-top
          (bbdb-search (bbdb-records) string string string nil
                       nil))
         (records
          (bbdb-search (bbdb-records) string string string notes
                       nil))
         temp)
    (setq temp records-top)
    (while temp
      (setq records (delete (car temp) records))
      (setq temp (cdr temp)))
    (if (or records-top records)
        (bbdb-display-records (append
                               records-top
                               records))
      ;; we could use error here, but it's not really an error.
      (message "No records matching '%s'" string))))

(defalias 'bbdb 'sacha/bbdb)
</pre>


<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2006%2F09%2Femacs-bbdb-prioritize-exact-matches%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item><item>
		<title>Emacs BBDB: Filtering tags with the power of lambda expressions</title>
		<link>https://sachachua.com/blog/2006/09/emacs-bbdb-filtering-tags-with-the-power-of-lambda-expressions/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Fri, 29 Sep 2006 15:28:00 GMT</pubDate>
    <category>bbdb</category>
<category>connecting</category>
<category>emacs</category>
		<guid isPermaLink="false">https://sachachua.com/blog/?p=3896</guid>
		<description><![CDATA[<p>What do you do when you're into both Emacs geeks and social<br>
networking? Well, you build a really really weird contact management<br>
tool, that's what!</p>
<p>One of the things I often need to do is filter my contacts for a<br>
particular set of interests. I would have no idea how to do this in<br>
Microsoft Outlook and other proprietary contact management systems.<br>
Because Emacs is infinitely programmable, though, I can just hack it<br>
in.</p>
<p>You'd expect the intersection of the set &#8220;emacs geek&#8221; and the set<br>
&#8220;social networker&#8221; to be a null set or a singleton (me!). As it turns<br>
out, there's at least one other geek in this space &#8211; hooray!</p>
<p>Paul Lussier's been bouncing all sorts of<br>
crazy ideas off me, which explains all the weird<br>
porridge-and-toe-nails posts of Emacs Lisp code on my blog lately.<br>
He's responsible for my puttting together yesterday's LinkedIn<br>
importer. Today, he wrote:</p>
<blockquote><p> Then found your sacha/bbdb-search-tags stuff which<br>
totally, completely rocks. I just wish I had the first inkling as to<br>
how it worked :) Now, the question I have is: How can I use<br>
sacha/bbdb-search-tags to search for entries which are tagged with one<br>
label, but NOT with another? For example, I want to search on: (and<br>
(taq eq &#8220;planner&#8221;) (not (tag eq &#8220;muse&#8221;))) </p></blockquote>
<p>I'd completely forgotten about sacha/bbdb-search-tags! Anyway, I'd<br>
been meaning to write a fancy alias management thing for a while now,<br>
and this code does a reasonable job for me. I can now filter my<br>
displayed records by arbitrary Lisp expressions, bringing me closer to<br>
insane contact relationship management. I mean, c'mon&#8230;</p>
<pre class="example">
;; M-x sacha/bbdb-filter-by-alias-function RET
;;     (lambda (aliases) (and (member "planner" aliases)
;;                       (not (member "muse" aliases))))) RET
</pre>
<p>If I do this often enough, I might make up an easier syntax, but<br>
lisp expressions work fine for me.</p>
<p>MWAHAHAHAHA!</p>
<p>Here's the code:</p>
<pre class="example">
;;;_+ Mail aliases

;; Code for working with aliases

;; You can use "a" (bbdb-add-or-remove-mail-alias) in BBDB buffers to add
;; a mail alias to the current entry, or "* a" to add a mail alias to
;; all displayed entries.

;; Goal: Be able to specify ALIAS and ALIAS
;; M-x sacha/bbdb-filter-displayed-records-by-alias RET alias alias
;; Goal: Be able to specify ALIAS or ALIAS
;; C-u M-x sacha/bbdb-filter-displayed-records-by-alias RET alias alias
;; Goal: Be able to specify not ...
;; M-x sacha/bbdb-omit-displayed-records-by-alias RET alias alias
;; C-u M-x sacha/bbdb-omit-displayed-records-by-alias RET alias alias

(defun sacha/bbdb-filter-by-alias-match-all (query-aliases record-aliases)
  "Return non-nil if all QUERY-ALIASES are in RECORD-ALIASES."
  (let ((result t))
    (while query-aliases
      (unless (member (car query-aliases) record-aliases)
        (setq query-aliases nil
              result nil))
      (setq query-aliases (cdr query-aliases)))
    result))

(defun sacha/bbdb-filter-by-alias-match-any (query-aliases record-aliases)
  "Return non-nil if any in QUERY-ALIASES can be found in RECORD-ALIASES."
  (let (result)
    (while query-aliases
      (when (member (car query-aliases) record-aliases)
        (setq query-aliases nil
              result t))
      (setq query-aliases (cdr query-aliases)))
    result))

;; Moved this to a convenience function so that we don't
;; have to deal with invert and property splitting.
(defun sacha/bbdb-filter-by-alias (bbdb-records
                                   alias-filter-function
                                   query
                                   &optional invert)
  "Return only the BBDB-RECORDS that match ALIAS-FILTER-FUNCTION.
ALIAS-FILTER-FUNCTION should accept two arguments:
 - QUERY, a list of keywords to search for
 - aliases, a list of keywords from the record
If INVERT is non-nil, return only the records that do
not match."
  (delq nil
        (mapcar
         (lambda (rec)
           (if (funcall alias-filter-function
                        query
                        (split-string
                         (or (bbdb-record-getprop
                              (if (vectorp rec)
                                  rec
                                (car rec))
                              propsym) "")
                         "[ \n\t,]+"))
               (when (null invert) rec)
             (when invert rec)))
         bbdb-records)))

;; Splitting this into two functions because of interactive calling.
(defun sacha/bbdb-filter-displayed-records-by-alias (query &optional any)
  "Display only records whose mail-aliases match QUERY.
If ANY is non-nil, match if any of the keywords in QUERY are
present.
See also `sacha/bbdb-omit-displayed-records-by-alias'."
  (interactive (list
                (let ((crm-separator " "))
                  (completing-read-multiple
                   "Mail aliases: "
                   (bbdb-get-mail-aliases)))
                current-prefix-arg))
  (when (stringp query)
    (setq query (split-string query "[ \n\t,]+")))
  (bbdb-display-records
   (sacha/bbdb-filter-by-alias-by-function
    (or bbdb-records (bbdb-records))
    (if any
        'sacha/bbdb-filter-by-alias-match-any
      'sacha/bbdb-filter-by-alias-match-all)
    query)))

;; Splitting this into two functions because of interactive calling.
(defun sacha/bbdb-omit-displayed-records-by-alias (query &optional any)
  "Display only records whose mail-aliases do not match QUERY.
If ANY is non-nil, match if any of the keywords in QUERY are
present.

See also `sacha/bbdb-filter-displayed-records-by-alias'."
  (interactive (list
                (let ((crm-separator " "))
                  (completing-read-multiple
                   "Mail aliases: "
                   (bbdb-get-mail-aliases))
                  current-prefix-arg)))
  (when (stringp query)
    (setq query (split-string query "[ \n\t,]+")))
  (bbdb-display-records
   (sacha/bbdb-filter-by-alias-by-function
    (or bbdb-records (bbdb-records))
    (if any
        'sacha/bbdb-filter-by-alias-match-any
      'sacha/bbdb-filter-by-alias-match-all)
    query
    t)))

;;;_+ Advanced mail alias queries

;; Goal: Use complicated lambda expressions to filter displayed records
;; M-x sacha/bbdb-filter-by-alias-function RET
;;     (lambda (aliases) (and (member "planner" aliases)
;;                       (not (member "muse" aliases))))) RET
;; Thanks to Paul Lussier for the suggestion!

(defun sacha/bbdb-filter-by-alias-function (bbdb-records
                                            alias-filter-function)
  "Return only the BBDB-RECORDS that match ALIAS-FILTER-FUNCTION.
ALIAS-FILTER-FUNCTION should accept one argument:
 - aliases, a list of keywords from the record."
  (interactive (list (or bbdb-records (bbdb-records))
                     (read t)))
  (let (records)
    (setq records
          (delq nil
                (mapcar
                 (lambda (rec)
                   (when (funcall alias-filter-function
                                  (split-string
                                   (or (bbdb-record-getprop
                                        (if (vectorp rec)
                                            rec
                                          (car rec))
                                        propsym) "")
                                   "[ \n\t,]+"))
                     rec))
                 bbdb-records)))
    (if (interactive-p) (bbdb-display-records records))
    records))
</pre>


<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2006%2F09%2Femacs-bbdb-filtering-tags-with-the-power-of-lambda-expressions%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item><item>
		<title>Emacs: BBDB rapid serial visualization</title>
		<link>https://sachachua.com/blog/2006/09/emacs-bbdb-rapid-serial-visualization/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Thu, 28 Sep 2006 15:35:00 GMT</pubDate>
    <category>bbdb</category>
<category>emacs</category>
		<guid isPermaLink="false">https://sachachua.com/blog/?p=3882</guid>
		<description><![CDATA[<p>And because it's good to quickly flash through records once in a while<br>
to refresh my memory&#8230;</p>
<pre class="example">
(defvar sacha/bbdb-rapid-serial-visualization-delay
 1
 "*Number of seconds to wait between records.
Set to 0 to wait for input.")

(defun sacha/bbdb-rapid-serial-visualization ()
  "Breeze through everyone's name and notes."
  (interactive)
  (window-configuration-to-register ?a)
  ;; Copy the currently visible records
  (let ((records bbdb-records)
        (default-size (face-attribute 'default :height))
        (new-size 400)
        (continue t))
    (set-face-attribute 'default nil :height new-size)
    (pop-to-buffer (get-buffer-create "BBDB-Serial"))
    (delete-other-windows)
    (while (and records continue)
      (insert (bbdb-record-name (caar records))
              "\n\n"
              (or (car (bbdb-record-net (caar records))) "No e-mail")
              "\n\n"
              (or (bbdb-record-notes (caar records)) "")
              (make-string 50 ?\n))
      (goto-char (point-min))
      (sit-for sacha/bbdb-rapid-serial-visualization-delay)
      (setq records (cdr records)))
    (set-face-attribute 'default nil :height default-size)
    (when continue
      (jump-to-register ?a))))
</pre>


<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2006%2F09%2Femacs-bbdb-rapid-serial-visualization%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item><item>
		<title>More Emacs goodness: Refresh your memory when you e-mail using notes from BBDB</title>
		<link>https://sachachua.com/blog/2006/09/more-emacs-goodness-refresh-your-memory-when-you-e-mail-using-notes-from-bbdb/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Sat, 02 Sep 2006 19:58:00 GMT</pubDate>
    <category>bbdb</category>
<category>connecting</category>
<category>emacs</category>
		<guid isPermaLink="false">https://sachachua.com/blog/?p=3824</guid>
		<description><![CDATA[<p>Inspired by an e-mail-based customer relationship management system briefly described by <a href="http://blog.digitalketchup.net/">Daniel Charles</a> of <a href="http://www.digitalketchup.net">digital ketchup</a> at Shoeless Joe's last Friday, I decided to hack together a system that would allow me to see the notes from my contact database (aptly named the Big Brother Database, or BBDB) when I write e-mail using the Gnus mail client in Emacs.</p>
<p>The first thing I needed to build, of course, was something that<br>
removed my notes from outgoing messages. People really don't need to<br>
see the kinds of notes I keep on them. ;) Well, they're fairly<br>
innocuous notes: how we met and what they're interested in, usually,<br>
although sometimes I'll have notes on people's food preferences or<br>
shoe sizes. I've recently started keeping track of the subjects of<br>
e-mail I send them, too.</p>
<pre class="example">
(defun sacha/gnus-remove-notes ()
  "Remove everything from &#45;&#45;- NOTES &#45;&#45;- to the signature."
  (goto-char (point-min))
  (when (re-search-forward "^&#45;&#45;- NOTES &#45;&#45;-" nil t)
    (let ((start (match-beginning 0))
          (end (and (re-search-forward "^&#45;&#45;- END NOTES &#45;&#45;-") (match-end 0))))
      (delete-region start end))))
(add-hook 'message-send-hook 'sacha/gnus-remove-notes)
</pre>
<p>Then it was easy to write another function that composed individual<br>
messages to all the people currently displayed in the BBDB buffer,<br>
adding notes to each message.</p>
<pre class="example">
(defun sacha/gnus-send-message-to-all (subject)
  "Compose message to everyone, with notes."
  (interactive "MSubject: ")
  (let ((records bbdb-records))
    (while records
      (when (bbdb-record-net (caar records))
        (bbdb-send-mail (caar records) subject)
        (when (bbdb-record-notes (caar records))
          (save-excursion
            (insert "\n&#45;&#45;- NOTES &#45;&#45;-\n"
                    (bbdb-record-notes (caar records))
                    "\n&#45;&#45;- END NOTES &#45;&#45;-\n"))))
      (setq records (cdr records)))))
</pre>
<p>I use BBDB to display only the people I want to e-mail, then I call<br>
M-x sacha/gnus-send-message-to-all and specify a message subject. This<br>
creates a gazillion message buffers which I can then edit. If I feel<br>
particularly paranoid, I can remove the notes section myself with C-c<br>
C-z (message-kill-to-signature), but sacha/gnus-remove-notes does it<br>
as long as it's in message-send-hook.</p>
<p>This code works particularly well with these other customizations:</p>
<ul>
<li><a href="https://sachachua.com/notebook/wiki/2006.07.20.php#anchor-1">Emacs BBDB magic: Greeting people with nicknames</a></li>
<li><a href="https://sachachua.com/notebook/wiki/2006.09.01.php#anchor-2">Emacs: Keep track of messages sent</a></li>
<li><a href="https://sachachua.com/notebook/wiki/2006.06.03.php#anchor-5">BBDB pinging code</a> &#8211; for adding datestamped notes to people's entries; can be rewritten without the use of Planner</li>
</ul>
<p>It supersedes <a href="https://sachachua.com/notebook/wiki/2006.09.01.php#anchor-4">More Emacs fun: Composing mail to everyone with notes</a>.</p>

<p><a href="https://sachachua.com/blog/emacs/dotgnus.el">../emacs/dotgnus.el</a></p>

<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2006%2F09%2Fmore-emacs-goodness-refresh-your-memory-when-you-e-mail-using-notes-from-bbdb%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item>
	</channel>
</rss>