emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] 02/03: Squashed 'packages/gnorb/' content from commit de3a512


From: Eric Abrahamsen
Subject: [elpa] 02/03: Squashed 'packages/gnorb/' content from commit de3a512
Date: Thu, 09 Oct 2014 01:48:27 +0000

girzel pushed a commit to branch master
in repository elpa.

commit 0b9eb2b647a49ffa3dc4e3e61cb8bd94c7fe3634
Author: Eric Abrahamsen <address@hidden>
Date:   Thu Oct 9 09:47:46 2014 +0800

    Squashed 'packages/gnorb/' content from commit de3a512
    
    git-subtree-dir: packages/gnorb
    git-subtree-split: de3a512fc8b33b6403e1a5b6391de0dac3f80c84
---
 .elpaignore       |    1 +
 .gitignore        |    4 +
 NEWS              |   11 +
 README.org        |   31 +++
 dir               |   18 ++
 gnorb-bbdb.el     |  565 ++++++++++++++++++++++++++++++++++++++++++
 gnorb-gnus.el     |  671 ++++++++++++++++++++++++++++++++++++++++++++++++++
 gnorb-org.el      |  678 +++++++++++++++++++++++++++++++++++++++++++++++++++
 gnorb-registry.el |  194 +++++++++++++++
 gnorb-utils.el    |  311 ++++++++++++++++++++++++
 gnorb.el          |   41 +++
 gnorb.info        |  701 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 gnorb.org         |  507 ++++++++++++++++++++++++++++++++++++++
 gnorb.texi        |  654 +++++++++++++++++++++++++++++++++++++++++++++++++
 nngnorb.el        |  375 ++++++++++++++++++++++++++++
 15 files changed, 4762 insertions(+), 0 deletions(-)

diff --git a/.elpaignore b/.elpaignore
new file mode 100644
index 0000000..f614ef7
--- /dev/null
+++ b/.elpaignore
@@ -0,0 +1 @@
+gnorb.org
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..60fa2ca
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*.elc
+notes.org
+gnorb-pkg.el
+gnorb-autoloads.el
\ No newline at end of file
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..728f219
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,11 @@
+GNU Emacs Gnorb NEWS -- history of user-visible changes.  -*- org -*-
+
+* Version 1 [2014-10-07 Tue]
+** First Elpa Version
+** Email Tracking
+The mechanism for email tracking has changed since Gnorb was made
+available on Elpa. See the manual for set-up instructions.
+** Directory Structure
+The directory structure has changed since Gnorb was made available on
+Elpa. There is no longer a lisp/ directory -- all *.el files are now
+at the top level.
diff --git a/README.org b/README.org
new file mode 100644
index 0000000..cbb13af
--- /dev/null
+++ b/README.org
@@ -0,0 +1,31 @@
+* Gnorb
+
+Glue code between the Gnus, Org, and BBDB packages for Emacs.
+
+This package connects Emacs-based email, project management, and
+contact management a little more closely together. The goal is to
+reduce friction when manipulating TODOs, contacts, messages, and
+files.
+
+Probably the most interesting thing Gnorb does is tracking
+correspondences between Gnus email messages and Org headings. Rather
+than "turning your inbox into a TODO list", as some software puts it,
+Gnorb (kind of) does the opposite: turning your TODO headings into
+mini mailboxes.
+
+*Note for previous users*: If you were using Gnorb from Github before
+it shifted to the Elpa repository, the email tracking mechanism has
+changed, please see the manual for details.
+
+** Installation
+
+It's easiest to install Gnorb from Elpa: run `list-packages' and look
+for it there.
+
+Or clone the Git repo at https://github.com/girzel/gnorb, and add the
+top-level directory to your load path.
+
+If you want to use Gnorb for tracking emails with TODOs, you'll need
+to add a nngnorb server to your `gnus-secondary-select-methods'
+variable, then call `gnorb-tracking-initialize' in your init files.
+Again, see the manual for details.
diff --git a/dir b/dir
new file mode 100644
index 0000000..b0d0ad6
--- /dev/null
+++ b/dir
@@ -0,0 +1,18 @@
+This is the file .../info/dir, which contains the
+topmost node of the Info hierarchy, called (dir)Top.
+The first time you invoke Info you start off looking at this node.
+
+File: dir,     Node: Top       This is the top of the INFO tree
+
+  This (the Directory node) gives a menu of major topics.
+  Typing "q" exits, "?" lists all Info commands, "d" returns here,
+  "h" gives a primer for first-timers,
+  "mEmacs<Return>" visits the Emacs manual, etc.
+
+  In Emacs, you can click mouse button 2 on a menu item or cross reference
+  to select it.
+
+* Menu:
+
+Emacs
+* Gnorb: (gnorb).               Glue code for Gnus, Org, and BBDB.
diff --git a/gnorb-bbdb.el b/gnorb-bbdb.el
new file mode 100644
index 0000000..b30298f
--- /dev/null
+++ b/gnorb-bbdb.el
@@ -0,0 +1,565 @@
+;;; gnorb-bbdb.el --- The BBDB-centric functions of gnorb
+
+;; Copyright (C) 2014  Eric Abrahamsen
+
+;; Author: Eric Abrahamsen <address@hidden>
+;; Keywords: 
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; 
+
+;;; Code:
+
+(require 'gnorb-utils)
+
+(defgroup gnorb-bbdb nil
+  "The BBDB bits of gnorb."
+  :tag "Gnorb BBDB"
+  :group 'gnorb)
+
+(defcustom gnorb-bbdb-org-tag-field 'org-tags
+  "The name (as a symbol) of the field to use for org tags."
+  :group 'gnorb-bbdb
+  :type 'symbol)
+
+(unless (assoc gnorb-bbdb-org-tag-field bbdb-separator-alist)
+  (push `(,gnorb-bbdb-org-tag-field ":" ":") bbdb-separator-alist))
+
+(defcustom gnorb-bbdb-messages-field 'messages
+  "The name (as a symbol) of the field where links to recent gnus
+messages from this record are stored.
+
+\\<bbdb-mode-map>Records that do not have this field defined
+will not collect links to messages: you have to call
+\"\\[gnorb-bbdb-open-link]\" on the record once -- after that,
+message links will be collected and updated automatically."
+  :group 'gnorb-bbdb
+  :type 'symbol)
+
+(defcustom gnorb-bbdb-collect-N-messages 5
+  "For records with a `gnorb-bbdb-messages-field' defined,
+collect links to a maximum of this many messages."
+  :group 'gnorb-bbdb
+  :type 'integer)
+
+(defcustom gnorb-bbdb-define-recent 'seen
+  "For records with a `gnorb-bbdb-message-tag-field' defined,
+this variable controls how gnorb defines a \"recent\" message.
+Setting it to the symbol seen will collect the messages most
+recently opened and viewed. The symbol received means gnorb will
+collect the most recent messages by Date header.
+
+In other words, if this variable is set to 'received, and a
+record's messages field is already full of recently-received
+messages, opening a five-year-old message (for instance) from
+this record will not push a link to the message into the field."
+  :group 'gnorb-bbdb
+  :type '(choice (const :tag "Most recently seen" 'seen)
+                 (const :tag "Most recently received" 'received)))
+
+(defcustom gnorb-bbdb-message-link-format-multi "%:count. %D: %:subject"
+  "How a single message is formatted in the list of recent messages.
+This format string is used in multi-line record display.
+
+Available information for each message includes the subject, the
+date, and the message's count in the list, as an integer. You can
+access subject and count using the %:subject and %:count escapes.
+The message date can be formatted using any of the escapes
+mentioned in the docstring of `format-time-string', which see."
+  :group 'gnorb-bbdb
+  :type 'string)
+
+(defcustom gnorb-bbdb-message-link-format-one "%:count"
+  "How a single message is formatted in the list of recent messages.
+This format string is used in single-line display -- note that by
+default, no user-created xfields are displayed in the 'one-line
+layout found in `bbdb-layout-alist'. If you want this field to
+appear there, put its name in the \"order\" list of the 'one-line
+layout.
+
+Available information for each message includes the subject, the
+date, and the message's count in the list, as an integer. You can
+access subject and count using the %:subject and %:count escapes.
+The message date can be formatted using any of the escapes
+mentioned in the docstring of `format-time-string', which see."
+  :group 'gnorb-bbdb
+  :type 'string)
+
+(defface gnorb-bbdb-link (org-compatible-face 'org-link nil)
+  "Custom face for displaying message links in the *BBDB* buffer.
+  Defaults to org-link."
+  :group 'gnorb-bbdb)
+
+(defstruct gnorb-bbdb-link
+  subject date group id)
+
+(defcustom gnorb-bbdb-posting-styles nil
+  "An alist of styles to use when composing messages to the BBDB
+record(s) under point. This is entirely analogous to
+`gnus-posting-styles', it simply works by examining record fields
+rather than group names.
+
+When composing a message to multiple contacts (using the \"*\"
+prefix), the records will be scanned in order, with the record
+initially under point (if any) set aside for last. That means
+that, in the case of conflicting styles, the record under point
+will override the others.
+
+In order not to be too intrusive, this option has no effect on
+the usual `bbdb-mail' command. Instead, the wrapper command
+`gnorb-bbdb-mail' is provided, which consults this option and
+then hands off to `bbdb-compose-mail'. If you'd always like to
+use `gnorb-bbdb-mail', you can simply bind it to \"m\" in the
+`bbdb-mode-map'.
+
+The value of the option should be a list of sexps, each one
+matching a single field. The first element should match a field
+name: one of the built-in fields like lastname, or an xfield.
+Field names should be given as symbols.
+
+The second element is a regexp used to match against the value of
+the field (non-string field values will be cast to strings, if
+possible). It can also be a cons of two strings, the first of
+which matches the field label, the second the field value.
+
+Alternately, the first element can be the name of a custom
+function that is called with the record as its only argument, and
+returns either t or nil. In this case, the second element of the
+list is disregarded.
+
+All following elements should be field setters for the message to
+be composed, just as in `gnus-posting-styles'.
+
+An example value might look like:"
+  :group 'gnorb-bbdb)
+
+;;;###autoload
+(defun gnorb-bbdb-mail (records &optional subject n verbose)
+  "\\<bbdb-mode-map>Acts just like `bbdb-mail', except runs
+RECORDS through `gnorb-bbdb-posting-styles', allowing
+customization of message styles for certain records. From the
+`bbdb-mail' docstring:
+
+Compose a mail message to RECORDS (optional: using SUBJECT).
+Interactively, use BBDB prefix \\[bbdb-do-all-records], see
+`bbdb-do-all-records'. By default, the first mail addresses of
+RECORDS are used. If prefix N is a number, use Nth mail address
+of RECORDS (starting from 1). If prefix N is C-u (t
+noninteractively) use all mail addresses of RECORDS. If VERBOSE
+is non-nil (as in interactive calls) be verbose."
+  ;; see the function `gnus-configure-posting-styles' for tips on how
+  ;; to actually do this.
+  (interactive (list (bbdb-do-records) nil
+                    (or (consp current-prefix-arg)
+                         current-prefix-arg)
+                    t))
+  (setq records (bbdb-record-list records))
+  (if (not records)
+      (user-error "No records displayed")
+    (let ((head (bbdb-current-record))
+         (to (bbdb-mail-address records n nil verbose))
+         (message-mode-hook (copy-sequence message-mode-hook)))
+      (setq records (remove head records))
+      (when gnorb-bbdb-posting-styles
+       (add-hook 'message-mode-hook
+                 `(lambda ()
+                    (gnorb-bbdb-configure-posting-styles (quote ,records))
+                    (gnorb-bbdb-configure-posting-styles (list ,head)))))
+      (bbdb-compose-mail to subject))))
+
+(defun gnorb-bbdb-configure-posting-styles (recs)
+  ;; My most magnificent work of copy pasta!
+  (dolist (r recs)
+    (let (field val label rec-val element filep
+               element v value results name address)
+      (dolist (style gnorb-bbdb-posting-styles)
+       (setq field (pop style)
+             val (pop style))
+       (when (consp val) ;; (label value)
+         (setq label (pop val)
+               val (pop val)))
+       (unless (fboundp field)
+         ;; what's the record's existing value for this field?
+         (setq rec-val (bbdb-record-field r field)))
+       (when (cond
+              ((eq field 'address)
+               (dolist (a rec-val)
+                 (unless (and label
+                              (not (string-match label (car a))))
+                   (string-match val (bbdb-format-address-default a)))))
+              ((eq field 'phone)
+               (dolist (p rec-val)
+                 (unless (and label
+                              (not (string-match label (car p))))
+                   (string-match val (bbdb-phone-string p)))))
+              ((consp rec-val)
+               (dolist (f rec-val)
+                 (string-match val f)))
+              ((fboundp field)
+               (funcall field r))
+              ((stringp rec-val)
+               (string-match val rec-val)))
+         ;; there are matches, run through the field setters in last
+         ;; element of the sexp
+         (dolist (attribute style)
+           (setq element (pop attribute)
+                 filep nil)
+           (setq value
+                 (cond
+                  ((eq (car attribute) :file)
+                   (setq filep t)
+                   (cadr attribute))
+                  ((eq (car attribute) :value)
+                   (cadr attribute))
+                  (t
+                   (car attribute))))
+           ;; We get the value.
+           (setq v
+                 (cond
+                  ((stringp value)
+                   value)
+                  ((or (symbolp value)
+                       (functionp value))
+                   (cond ((functionp value)
+                          (funcall value))
+                         ((boundp value)
+                          (symbol-value value))))
+                  ((listp value)
+                   (eval value))))
+           ;; Post-processing for the signature posting-style:
+           (and (eq element 'signature) filep
+                message-signature-directory
+                ;; don't actually use the signature directory
+                ;; if message-signature-file contains a path.
+                (not (file-name-directory v))
+                (setq v (nnheader-concat message-signature-directory v)))
+           ;; Get the contents of file elems.
+           (when (and filep v)
+             (setq v (with-temp-buffer
+                       (insert-file-contents v)
+                       (buffer-substring
+                        (point-min)
+                        (progn
+                          (goto-char (point-max))
+                          (if (zerop (skip-chars-backward "\n"))
+                              (point)
+                            (1+ (point))))))))
+           (setq results (delq (assoc element results) results))
+           (push (cons element v) results))))
+      (setq name (assq 'name results)
+           address (assq 'address results))
+      (setq results (delq name (delq address results)))
+      (gnus-make-local-hook 'message-setup-hook)
+      (setq results (sort results (lambda (x y)
+                                   (string-lessp (car x) (car y)))))
+      (dolist (result results)
+       (add-hook 'message-setup-hook
+                 (cond
+                  ((eq 'eval (car result))
+                   'ignore)
+                  ((eq 'body (car result))
+                   `(lambda ()
+                      (save-excursion
+                        (message-goto-body)
+                        (insert ,(cdr result)))))
+                  ((eq 'signature (car result))
+                   (set (make-local-variable 'message-signature) nil)
+                   (set (make-local-variable 'message-signature-file) nil)
+                   (if (not (cdr result))
+                       'ignore
+                     `(lambda ()
+                        (save-excursion
+                          (let ((message-signature ,(cdr result)))
+                            (when message-signature
+                              (message-insert-signature)))))))
+                  (t
+                   (let ((header
+                          (if (symbolp (car result))
+                              (capitalize (symbol-name (car result)))
+                            (car result))))
+                     `(lambda ()
+                        (save-excursion
+                          (message-remove-header ,header)
+                          (let ((value ,(cdr result)))
+                            (when value
+                              (message-goto-eoh)
+                              (insert ,header ": " value)
+                              (unless (bolp)
+                                (insert "\n")))))))))
+                 t 'local))
+      (when (or name address)
+       (add-hook 'message-setup-hook
+                 `(lambda ()
+                    (set (make-local-variable 'user-mail-address)
+                         ,(or (cdr address) user-mail-address))
+                    (let ((user-full-name ,(or (cdr name) (user-full-name)))
+                          (user-mail-address
+                           ,(or (cdr address) user-mail-address)))
+                      (save-excursion
+                        (message-remove-header "From")
+                        (message-goto-eoh)
+                        (insert "From: " (message-make-from) "\n"))))
+                 t 'local)))))
+
+;;;###autoload
+(defun gnorb-bbdb-tag-agenda (records)
+  "Open an Org agenda tags view from the BBDB buffer, using the
+value of the record's org-tags field. This shows only TODOs by
+default; a prefix argument shows all tagged headings; a \"*\"
+prefix operates on all currently visible records. If you want
+both, use \"C-u\" before the \"*\"."
+  (interactive (list (bbdb-do-records)))
+  (require 'org-agenda)
+  (unless (and (eq major-mode 'bbdb-mode)
+              (equal (buffer-name) bbdb-buffer-name))
+    (error "Only works in the BBDB buffer"))
+  (setq records (bbdb-record-list records))
+  (let ((tag-string
+        (mapconcat
+         'identity
+         (delete-dups
+          (mapcan (lambda (r)
+                    (bbdb-record-xfield-split r gnorb-bbdb-org-tag-field))
+                  records))
+         "|")))
+    (if tag-string
+       ;; C-u = all headings, not just todos
+       (if (equal current-prefix-arg '(4))
+           (org-tags-view nil tag-string)
+         (org-tags-view t tag-string))
+      (error "No org-tags field present"))))
+
+;;;###autoload
+(defun gnorb-bbdb-mail-search (records)
+  "Initiate a mail search from the BBDB buffer.
+
+Use the prefix arg to edit the search string first, and the \"*\"
+prefix to search mails from all visible contacts. When using both
+a prefix arg and \"*\", the prefix arg must come first."
+  (interactive (list (bbdb-do-records)))
+  (unless (and (eq major-mode 'bbdb-mode)
+              (equal (buffer-name) bbdb-buffer-name))
+    (error "Only works in the BBDB buffer"))
+  (setq records (bbdb-record-list records))
+  (require 'gnorb-gnus)
+  (let* ((backend (or (assoc gnorb-gnus-mail-search-backend
+                            gnorb-gnus-mail-search-backends)
+                     (error "No search backend specified")))
+        (search-string
+         (funcall (second backend)
+                  (cl-mapcan 'bbdb-record-mail records))))
+    (when (equal current-prefix-arg '(4))
+      (setq search-string
+           (read-from-minibuffer
+            (format "%s search string: " (first backend)) search-string)))
+    (funcall (third backend) search-string)
+    (delete-other-windows)))  
+
+;;;###autoload
+(defun gnorb-bbdb-cite-contact (rec)
+  (interactive (list (gnorb-prompt-for-bbdb-record)))
+  (let ((mail-string (bbdb-dwim-mail rec)))
+   (if (called-interactively-p 'any)
+       (insert mail-string)
+     mail-string)))
+
+;;; Field containing links to recent messages
+
+(add-to-list 'bbdb-xfield-label-list gnorb-bbdb-messages-field nil 'eq)
+
+(defun gnorb-bbdb-display-messages (record format)
+  "Show links to the messages collected in the
+`gnorb-bbdb-messages-field' field of a BBDB record. Each link
+will be formatted using the format string in
+`gnorb-bbdb-message-link-format-multi' or
+`gnorb-bbdb-message-link-format-one', depending on the current
+layout type."
+  (let ((full-field (assq gnorb-bbdb-messages-field
+                         (bbdb-record-xfields record)))
+       (val (bbdb-record-xfield record gnorb-bbdb-messages-field))
+       (map (make-sparse-keymap))
+       (count 1)) ; one-indexed to fit with prefix arg to 
`gnorb-bbdb-open-link'
+    (define-key map [mouse-1] 'gnorb-bbdb-mouse-open-link)
+    (define-key map (kbd "<RET>") 'gnorb-bbdb-RET-open-link)
+    (when val
+      ;; indent and fmt are dynamically bound
+      (when (eq format 'multi)
+       (bbdb-display-text (format fmt gnorb-bbdb-messages-field)
+                          `(xfields ,full-field field-label)
+                          'bbdb-field-name))
+      (insert (cond ((and (stringp val)
+                         (eq format 'multi))
+                    (bbdb-indent-string (concat val "\n") indent))
+                   ((listp val)
+                    (concat
+                     (bbdb-indent-string
+                      (mapconcat
+                       (lambda (m)
+                         (prog1
+                             (org-propertize
+                              (concat
+                               (format-time-string
+                                (replace-regexp-in-string
+                                 "%:subject" (gnorb-bbdb-link-subject m)
+                                 (replace-regexp-in-string
+                                  "%:count" (number-to-string count)
+                                  (if (eq format 'multi)
+                                      gnorb-bbdb-message-link-format-multi
+                                    gnorb-bbdb-message-link-format-one)))
+                                (gnorb-bbdb-link-date m)))
+                              'face 'gnorb-bbdb-link
+                              'mouse-face 'highlight
+                              'gnorb-bbdb-link-count count
+                              'keymap map)
+                           (incf count)))
+                       val (if (eq format 'multi)
+                               "\n" ", "))
+                      indent)
+                     (if (eq format 'multi) "\n" "")))
+                   (t
+                    ""))))))
+
+(fset (intern (format "bbdb-display-%s-multi-line"
+                     gnorb-bbdb-messages-field))
+      (lambda (record)
+       (gnorb-bbdb-display-messages record 'multi)))
+
+(fset (intern (format "bbdb-display-%s-one-line"
+                     gnorb-bbdb-messages-field))
+      (lambda (record)
+       (gnorb-bbdb-display-messages record 'one)))
+
+;; Don't allow direct editing of this field
+
+(fset (intern (format "bbdb-read-xfield-%s"
+                     gnorb-bbdb-messages-field))
+      (lambda (&optional init)
+       (user-error "This field shouldn't be edited manually")))
+
+;; Open links from the *BBDB* buffer.
+
+;;;###autoload
+(defun gnorb-bbdb-open-link (record arg)
+  "\\<bbdb-mode-map>Call this on a BBDB record to open one of the
+links in the message field. By default, the first link will be
+opened. Use a prefix arg to open different links. For instance,
+M-3 \\[gnorb-bbdb-open-link] will open the third link in the
+list. If the %:count escape is present in the message formatting
+string (see `gnorb-bbdb-message-link-format-multi' and
+`gnorb-bbdb-message-link-format-one'), that's the number to use.
+
+This function also needs to be called on a contact once before
+that contact will start collecting links to messages."
+  (interactive (list
+               (or (bbdb-current-record)
+                   (user-error "No record under point"))
+               current-prefix-arg))
+  (unless (fboundp 'bbdb-record-xfield-string)
+    (user-error "This function only works with the git version of BBDB"))
+  (let* ((record (bbdb-current-record))
+        msg-list target-msg)
+    (if (not (memq gnorb-bbdb-messages-field
+                  (mapcar 'car (bbdb-record-xfields record))))
+       (when (y-or-n-p
+              (format "Start collecting message links for %s?"
+                      (bbdb-record-name record)))
+         (bbdb-record-set-xfield record gnorb-bbdb-messages-field "no links 
yet")
+         (message "Opening messages from %s will add links to the %s field"
+                  (bbdb-record-name record)
+                  gnorb-bbdb-messages-field)
+         (bbdb-change-record record))
+       (setq msg-list
+             (bbdb-record-xfield record gnorb-bbdb-messages-field))
+       (setq target-msg
+             (or (and arg
+                      (nth (1- arg) msg-list))
+                 (car msg-list)))
+       (when target-msg
+         (org-gnus-follow-link (gnorb-bbdb-link-group target-msg)
+                               (gnorb-bbdb-link-id target-msg))))))
+
+(defun gnorb-bbdb-mouse-open-link (event)
+  (interactive "e")
+  (mouse-set-point event)
+  (let ((rec (bbdb-current-record))
+       (num (get-text-property (point) 'gnorb-bbdb-link-count)))
+    (if (not num)
+       (user-error "No link under point")
+      (gnorb-bbdb-open-link rec num))))
+
+(defun gnorb-bbdb-RET-open-link ()
+  (interactive)
+  (let ((rec (bbdb-current-record))
+       (num (get-text-property (point) 'gnorb-bbdb-link-count)))
+    (if (not num)
+       (user-error "No link under point")
+      (gnorb-bbdb-open-link rec num))))
+
+(defun gnorb-bbdb-store-message-link (record)
+  "Used in the `bbdb-notice-record-hook' to possibly save a link
+to a message into the record's `gnorb-bbdb-messages-field'."
+
+  (when (not (fboundp 'bbdb-record-xfield-string))
+    (user-error "This function only works with the git version of BBDB"))
+  (unless (or (not (and (memq gnorb-bbdb-messages-field
+                             (mapcar 'car (bbdb-record-xfields record)))
+                       (memq major-mode '(gnus-summary-mode 
gnus-article-mode))))
+             (with-current-buffer gnus-article-buffer
+               (not ; only store messages if the record is the sender
+                (member (nth 1 (car (bbdb-get-address-components 'sender)))
+                        (bbdb-record-mail record)))))
+    (with-current-buffer gnus-summary-buffer
+      (let* ((val (bbdb-record-xfield record gnorb-bbdb-messages-field))
+            (art-no (gnus-summary-article-number))
+            (heads (gnus-summary-article-header art-no))
+            (date (apply 'encode-time
+                         (parse-time-string (mail-header-date heads))))
+            (subject (mail-header-subject heads))
+            (id (mail-header-id heads))
+            (group gnus-newsgroup-name)
+            link)
+       ;; check for both nnvirtual and nnir, and link to the real
+       ;; group in those cases
+       (when (eq (car (gnus-find-method-for-group group))
+                 'nnvirtual)
+         (setq group (car (nnvirtual-map-article art-no))))
+       (when (eq (car (gnus-find-method-for-group group))
+                 'nnir)
+         (setq group (nnir-article-group art-no)))
+       (if (not (and date subject id group))
+           (message "Could not save a link to this message")
+         (setq link (make-gnorb-bbdb-link :subject subject :date date
+                                          :group group :id id))
+         (when (stringp val)
+           (setq val nil))
+         (setq val (cons link (delete link val)))
+         (when (eq gnorb-bbdb-define-recent 'received)
+           (setq val (sort val
+                           (lambda (a b)
+                             (time-less-p
+                              (gnorb-bbdb-link-date b)
+                              (gnorb-bbdb-link-date a))))))
+         (setq val (subseq val 0 gnorb-bbdb-collect-N-messages))
+         (bbdb-record-set-xfield record
+                                 gnorb-bbdb-messages-field
+                                 (delq nil val))
+         (bbdb-change-record record))))))
+
+(add-hook 'bbdb-notice-record-hook 'gnorb-bbdb-store-message-link)
+
+(provide 'gnorb-bbdb)
+;;; gnorb-bbdb.el ends here
diff --git a/gnorb-gnus.el b/gnorb-gnus.el
new file mode 100644
index 0000000..ba72107
--- /dev/null
+++ b/gnorb-gnus.el
@@ -0,0 +1,671 @@
+;;; gnorb-gnus.el --- The gnus-centric fuctions of gnorb
+
+;; Copyright (C) 2014  Eric Abrahamsen
+
+;; Author: Eric Abrahamsen <address@hidden>
+;; Keywords: 
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; 
+
+;;; Code:
+
+(require 'gnorb-utils)
+
+(declare-function org-gnus-article-link "org-gnus"
+                 (group newsgroups message-id x-no-archive))
+(declare-function org-gnus-follow-link "org-gnus"
+                 (group article))
+
+(defgroup gnorb-gnus nil
+  "The Gnus bits of Gnorb."
+  :tag "Gnorb Gnus"
+  :group 'gnorb)
+
+
+(defcustom gnorb-gnus-mail-search-backends
+  '((notmuch (lambda (terms)
+              (mapconcat
+               (lambda (m)
+                 (replace-regexp-in-string "\\." "\\\\." m))
+               terms " OR "))
+            notmuch-search)
+    (mairix (lambda (terms)
+             (mapconcat 'identity
+                        terms ","))
+           mairix-search)
+    (namazu (lambda (terms)
+             (mapconcat 'identity
+                        terms " or "))
+           namazu-search))
+  "Various backends for mail search.
+
+An alist of backends, where each element consists of three parts:
+the symbol name of the backend, a lambda form which receives a
+list of email addresses and returns a properly-formatted search
+string, and the symbol name of the function used to initiate the
+search."
+  :group 'gnorb-gnus
+  :type 'list)
+
+(defcustom gnorb-gnus-mail-search-backend nil
+  "Mail search backend currently in use. One of the three symbols
+notmuch, namazu, or mairix."
+  :group 'gnorb-gnus
+  :type 'symbol)
+
+(defcustom gnorb-gnus-capture-always-attach nil
+  "Always prompt about attaching attachments when capturing from
+  a Gnus message, even if the template being used hasn't
+  specified the :gnus-attachments key.
+
+Basically behave as if all attachments have \":gnus-attachments t\"."
+  :group 'gnorb-gnus
+  :type 'boolean)
+
+(defcustom gnorb-gnus-new-todo-capture-key nil
+  "Key for the capture template to use when creating a new TODO
+  from an outgoing message."
+  :group 'gnorb-gnus
+  :type 'string)
+
+(defcustom gnorb-gnus-hint-relevant-article t
+  "When opening a gnus message, should gnorb let you know if the
+  message is relevant to an existing TODO?"
+  :group 'gnorb-gnus
+  :type 'boolean)
+
+(defcustom gnorb-gnus-summary-mark-format-letter "g"
+  "Format letter to be used as part of your
+  `gnus-summary-line-format', to indicate in the *Summary* buffer
+  which articles might be relevant to TODOs. Since this is a user
+  format code, it should be prefixed with %u, eg %ug. It will
+  result in the insertion of the value of
+  `gnorb-gnus-summary-mark', for relevant messages, or
+  else a space."
+  :group 'gnorb-gnus
+  :type 'string)
+
+(defcustom gnorb-gnus-summary-mark "¡"
+  "Default mark to insert in the summary format line of articles
+  that are likely relevant to existing TODO headings."
+  :group 'gnorb-gnus
+  :type 'string)
+
+(defcustom gnorb-gnus-trigger-refile-targets
+  '((org-agenda-files :maxlevel . 4))
+  "A value to use as an equivalent of `org-refile-targets' (which
+  see) when offering trigger targets for
+  `gnorb-gnus-incoming-do-todo'."
+  :group 'gnorb-gnus
+  :type 'list)
+
+(defcustom gnorb-gnus-sent-groups nil
+  "A list of strings indicating sent mail groups.
+
+In some cases, Gnorb can't detect where your sent messages are
+stored (ie if you're using IMAP sent mail folders instead of
+local archiving. If you want Gnorb to be able to find sent
+messages, this option can help it do that. It should be set to a
+list of strings, which are assumed to be fully qualified
+server+group combinations, ie \"nnimap+Server:[Gmail]/Sent
+Mail\", or something similar. This only has to be done once for
+each message."
+  :group 'gnorb-gnus
+  :type 'list)
+
+(defvar gnorb-gnus-capture-attachments nil
+  "Holding place for attachment names during the capture
+  process.")
+
+;;; What follows is a very careful copy-pasta of bits and pieces from
+;;; mm-decode.el and gnus-art.el. Voodoo was involved.
+
+;;;###autoload
+(defun gnorb-gnus-article-org-attach (n)
+  "Save MIME part N, which is the numerical prefix, of the
+  article under point as an attachment to the specified org
+  heading."
+  (interactive "P")
+  (gnus-article-part-wrapper n 'gnorb-gnus-attach-part))
+
+;;;###autoload
+(defun gnorb-gnus-mime-org-attach ()
+  "Save the MIME part under point as an attachment to the
+  specified org heading."
+  (interactive)
+  (gnus-article-check-buffer)
+  (let ((data (get-text-property (point) 'gnus-data)))
+    (when data
+      (gnorb-gnus-attach-part data))))
+
+(defun gnorb-gnus-attach-part (handle &optional org-heading)
+  "Attach HANDLE to an existing org heading."
+  (let* ((filename (gnorb-gnus-save-part handle))
+        (org-refile-targets gnorb-gnus-trigger-refile-targets)
+        (ref-msg-ids
+         (concat (gnus-fetch-original-field "references") " "
+                 (gnus-fetch-original-field "in-reply-to")))
+        (rel-heading
+         (when gnorb-tracking-enabled
+           (car (gnorb-find-visit-candidates
+                 ref-msg-ids))))
+        (org-heading
+         (if (and rel-heading
+                  (y-or-n-p (message
+                             "Attach part to %s"
+                             (gnorb-pretty-outline rel-heading))))
+             rel-heading
+           (org-refile-get-location "Attach part to" nil t))))
+    (require 'org-attach)
+    (save-window-excursion
+      (if (stringp org-heading)
+         (org-id-goto org-heading)
+       (progn
+         (find-file (nth 1 org-heading))
+         (goto-char (nth 3 org-heading))))
+      (org-attach-attach filename nil 'mv))))
+
+(defun gnorb-gnus-save-part (handle)
+  (let ((filename (or (mail-content-type-get
+                      (mm-handle-disposition handle) 'filename)
+                     (mail-content-type-get
+                      (mm-handle-type handle) 'name))))
+    (setq filename
+         (gnus-map-function mm-file-name-rewrite-functions
+                            (file-name-nondirectory filename)))
+    (setq filename (expand-file-name filename gnorb-tmp-dir))
+    (mm-save-part-to-file handle filename)
+    filename))
+
+(defun gnorb-gnus-collect-all-attachments (&optional capture-p store)
+  "Collect all the attachments from the message under point, and
+save them into `gnorb-tmp-dir'."
+  (save-window-excursion
+    (when capture-p
+      (set-buffer (org-capture-get :original-buffer)))
+    (unless (memq major-mode '(gnus-summary-mode gnus-article-mode))
+      (error "Only works in Gnus summary or article buffers"))
+    (let ((article (gnus-summary-article-number)) 
+         mime-handles)
+      (when (or (null gnus-current-article)
+               (null gnus-article-current)
+               (/= article (cdr gnus-article-current))
+               (not (equal (car gnus-article-current) gnus-newsgroup-name)))
+       (gnus-summary-display-article article))
+      (gnus-eval-in-buffer-window gnus-article-buffer
+       (setq mime-handles (cl-remove-if-not
+                           (lambda (h)
+                             (let ((disp (mm-handle-disposition (cdr h))))
+                               (and (member (car disp)
+                                            '("inline" "attachment"))
+                                    (mail-content-type-get disp 'filename))))
+                           gnus-article-mime-handle-alist)))
+      (when mime-handles
+       (dolist (h mime-handles)
+         (let ((filename
+                (gnorb-gnus-save-part (cdr h))))
+           (when (or capture-p store)
+             (push filename gnorb-gnus-capture-attachments))))))))
+
+;;; Make the above work in the capture process
+
+(defun gnorb-gnus-capture-attach ()
+  (when (and (or gnorb-gnus-capture-always-attach
+                (org-capture-get :gnus-attachments))
+            (with-current-buffer
+                (org-capture-get :original-buffer)
+              (memq major-mode '(gnus-summary-mode gnus-article-mode))))
+    (require 'org-attach)
+    (setq gnorb-gnus-capture-attachments nil)
+    (gnorb-gnus-collect-all-attachments t)
+    (map-y-or-n-p
+     (lambda (a)
+       (format "Attach %s to capture heading? "
+              (file-name-nondirectory a)))
+     (lambda (a) (org-attach-attach a nil 'mv))
+     gnorb-gnus-capture-attachments
+     '("file" "files" "attach"))
+    (setq gnorb-gnus-capture-attachments nil)))
+
+(add-hook 'org-capture-mode-hook 'gnorb-gnus-capture-attach)
+
+(defun gnorb-gnus-capture-abort-cleanup ()
+  (when (and org-note-abort
+            (org-capture-get :gnus-attachments))
+    (condition-case error
+       (progn (org-attach-delete-all)
+              (setq abort-note 'clean)
+              ;; remove any gnorb-mail-header values here
+              )
+      (error
+       (setq abort-note 'dirty)))))
+
+(add-hook 'org-capture-prepare-finalize-hook
+         'gnorb-gnus-capture-abort-cleanup)
+
+;;; Storing, removing, and acting on Org headers in messages.
+
+(defvar gnorb-gnus-message-info nil
+  "Place to store the To, Subject, Date, and Message-ID headers
+  of the currently-sending or last-sent message.")
+
+(defun gnorb-gnus-check-outgoing-headers ()
+  "Save the value of the `gnorb-mail-header' for the current
+message; multiple header values returned as a string. Also save
+information about the outgoing message into
+`gnorb-gnus-message-info'."
+  (save-restriction
+    (message-narrow-to-headers)
+    (setq gnorb-gnus-message-info nil)
+    (let* ((org-ids (mail-fetch-field gnorb-mail-header nil nil t))
+          (msg-id (mail-fetch-field "Message-ID"))
+          (refs (mail-fetch-field "References"))
+          (in-reply-to (mail-fetch-field "In-Reply-To"))
+          (to (if (message-news-p)
+                  (mail-fetch-field "Newsgroups")
+                (mail-fetch-field "To")))
+          (from (mail-fetch-field "From"))
+          (subject (mail-fetch-field "Subject"))
+          (date (mail-fetch-field "Date"))
+          ;; If we can get a link, that's awesome.
+          (gcc (mail-fetch-field "Gcc"))
+          (link (or (and gcc
+                         (org-store-link nil))
+                    nil))
+          (group (ignore-errors (car (split-string link "#")))))
+      ;; If we can't make a real link, then save some information so
+      ;; we can fake it.
+      (when in-reply-to
+       (setq refs (concat refs " " in-reply-to)))
+      (when refs
+       (setq refs (gnus-extract-references refs)))
+      (setq gnorb-gnus-message-info
+           `(:subject ,subject :msg-id ,msg-id
+                      :to ,to :from ,from
+                      :link ,link :date ,date :refs ,refs
+                      :group ,group))
+      (if org-ids
+         (progn
+           (require 'gnorb-org)
+           (setq gnorb-message-org-ids org-ids)
+           ;; `gnorb-org-setup-message' may have put this here, but
+           ;; if we're working from a draft, or triggering this from
+           ;; a reply, it might not be there yet.
+           (add-to-list 'message-exit-actions
+                        'gnorb-org-restore-after-send t))
+       (setq gnorb-message-org-ids nil)))))
+
+(add-hook 'message-header-hook 'gnorb-gnus-check-outgoing-headers)
+
+;;;###autoload
+(defun gnorb-gnus-outgoing-do-todo (&optional arg)
+  "Call this function to use the message currently being composed
+as an email todo action. If it's a new message, or a reply to a
+message that isn't referenced by any TODOs, a new TODO will be
+created. If it references an existing TODO, you'll be prompted to
+trigger a state-change or a note on that TODO.
+
+Otherwise, you can call it with a prefix arg to associate the
+sending/sent message with an existing Org subtree, and trigger an
+action on that subtree.
+
+If a new todo is made, it needs a capture template: set
+`gnorb-gnus-new-todo-capture-key' to the string key for the
+appropriate capture template. If you're using a gnus-based
+archive method (ie you have `gnus-message-archive-group' set to
+something, and your outgoing messages have a \"Fcc\" header),
+then a real link will be made to the outgoing message, and all
+the gnus-type escapes will be available (see the Info
+manual (org) Template expansion section). If you don't, then the
+%:subject, %:to, %:toname, %:toaddress, and %:date escapes for
+the outgoing message will still be available -- nothing else will
+work."
+  (interactive "P")
+  (let ((org-refile-targets gnorb-gnus-trigger-refile-targets)
+       (compose-marker (make-marker))
+       header-ids ref-ids rel-headings gnorb-window-conf
+       reply-id reply-group in-reply-to)
+    (when arg
+      (setq rel-headings
+           (org-refile-get-location "Trigger action on" nil t))
+      (setq rel-headings
+           (list (list (save-window-excursion
+                         (find-file (nth 1 rel-headings))
+                         (goto-char (nth 3 rel-headings))
+                         (org-id-get-create))))))
+    (if (not (eq major-mode 'message-mode))
+       ;; The message is already sent, so we're relying on whatever was
+       ;; stored into `gnorb-gnus-message-info'.
+       (if arg
+           (progn
+             (push (car rel-headings) gnorb-message-org-ids)
+             (gnorb-org-restore-after-send))
+         (setq ref-ids (plist-get gnorb-gnus-message-info :refs))
+         (if ref-ids
+             ;; the message might be relevant to some TODO
+             ;; heading(s). But if there had been org-id
+             ;; headers, they would already have been
+             ;; handled when the message was sent.
+             (progn
+               (setq rel-headings (gnorb-find-visit-candidates ref-ids))
+               (if (not rel-headings)
+                   (gnorb-gnus-outgoing-make-todo-1)
+                 (dolist (h rel-headings)
+                   (push h gnorb-message-org-ids))
+                 (gnorb-org-restore-after-send)))
+           ;; not relevant, just make a new TODO
+           (gnorb-gnus-outgoing-make-todo-1)))
+      ;; We are still in the message composition buffer, so let's see
+      ;; what we've got.
+
+      ;; What we want is a link to the original message we're replying
+      ;; to, if this is actually a reply.
+      (when message-reply-headers
+       (setq reply-id (aref message-reply-headers 4)))
+      ;; Save-excursion won't work, because point will move if we
+      ;; insert headings.
+      (move-marker compose-marker (point))
+      (save-restriction
+       (widen)
+       (message-narrow-to-headers-or-head)
+       (setq header-ids (mail-fetch-field gnorb-mail-header nil nil t))
+       ;; With a prefix arg we do not check references, because the
+       ;; whole point is to add new references. We still want to know
+       ;; what org id headers are present, though, so we don't add
+       ;; duplicates.
+       (setq ref-ids (unless arg (mail-fetch-field "References" t)))
+       (setq in-reply-to (unless arg (mail-fetch-field "In-Reply-to" t)))
+       (when in-reply-to
+         (setq ref-ids (concat ref-ids " " in-reply-to)))
+       (setq reply-group (when (mail-fetch-field "X-Draft-From" t)
+                           (car-safe (read (mail-fetch-field "X-Draft-From" 
t)))))
+       ;; when it's a reply, store a link to the reply just in case.
+       ;; This is pretty embarrassing -- we follow a link just to
+       ;; create a link. But I'm not going to recreate all of
+       ;; `org-store-link' by hand.
+       (when (and reply-group reply-id)
+         (save-window-excursion
+           (org-gnus-follow-link reply-group reply-id)
+           (call-interactively 'org-store-link)))
+       (when ref-ids
+         ;; if the References header points to any message ids that are
+         ;; tracked by TODO headings...
+         (setq rel-headings (gnorb-find-visit-candidates ref-ids)))
+       (when rel-headings
+         (goto-char (point-min))
+         (dolist (h (delete-dups rel-headings))
+           ;; then get the org-ids of those headings, and insert
+           ;; them into this message as headers. If the id was
+           ;; already present in a header, don't add it again.
+           (unless (member h header-ids)
+             (goto-char (point-at-bol))
+             (open-line 1)
+             (message-insert-header
+              (intern gnorb-mail-header)
+              h)
+             ;; tell the rest of the function that this is a relevant
+             ;; message
+             (push h header-ids)))))
+      (goto-char compose-marker)
+      (add-to-list
+       'message-exit-actions
+       (if header-ids
+          'gnorb-org-restore-after-send
+        'gnorb-gnus-outgoing-make-todo-1)
+       t)
+      (message
+       (if header-ids
+          "Message will trigger TODO state-changes after sending"
+        "A TODO will be made from this message after it's sent")))))
+
+(defun gnorb-gnus-outgoing-make-todo-1 ()
+  (unless gnorb-gnus-new-todo-capture-key
+    (error "No capture template key set, customize 
gnorb-gnus-new-todo-capture-key"))
+  (let* ((link (plist-get gnorb-gnus-message-info :link))
+        (group (plist-get gnorb-gnus-message-info :group))
+        (date (plist-get gnorb-gnus-message-info :date))
+        (date-ts (and date
+                      (ignore-errors
+                        (format-time-string
+                         (org-time-stamp-format t)
+                         (date-to-time date)))))
+        (date-ts-ia (and date
+                         (ignore-errors
+                           (format-time-string
+                            (org-time-stamp-format t t)
+                            (date-to-time date)))))
+        (msg-id (plist-get gnorb-gnus-message-info :msg-id))
+        (sender (plist-get gnorb-gnus-message-info :from))
+        (subject (plist-get gnorb-gnus-message-info :subject))
+        ;; Convince Org we already have a link stored, even if we
+        ;; don't.
+        (org-capture-link-is-already-stored t))
+    (if link
+       ;; Even if you make a link to not-yet-sent messages, even if
+       ;; you've saved the draft and it has a Date header, that
+       ;; header isn't saved into the link plist. So fake that, too.
+       (org-add-link-props
+        :date date
+        :date-timestamp date-ts
+        :date-timestamp-inactive date-ts-ia
+        :annotation link)
+      (org-store-link-props
+       :subject (plist-get gnorb-gnus-message-info :subject)
+       :to (plist-get gnorb-gnus-message-info :to)
+       :date date
+       :date-timestamp date-ts
+       :date-timestamp-inactive date-ts-ia
+       :message-id msg-id
+       :annotation link))
+    (org-capture nil gnorb-gnus-new-todo-capture-key)
+    (when msg-id
+      (org-entry-put (point) gnorb-org-msg-id-key msg-id)
+      (gnorb-registry-make-entry msg-id sender subject (org-id-get-create) 
group))))
+
+;;; If an incoming message should trigger state-change for a Org todo,
+;;; call this function on it.
+
+;;;###autoload
+(defun gnorb-gnus-incoming-do-todo (arg headers &optional id)
+  "Call this function from a received gnus message to store a
+link to the message, prompt for a related Org heading, visit the
+heading, and either add a note or trigger a TODO state change.
+Set `gnorb-trigger-todo-default' to 'note or 'todo (you can
+get the non-default behavior by calling this function with a
+prefix argument), or to 'prompt to always be prompted.
+
+In some cases, Gnorb can guess for you which Org heading you
+probably want to trigger, which can save some time. It does this
+by looking in the References header, and seeing if any of the IDs
+there match the value of the `gnorb-org-msg-id-key' property for
+any headings. In order for this to work, you will have to have
+loaded org-id, and have the variable `org-id-track-globally' set
+to t (it is, by default)."
+  (interactive (gnus-interactive "P\nH"))
+  (when (not (memq major-mode '(gnus-summary-mode gnus-article-mode)))
+    (user-error "Only works in gnus summary or article mode"))
+  ;; We should only store a link if it's not already at the head of
+  ;; `org-stored-links'. There's some duplicate storage, at
+  ;; present. Take a look at calling it non-interactively.
+  (setq gnorb-window-conf (current-window-configuration))
+  (move-marker gnorb-return-marker (point))
+  (setq gnorb-gnus-message-info nil)
+  (let* ((msg-id (mail-header-id headers))
+        (from (mail-header-from headers))
+        (subject (mail-header-subject headers))
+        (date (mail-header-date headers))
+        (to (cdr (assoc 'To (mail-header-extra headers))))
+        (group gnus-newsgroup-name)
+        (link (call-interactively 'org-store-link))
+        (org-refile-targets gnorb-gnus-trigger-refile-targets)
+        (ref-msg-ids (mail-header-references headers))
+        (offer-heading
+         (when (and (not id) ref-msg-ids gnorb-tracking-enabled)
+           (if org-id-track-globally
+               ;; for now we're basically ignoring the fact that
+               ;; multiple candidates could exist; just do the first
+               ;; one.
+               (car (gnorb-find-visit-candidates
+                     ref-msg-ids))
+             (message "Gnorb can't check for relevant headings unless 
`org-id-track-globally' is t")
+             (sit-for 1))))
+        targ)
+    (setq gnorb-gnus-message-info
+           `(:subject ,subject :msg-id ,msg-id
+                      :to ,to :from ,from
+                      :link ,link :date ,date :refs ,ref-msg-ids
+                      :group ,group))
+    (gnorb-gnus-collect-all-attachments nil t)
+    ;; Delete other windows, users can restore with
+    ;; `gnorb-restore-layout'.
+    (delete-other-windows)
+    (if id
+       (gnorb-trigger-todo-action arg id)
+      (if (and offer-heading
+              (y-or-n-p (format "Trigger action on %s"
+                                (gnorb-pretty-outline offer-heading))))
+         (gnorb-trigger-todo-action arg offer-heading)
+       (setq targ (org-refile-get-location
+                   "Trigger heading" nil t))
+       (find-file (nth 1 targ))
+       (goto-char (nth 3 targ))
+       (gnorb-trigger-todo-action arg)))))
+
+;;;###autoload
+(defun gnorb-gnus-search-messages (str &optional ret)
+  "Initiate a search for gnus message links in an org subtree.
+The arg STR can be one of two things: an Org heading id value
+\(IDs should be prefixed with \"id+\"\), in which case links will
+be collected from that heading, or a string corresponding to an
+Org tags search, in which case links will be collected from all
+matching headings.
+
+In either case, once a collection of links have been made, they
+will all be displayed in an ephemeral group on the \"nngnorb\"
+server. There must be an active \"nngnorb\" server for this to
+work."
+  (interactive)
+  (let ((nnir-address
+        (or (gnus-method-to-server '(nngnorb))
+            (user-error
+             "Please add a \"nngnorb\" backend to your gnus installation."))))
+    (when (version= "5.13" gnus-version-number)
+      (setq nnir-current-query nil
+           nnir-current-server nil
+           nnir-current-group-marked nil
+           nnir-artlist nil))
+    (gnus-group-read-ephemeral-group
+     ;; in 24.4, the group name is mostly decorative. in 24.3, the
+     ;; query itself is read from there. It should look like (concat
+     ;; "nnir:" (prin1-to-string '((query str))))
+     (if (version= "5.13" gnus-version-number)
+        (concat "nnir:" (prin1-to-string `((query ,str))))
+       (concat "gnorb-" str))
+     (if (version= "5.13" gnus-version-number)
+        (list 'nnir nnir-address)
+       (list 'nnir "nnir"))
+     nil
+     ret ;; it's possible you can't just put an arbitrary form in
+        ;; here, which sucks.
+     nil nil
+     ;; the following seems to simply be ignored under gnus 5.13
+     (list (cons 'nnir-specs (list (cons 'nnir-query-spec `((query . ,str)))
+                                  (cons 'nnir-group-spec `((,nnir-address 
nil)))))
+          (cons 'nnir-artlist nil)))
+    (gnorb-summary-minor-mode)))
+
+;;; Automatic noticing of relevant messages
+
+;; likely hooks for the summary buffer include:
+;; `gnus-parse-headers-hook'
+
+;; BBDB puts its notice stuff in the `gnus-article-prepare-hook',
+;; which seems as good a spot as any.
+
+(defun gnorb-gnus-hint-relevant-message ()
+  "When opening an article buffer, check the message to see if it
+is relevant to any existing TODO headings. If so, flash a message
+to that effect. This function is added to the
+`gnus-article-prepare-hook'. It will only do anything if the
+option `gnorb-gnus-hint-relevant-article' is non-nil."
+  (when (and gnorb-tracking-enabled
+            gnorb-gnus-hint-relevant-article
+            (not (memq (car (gnus-find-method-for-group
+                             gnus-newsgroup-name))
+                       '(nnvirtual nnir))))
+    (let* ((ref-ids (concat
+                    (gnus-fetch-original-field "references") " "
+                    (gnus-fetch-original-field "in-reply-to")))
+          (msg-id (gnus-fetch-original-field "message-id"))
+          (assoc-heading
+           (gnus-registry-get-id-key msg-id 'gnorb-ids))
+          (key
+           (where-is-internal 'gnorb-gnus-incoming-do-todo
+                              nil t))
+          rel-headings)
+      (cond (assoc-heading
+            (message "Message is associated with %s"
+                     (gnorb-pretty-outline (car assoc-heading) t)))
+           (ref-ids
+            (when (setq rel-headings
+                        (gnorb-find-visit-candidates ref-ids))
+              (message "Possible relevant todo %s, trigger with %s"
+                       (gnorb-pretty-outline (car rel-headings) t)
+                       (if key
+                           (key-description key)
+                         "M-x gnorb-gnus-incoming-do-todo"))))))))
+
+(add-hook 'gnus-article-prepare-hook 'gnorb-gnus-hint-relevant-message)
+
+(defun gnorb-gnus-insert-format-letter-maybe (header)
+  (if (and gnorb-tracking-enabled
+                (not (memq (car (gnus-find-method-for-group
+                                 gnus-newsgroup-name))
+                           '(nnvirtual nnir))))
+           (let ((ref-ids (mail-header-references header))
+                 (msg-id (mail-header-message-id header)))
+             (if (or (gnus-registry-get-id-key msg-id 'gnorb-ids)
+                     (and ref-ids
+                          (gnorb-find-visit-candidates ref-ids)))
+                 gnorb-gnus-summary-mark
+               " "))
+         " "))
+
+(fset (intern (concat "gnus-user-format-function-"
+                     gnorb-gnus-summary-mark-format-letter))
+      (lambda (header)
+       (gnorb-gnus-insert-format-letter-maybe header)))
+
+;;;###autoload
+(defun gnorb-gnus-view ()
+  "Display the first relevant TODO heading for the message under point"
+  ;; this is pretty barebones, need to make sure we have a valid
+  ;; article buffer to access, and think about what to do for
+  ;; window-configuration!
+
+  ;; boy is this broken now.
+  (interactive)
+  (let ((refs (gnus-fetch-original-field "references"))
+       rel-headings)
+    (when refs
+      (setq rel-headings (gnorb-find-visit-candidates refs))
+      (delete-other-windows)
+      (org-id-goto (car rel-headings)))))
+
+(provide 'gnorb-gnus)
+;;; gnorb-gnus.el ends here
diff --git a/gnorb-org.el b/gnorb-org.el
new file mode 100644
index 0000000..bc46eda
--- /dev/null
+++ b/gnorb-org.el
@@ -0,0 +1,678 @@
+;;; gnorb-org.el --- The Org-centric functions of gnorb
+
+;; Copyright (C) 2014  Eric Abrahamsen
+
+;; Author: Eric Abrahamsen  <address@hidden>
+;; Keywords: 
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; 
+
+;;; Code:
+
+(require 'gnorb-utils)
+
+(defgroup gnorb-org nil
+  "The Org bits of Gnorb."
+  :tag "Gnorb Org"
+  :group 'gnorb)
+
+(defcustom gnorb-org-after-message-setup-hook nil
+  "Hook run in a message buffer after setting up the message from
+  `gnorb-org-handle-mail' or `gnorb-org-email-subtree'."
+  :group 'gnorb-org
+  :type 'hook)
+
+(defcustom gnorb-org-trigger-actions
+  '(("todo state" . todo)
+    ("take note" . note)
+    ("don't associate" . no-associate)
+    ("only associate" . associate)
+;    ("capture to child" . cap-child)
+;    ("capture to sibling" . cap-sib)
+)
+  "List of potential actions that can be taken on headings.
+
+When triggering an Org heading after receiving or sending a
+message, this option lists the possible actions to take. Built-in
+actions include:
+
+todo state: Associate the message, and change TODO state.
+take note: Associate the message, and take a note.
+don't associate: Do nothing at all, don't connect the message and TODO.
+only associate: Associate the message with this heading, do nothing else.
+capture to child: [not yet implemented] Associate this message with a new 
child heading.
+capture to sibling: [not yet implemented] Associate this message with a new 
sibling heading.
+
+You can reorder this list or remove items as suits your workflow.
+The two \"capture\" options will use the value of
+`gnorb-gnus-new-todo-capture-key' to find the appropriate
+template.
+
+You can also add custom actions to the list. Actions should be a
+cons of a string tag and a symbol indicating a custom function.
+This function will be called on the heading in question, and
+passed a plist containing information about the message from
+which we're triggering."
+  :group 'gnorb-org
+  :type 'list)
+
+(defcustom gnorb-org-msg-id-key "GNORB_MSG_ID"
+  "The name of the org property used to store the Message-IDs
+  from relevant messages. This is no longer used, and will be
+  removed soon."
+  :group 'gnorb-org
+  :type 'string)
+
+(defcustom gnorb-org-mail-scan-scope 2
+  "Number of paragraphs to scan for mail-related links.
+
+When handling a TODO heading with `gnorb-org-handle-mail', Gnorb
+will typically reply to the most recent message associated with
+this heading. If there are no such messages, or message tracking
+is disabled entirely, or `gnorb-org-handle-mail' has been called
+with a prefix arg, the heading and body text of the subtree under
+point will instead be scanned for gnus:, mailto:, and bbdb:
+links. This option controls how many paragraphs of body text to
+scan. Set to 0 to only look in the heading.")
+
+(make-obsolete-variable
+ 'gnorb-org-mail-scan-strategies
+ "This variable has been superseded by `gnorb-org-trigger-actions'"
+ "September 12, 2014" 'set)
+
+(make-obsolete-variable
+ 'gnorb-org-mail-scan-state-changes
+ "This variable has been superseded by `gnorb-org-trigger-actions'"
+ "September 12, 2014" 'set)
+
+(make-obsolete-variable
+ 'gnorb-org-mail-scan-function
+ "This variable has been superseded by `gnorb-org-trigger-actions'"
+ "September 12, 2014" 'set)
+
+(defcustom gnorb-org-find-candidates-match nil
+  "When scanning all org files for heading related to an incoming
+message, this option will limit which headings will be offered as
+target candidates. Specifically it will be used as the second
+argument to `org-map-entries', and syntax is the same as that
+used in an agenda tags view."
+  :group 'gnorb-org
+  :type 'symbol)
+
+;;;###autoload
+(defun gnorb-org-contact-link (rec)
+  "Prompt for a BBDB record and insert a link to that record at
+point.
+
+There's really no reason to use this instead of regular old
+`org-insert-link' with BBDB completion. But there might be in the
+future!"
+  ;; this needs to handle an active region.
+  (interactive (list (gnorb-prompt-for-bbdb-record)))
+  (let* ((name (bbdb-record-name rec))
+        (link (concat "bbdb:" (org-link-escape name))))
+    (org-store-link-props :type "bbdb" :name name
+                         :link link :description name)
+    (if (called-interactively-p 'any)
+       (insert (format "[[%s][%s]]" link name))
+      link)))
+
+(defun gnorb-org-restore-after-send ()
+  "After an email is sent, clean up the gnus summary buffer, put
+us back where we came from, and go through all the org ids that
+might have been in the outgoing message's headers and call
+`gnorb-trigger-todo-action' on each one."
+  (delete-other-windows)
+  (dolist (id gnorb-message-org-ids)
+    (org-id-goto id)
+    (org-reveal)
+    (gnorb-trigger-todo-action nil id))
+  ;; this is a little unnecessary, but it may save grief
+  (setq gnorb-gnus-message-info nil)
+  (setq gnorb-message-org-ids nil))
+
+(defun gnorb-org-extract-links (&optional arg region)
+  "See if there are viable links in the subtree under point."
+  ;; We're not currently using the arg. What could we do with it?
+  (let (strings)
+    ;; If the region was active, only use the region
+    (if region
+       (push (buffer-substring (car region) (cdr region))
+             strings)
+      ;; Otherwise collect the heading text, and all the paragraph
+      ;; text.
+      (save-restriction
+       (org-narrow-to-subtree)
+       (let ((head (org-element-at-point))
+             (tree (org-element-parse-buffer)))
+         (push (org-element-property
+                :raw-value
+                head)
+               strings)
+         (org-element-map tree 'paragraph
+           (lambda (p)
+             (push (org-element-interpret-data p)
+                   strings))
+           nil nil 'drawer))))
+    (when strings
+      ;; Limit number of paragraphs based on
+      ;; `gnorb-org-mail-scan-scope'
+      (setq strings
+           (cond ((eq gnorb-org-mail-scan-scope 'all)
+                  strings)
+                 ((numberp gnorb-org-mail-scan-scope)
+                  (delq nil
+                        (subseq
+                         strings 0 (1+ gnorb-org-mail-scan-scope))))
+                 ;; We could provide more options here. 'tree vs
+                 ;; 'subtree, for instance.
+                 (t
+                  strings)))
+      (with-temp-buffer
+       (dolist (s strings)
+         (insert s)
+         (insert "\n"))
+       (goto-char (point-min))
+       (gnorb-scan-links (point-max) 'gnus 'mail 'bbdb)))))
+
+(defun gnorb-org-extract-mail-stuff (&optional arg region)
+  "Decide how to hande the Org heading under point as an email task.
+
+See the docstring of `gnorb-org-handle-mail' for details."
+  (if (or (not gnorb-tracking-enabled)
+         region)
+      (gnorb-org-extract-links arg region)
+    ;; Get all the messages associated with the IDS in this subtree.
+    (let ((assoc-msg-ids
+          (delete-dups
+           (cl-mapcan
+            (lambda (id)
+              (gnorb-registry-org-id-search id))
+            (gnorb-collect-ids)))))
+      (gnorb-org-extract-mail-tracking assoc-msg-ids arg region))))
+
+(defun gnorb-org-extract-mail-tracking (assoc-msg-ids &optional arg region)
+
+  (let* ((all-links (gnorb-org-extract-links nil region))
+        ;; The latest (by the creation-time registry key) of all the
+        ;; tracked messages that were not sent by our user.
+        (latest-msg-id
+         (when assoc-msg-ids
+           (car
+            (sort
+             (remove-if
+              (lambda (m)
+                (let ((from (car (gnus-registry-get-id-key m 'sender))))
+                  (or (null from)
+                      (string-match-p
+                       user-mail-address from)
+                      (string-match-p
+                       message-alternative-emails from))))
+              assoc-msg-ids)
+             (lambda (r l)
+               (time-less-p
+                (car (gnus-registry-get-id-key l 'creation-time))
+                (car (gnus-registry-get-id-key r 'creation-time)))))))))
+    (cond
+     ;; If there are no tracked messages, or the user has specifically
+     ;; requested we ignore them with the prefix arg, just return the
+     ;; found links in the subtree.
+     ((or arg
+         (null latest-msg-id))
+      all-links)
+     ;; Otherwise ignore the other links in the subtree, and return
+     ;; the latest message.
+     (latest-msg-id
+      `(:gnus ,(list (gnorb-msg-id-to-link latest-msg-id)))))))
+
+(defun gnorb-org-setup-message
+    (&optional messages mails from cc bcc attachments text ids)
+  "Common message setup routine for other gnorb-org commands.
+MESSAGES is a list of gnus links pointing to messages -- we
+currently only use the first of the list. MAILS is a list of
+email address strings suitable for inserting in the To header.
+ATTACHMENTS is a list of filenames to attach. TEXT is a string or
+buffer, which is inserted in the message body. IDS is one or more
+Org heading ids, associating the outgoing message with those
+headings."
+  (require 'gnorb-gnus)
+  (if (not messages)
+      ;; Either compose new message...
+      (compose-mail (mapconcat 'identity mails ", "))
+    ;; ...or follow link and start reply.
+    (condition-case err
+       (let ((ret-val (org-gnus-open (org-link-unescape (car messages)))))
+         ;; We failed to open the link (probably), ret-val would be
+         ;; t otherwise
+         (when (stringp ret-val)
+           (error ret-val))
+         (call-interactively
+          'gnus-summary-wide-reply-with-original)
+         ;; Add MAILS to message To header.
+         (when mails
+           (message-goto-to)
+           (insert ", ")
+           (insert (mapconcat 'identity mails ", "))))
+      (error (when (and (window-configuration-p gnorb-window-conf)
+                       gnorb-return-marker)
+              (set-window-configuration gnorb-window-conf)
+              (goto-char gnorb-return-marker))
+            (signal (car err) (cdr err)))))
+  ;; Return us after message is sent.
+  (add-to-list 'message-exit-actions
+              'gnorb-org-restore-after-send t)
+  ;; Set headers from MAIL_* properties (from, cc, and bcc).
+  (cl-flet ((sh (h)
+               (when (cdr h)
+                 (funcall (intern (format "message-goto-%s" (car h))))
+                 (let ((message-beginning-of-line t)
+                       (show-trailing-whitespace t))
+                   (message-beginning-of-line)
+                   (unless (bolp)
+                     (kill-line))
+                   (insert (cdr h))))))
+    (dolist (h `((from . ,from) (cc . ,cc) (bcc . ,bcc)))
+      (sh h)))
+  ;; attach ATTACHMENTS
+  (map-y-or-n-p
+   (lambda (a) (format "Attach %s to outgoing message? "
+                      (file-name-nondirectory a)))
+   (lambda (a)
+     (mml-attach-file a (mm-default-file-encoding a)
+                     nil "attachment"))
+   attachments
+   '("file" "files" "attach"))
+  ;; insert text, if any
+  (when text
+    (message-goto-body)
+    (insert"\n")
+    (if (bufferp text)
+       (insert-buffer-substring text)
+      (insert text)))
+  ;; insert org ids, if any
+  (when ids
+    (unless (listp ids)
+      (setq ids (list ids)))
+    (save-excursion
+      (save-restriction
+       (message-narrow-to-headers)
+       (dolist (i ids)
+         (goto-char (point-at-bol))
+         (open-line 1)
+         ;; this function hardly does anything
+         (message-insert-header
+          (intern gnorb-mail-header) i)))))
+  ;; put point somewhere reasonable
+  (if (or mails messages)
+      (if (not messages)
+         (message-goto-subject)
+       (message-goto-body))
+    (message-goto-to))
+  (run-hooks 'gnorb-org-after-message-setup-hook))
+
+(defun gnorb-org-attachment-list (&optional id)
+  "Get a list of files (absolute filenames) attached to the
+current heading, or the heading indicated by optional argument ID."
+  (when (featurep 'org-attach)
+    (let* ((attach-dir (save-excursion
+                        (when id
+                          (org-id-goto id))
+                        (org-attach-dir t)))
+          (files
+           (mapcar
+            (lambda (f)
+              (expand-file-name f attach-dir))
+            (org-attach-file-list attach-dir))))
+      files)))
+
+;;;###autoload
+(defun gnorb-org-handle-mail (&optional arg text file)
+  "Handle current headline as a mail TODO.
+
+How this function behaves depends on whether you're using Gnorb
+for email tracking, also on the prefix arg, and on the active
+region.
+
+If tracking is enabled and there is no prefix arg, Gnorb will
+begin a reply to the newest associated message that wasn't sent
+by the user -- ie, the Sender header doesn't match
+`user-mail-address' or `message-alternative-emails'.
+
+If tracking is enabled and there is a prefix arg, ignore the
+tracked messages and instead scan the subtree for mail-related
+links. This means links prefixed with gnus:, mailto:, or bbdb:.
+See `gnorb-org-mail-scan-scope' to limit the scope of this scan.
+Do something appropriate with the resulting links.
+
+With a double prefix arg, ignore all tracked messages and all
+links, and compose a blank new message.
+
+If tracking is enabled and you want to reply to a
+specific (earlier) message in the tracking history, use
+`gnorb-org-view' to open an nnir *Summary* buffer containing all
+the messages, and reply to the one you want. Your reply will be
+automatically tracked, as well.
+
+If tracking is not enabled and you want to use a specific link in
+the subtree as a basis for the email action, then put the region
+around that link before you call this message."
+  (interactive "P")
+  (setq gnorb-window-conf (current-window-configuration))
+  (move-marker gnorb-return-marker (point))
+  (when (eq major-mode 'org-agenda-mode)
+    ;; If this is all the different types, we could skip the check.
+    (org-agenda-check-type t 'agenda 'timeline 'todo 'tags 'search)
+    (org-agenda-check-no-diary)
+    (let* ((marker (or (org-get-at-bol 'org-hd-marker)
+                      (org-agenda-error)))
+          (buffer (marker-buffer marker))
+          (pos (marker-position marker)))
+      (switch-to-buffer buffer)
+      (widen)
+      (goto-char pos)))
+  (let ((region
+        (when (use-region-p)
+          (cons (region-beginning) (region-end)))))
+    (deactivate-mark)
+    (save-excursion
+      (unless (org-back-to-heading t)
+       (error "Not in an org item"))
+      (cl-flet ((mp (p) (org-entry-get (point) p t)))
+       ;; Double prefix means ignore everything and compose a blank
+       ;; mail.
+       (let* ((links (unless (equal arg '(16))
+                       (gnorb-org-extract-mail-stuff arg region)))
+              (attachments (gnorb-org-attachment-list))
+              (from (mp "MAIL_FROM"))
+              (cc (mp "MAIL_CC"))
+              (bcc (mp "MAIL_BCC"))
+              (org-id (org-id-get-create))
+              (recs (plist-get links :bbdb))
+              (message-mode-hook (copy-sequence message-mode-hook))
+              mails)
+         (when file
+           (cons file attachments))
+         (when recs
+           (setq recs
+                 (delq nil
+                       (mapcar
+                        (lambda (r)
+                          (car (bbdb-message-search
+                                (org-link-unescape r)
+                                nil)))
+                        recs))))
+         (when recs
+           (dolist (r recs)
+             (push (bbdb-mail-address r) mails)))
+         (when (and recs
+                    gnorb-bbdb-posting-styles)
+           (add-hook 'message-mode-hook
+                     (lambda ()
+                       (gnorb-bbdb-configure-posting-styles (cdr recs))
+                       (gnorb-bbdb-configure-posting-styles (list (car 
recs))))))
+         (gnorb-org-setup-message
+          (plist-get links :gnus)
+          (append mails (plist-get links :mail))
+          from cc bcc
+          attachments text org-id))))))
+
+;;; Email subtree
+
+(defcustom gnorb-org-email-subtree-text-parameters nil
+  "A plist of export parameters corresponding to the EXT-PLIST
+  argument to the export functions, for use when exporting to
+  text."
+  :group 'gnorb-org
+  :type 'boolean)
+
+(defcustom gnorb-org-email-subtree-file-parameters nil
+  "A plist of export parameters corresponding to the EXT-PLIST
+  argument to the export functions, for use when exporting to a
+  file."
+  :group 'gnorb-org
+  :type 'boolean)
+
+(defcustom gnorb-org-email-subtree-text-options '(nil t nil t)
+  "A list of ts and nils corresponding to Org's export options,
+to be used when exporting to text. The options, in order, are
+async, subtreep, visible-only, and body-only."
+  :group 'gnorb-org
+  :type 'list)
+
+(defcustom gnorb-org-email-subtree-file-options '(nil t nil nil)
+  "A list of ts and nils corresponding to Org's export options,
+to be used when exporting to a file. The options, in order, are
+async, subtreep, visible-only, and body-only."
+  :group 'gnorb-org
+  :type 'list)
+
+(defcustom gnorb-org-export-extensions
+  '((latex ".tex")
+    (ascii ".txt")
+    (html ".html")
+    (org ".org")
+    (icalendar ".ics")
+    (man ".man")
+    (md ".md")
+    (odt ".odt") ; not really, though
+    (texinfo ".texi")
+    (beamer ".tex"))
+  "Correspondence between export backends and their
+respective (usual) file extensions. Ugly way to do it, but what
+the hey..."
+  :group 'gnorb-org)
+
+;;;###autoload
+(defun gnorb-org-email-subtree (&optional arg)
+  "Call on a subtree to export it either to a text string or a file,
+then compose a mail message either with the exported text
+inserted into the message body, or the exported file attached to
+the message.
+
+Export options default to the following: When exporting to a
+buffer: async = nil, subtreep = t, visible-only = nil, body-only
+= t. Options are the same for files, except body-only is set to
+nil. Customize `gnorb-org-email-subtree-text-options' and
+`gnorb-org-email-subtree-file-options', respectively.
+
+Customize `gnorb-org-email-subtree-parameters' to your preferred
+default set of parameters."
+  ;; I sure would have liked to use the built-in dispatch ui, but it's
+  ;; got too much hard-coded stuff.
+  (interactive "P")
+  (org-back-to-heading t)
+  (let* ((backend-string
+         (org-completing-read
+          "Export backend: "
+          (mapcar (lambda (b)
+                    (symbol-name (org-export-backend-name b)))
+                  org-export--registered-backends) nil t))
+        (backend-symbol (intern backend-string))
+        (f-or-t (org-completing-read "Export as file or text? "
+                                     '("file" "text") nil t))
+        (org-export-show-temporary-export-buffer nil)
+        (opts (if (equal f-or-t "text")
+                  gnorb-org-email-subtree-text-options
+                gnorb-org-email-subtree-file-options))
+        (result
+         (if (equal f-or-t "text")
+             (apply 'org-export-to-buffer
+                    `(,backend-symbol
+                      "*Gnorb Export*"
+                      ,@opts
+                      ,gnorb-org-email-subtree-text-parameters))
+           (apply 'org-export-to-file
+                  `(,backend-symbol
+                    ,(org-export-output-file-name
+                      (second (assoc backend-symbol 
gnorb-org-export-extensions))
+                      t gnorb-tmp-dir)
+                    ,@opts
+                    ,gnorb-org-email-subtree-file-parameters))))
+        text file)
+    (setq gnorb-window-conf (current-window-configuration))
+    (move-marker gnorb-return-marker (point))
+    (if (bufferp result)
+       (setq text result)
+      (setq file result))
+    (gnorb-org-handle-mail arg text file)))
+
+(defcustom gnorb-org-capture-collect-link-p t
+  "Should the capture process store a link to the gnus message or
+  BBDB record under point, even if it's not part of the template?
+  You'll probably end up needing it, anyway."
+  :group 'gnorb-org)
+
+(defun gnorb-org-capture-collect-link ()
+  (when gnorb-org-capture-collect-link-p
+    (let ((buf (org-capture-get :original-buffer)))
+      (when buf
+       (with-current-buffer buf
+         (when (memq major-mode '(gnus-summary-mode
+                                  gnus-article-mode
+                                  bbdb-mode))
+           (call-interactively 'org-store-link)))))))
+
+(add-hook 'org-capture-mode-hook 'gnorb-org-capture-collect-link)
+
+;;; Agenda/BBDB popup stuff
+
+(defcustom gnorb-org-agenda-popup-bbdb nil
+  "Should Agenda tags search pop up a BBDB buffer with matching
+  records?
+
+Records are considered matching if they have an `org-tags' field
+matching the current Agenda search. The name of that field can be
+customized with `gnorb-bbdb-org-tag-field'."
+  :group 'gnorb-org)
+
+(defcustom gnorb-org-bbdb-popup-layout 'pop-up-multi-line
+  "Default BBDB buffer layout for automatic Org Agenda display."
+  :group 'gnorb-org
+  :type '(choice (const one-line)
+                (const multi-line)
+                (const full-multi-line)
+                (symbol)))
+
+;;;###autoload
+(defun gnorb-org-popup-bbdb (&optional str)
+  "In an `org-tags-view' Agenda buffer, pop up a BBDB buffer
+showing records whose `org-tags' field matches the current tags
+search."
+  ;; I was hoping to use `org-make-tags-matcher' directly, then snag
+  ;; the tagmatcher from the resulting value, but there doesn't seem
+  ;; to be a reliable way of only getting the tag-related returns. But
+  ;; I'd still like to use that function. So an ugly hack to first
+  ;; remove non-tag contents from the query string, and then make a
+  ;; new call to `org-make-tags-matcher'.
+  (interactive)
+  (require 'gnorb-bbdb)
+  (let (recs)
+    (cond ((and
+           (and (eq major-mode 'org-agenda-mode)
+                (eq org-agenda-type 'tags))
+           (or (called-interactively-p 'any)
+               gnorb-org-agenda-popup-bbdb))
+          (let ((todo-only nil)
+                (str (or str org-agenda-query-string))
+                (re 
"^&?\\([-+:]\\)?\\({[^}]+}\\|LEVEL\\([<=>]\\{1,2\\}\\)\\([0-9]+\\)\\|\\(\\(?:[[:alnum:]_]+\\(?:\\\\-\\)*\\)+\\)\\([<>=]\\{1,2\\}\\)\\({[^}]+}\\|\"[^\"]*\"\\|-?[.0-9]+\\(?:[eE][-+]?[0-9]+\\)?\\)\\|[[:alnum:address@hidden)")
+                or-terms term rest out-or acc tag-clause)
+            (setq or-terms (org-split-string str "|"))
+            (while (setq term (pop or-terms))
+              (setq acc nil)
+              (while (string-match re term)
+                (setq rest (substring term (match-end 0)))
+                (let ((sub-term (match-string 0 term)))
+                  (unless (save-match-data ; this isn't a tag, don't want it
+                            (string-match "\\([<>=]\\)" sub-term))
+                    (push sub-term acc))
+                  (setq term rest)))
+              (push (mapconcat 'identity (nreverse acc) "") out-or))
+            (setq str (mapconcat 'identity (nreverse out-or) "|"))
+            (setq tag-clause (cdr (org-make-tags-matcher str)))
+            (unless (equal str "")
+              (setq recs
+                    (remove-if-not
+                     (lambda (r)
+                       (let ((rec-tags (bbdb-record-xfield
+                                        r gnorb-bbdb-org-tag-field)))
+                         (and rec-tags
+                              (let ((tags-list (org-split-string rec-tags ":"))
+                                    (case-fold-search t)
+                                    (org-trust-scanner-tags t))
+                                (eval tag-clause)))))
+                     (bbdb-records))))))
+         ((eq major-mode 'org-mode)
+          (save-excursion
+            (org-back-to-heading)
+            (let ((bound (org-element-property
+                          :end (org-element-at-point)))
+                  desc rec)
+              (while (re-search-forward
+                      org-bracket-link-analytic-regexp bound t)
+                (when (string-match-p "bbdb" (match-string 2))
+                  (setq desc (match-string 5)
+                        rec (bbdb-search (bbdb-records) desc desc desc)
+                        recs (append recs rec))))))))
+    (if recs
+       (bbdb-display-records
+        recs gnorb-org-bbdb-popup-layout)
+      (when (get-buffer-window bbdb-buffer-name)
+       (quit-window nil
+                    (get-buffer-window bbdb-buffer-name)))
+      (when (called-interactively-p 'any)
+       (message "No relevant BBDB records")))))
+
+(if (featurep 'gnorb-bbdb)
+    (add-hook 'org-agenda-finalize-hook 'gnorb-org-popup-bbdb))
+
+;;; Groups from the gnorb gnus server backend
+
+;;;###autoload
+(defun gnorb-org-view ()
+  "Search the subtree at point for links to gnus messages, and
+then show them in an ephemeral group, in gnus.
+
+This won't work unless you've added a \"nngnorb\" server to
+your gnus select methods."
+  ;; this should also work on the active region, if there is one.
+  (interactive)
+  (setq gnorb-window-conf (current-window-configuration))
+  (move-marker gnorb-return-marker (point))
+  (when (eq major-mode 'org-agenda-mode)
+    (org-agenda-check-type t 'agenda 'timeline 'todo 'tags)
+    (org-agenda-check-no-diary)
+    (let* ((marker (or (org-get-at-bol 'org-hd-marker)
+                      (org-agenda-error)))
+          (buffer (marker-buffer marker))
+          (pos (marker-position marker)))
+      (switch-to-buffer buffer)
+      (goto-char pos)
+      (org-reveal)))
+  (let (id)
+    (save-excursion
+      (org-back-to-heading)
+      (setq id (concat "id+" (org-id-get-create))))
+    (gnorb-gnus-search-messages
+     id
+     `(when (and (window-configuration-p gnorb-window-conf)
+                gnorb-return-marker)
+       (set-window-configuration gnorb-window-conf)
+       (goto-char gnorb-return-marker)))))
+
+(provide 'gnorb-org)
+;;; gnorb-org.el ends here
diff --git a/gnorb-registry.el b/gnorb-registry.el
new file mode 100644
index 0000000..0eee32c
--- /dev/null
+++ b/gnorb-registry.el
@@ -0,0 +1,194 @@
+;;; gnorb-registry.el --- Registry implementation for Gnorb
+
+;; This file is in the public domain.
+
+;; Author: Eric Abrahamsen <address@hidden>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Early on, Gnorb's message/todo tracking was done by relying on the
+;; user to insert links to received messages into an Org heading, and
+;; by automatically storing the Message-Ids of sent messages in a
+;; property (`gnorb-org-msg-id-key', defaulting to GNORB_MSG_ID) on
+;; the same heading. The heading could find all relevant messages by
+;; combining the links (incoming) and the IDs of the Gnorb-specific
+;; property (outgoing).
+;;
+;; In the end, this proved to be fragile and messy. Enter the
+;; registry. The Gnus registry is a specialization of a general
+;; "registry" library -- it's possible to roll your own. If you want
+;; to track connections between messages and Org headings, it's an
+;; obvious choice: Each relevant message is stored in the registry,
+;; keyed on its Message-ID, and the org-ids of all relevant headings
+;; are stored in a custom property, in our case gnorb-ids. This allows
+;; us to keep all Gnorb-specific data in one place, without polluting
+;; Org files or Gnus messages, persistent on disk, and with the added
+;; bonus of providing a place to keep arbitrary additional metadata.
+;;
+;; The drawback is that the connections are no longer readily visible
+;; to the user (they need to query the registry to see them), and it
+;; becomes perhaps a bit more difficult (but only a bit) to keep
+;; registry data in sync with the current state of the user's Gnus and
+;; Org files. But a clear win, in the end.
+
+;;; Code:
+
+(require 'gnus-registry)
+
+(defgroup gnorb-registry nil
+  "Gnorb's use of the Gnus registry."
+  :tag "Gnorb Registry"
+  :group 'gnorb)
+
+(defun gnorb-registry-make-entry (msg-id sender subject org-id group)
+  "Create a Gnus registry entry for a message, either received or
+sent. Save the relevant Org ids in the 'gnorb-ids key."
+  ;; This set-id-key stuff is actually horribly
+  ;; inefficient.
+  (when gnorb-tracking-enabled
+    (gnus-registry-get-or-make-entry msg-id)
+    (when sender
+      (gnus-registry-set-id-key msg-id 'sender (list sender)))
+    (when subject
+      (gnus-registry-set-id-key msg-id 'subject (list subject)))
+    (when org-id
+      (let ((ids (gnus-registry-get-id-key msg-id 'gnorb-ids)))
+       (unless (member org-id ids)
+        (gnus-registry-set-id-key msg-id 'gnorb-ids (if (stringp org-id)
+                                                        (cons org-id ids)
+                                                      (append org-id ids))))))
+    (when group
+      (gnus-registry-set-id-key msg-id 'group (list group)))
+    (gnus-registry-get-or-make-entry msg-id)))
+
+(defun gnorb-registry-capture ()
+  "When capturing from a Gnus message, add our new Org heading id
+to the message's registry entry, under the 'gnorb-ids key."
+  (when (and (with-current-buffer
+                (org-capture-get :original-buffer)
+              (memq major-mode '(gnus-summary-mode gnus-article-mode)))
+            (not org-note-abort))
+    (let* ((msg-id
+           (format "<%s>" (plist-get org-store-link-plist :message-id)))
+          (entry (gnus-registry-get-or-make-entry msg-id))
+          (org-ids
+           (gnus-registry-get-id-key msg-id 'gnorb-ids))
+          (new-org-id (org-id-get-create)))
+      (plist-put org-capture-plist :gnorb-id new-org-id)
+      (setq org-ids (cons new-org-id org-ids))
+      (setq org-ids (delete-dups org-ids))
+      (gnus-registry-set-id-key msg-id 'gnorb-ids org-ids))))
+
+
+(defun gnorb-registry-capture-abort-cleanup ()
+  (when (and (org-capture-get :gnorb-id)
+            org-note-abort)
+    (condition-case error
+       (let* ((msg-id (format "<%s>" (plist-get org-store-link-plist 
:message-id)))
+              (existing-org-ids (gnus-registry-get-id-key msg-id 'gnorb-ids))
+              (org-id (org-capture-get :gnorb-id)))
+         (when (member org-id existing-org-ids)
+           (gnus-registry-set-id-key msg-id 'gnorb-ids
+                                     (remove org-id existing-org-ids)))
+         (setq abort-note 'clean))
+      (error
+       (setq abort-note 'dirty)))))
+
+(defun gnorb-find-visit-candidates (ids)
+  "For all message-ids in IDS (which should be a list of
+Message-ID strings, with angle brackets, or a single string of
+Message-IDs), produce a list of Org ids for headings that are
+relevant to that message."
+  (let (ret-val sub-val)
+    (when (stringp ids)
+      (setq ids (gnus-extract-references ids)))
+    (when gnorb-tracking-enabled
+      (setq ids (delete-dups ids))
+      (progn
+       (dolist (id ids)
+         (when
+             (setq sub-val
+                   (gnus-registry-get-id-key id 'gnorb-ids))
+           (setq ret-val (append sub-val ret-val))))))
+    (delete-dups ret-val)))
+
+(defun gnorb-registry-org-id-search (id)
+  "Find all messages that have the org ID in their 'gnorb-ids
+key."
+  (registry-search gnus-registry-db :member `((gnorb-ids ,id))))
+
+(defun gnorb-registry-transition-from-props (arg)
+  "Helper function for transitioning the old tracking system to the new.
+
+The old system relied on storing sent message ids on relevant Org
+headings, in the `gnorb-org-msg-id-key' property. The new system
+uses the gnus registry to track relations between messages and
+Org headings. This function will go through your agenda files,
+find headings that have the `gnorb-org-msg-id-key' property set,
+and create new registry entries that reflect that connection.
+
+Call with a prefix arg to additionally delete the
+`gnorb-org-msg-id-key' altogether from your Org headings. As this
+function will not create duplicate registry entries, it's safe to
+run it once with no prefix arg, to keep the properties in place,
+and then once you're sure everything's working okay, run it again
+with a prefix arg, to clean the Gnorb-specific properties from
+your Org files."
+  (interactive "P")
+  (let ((count 0))
+    (message "Collecting all relevant Org headings, this could take a 
while...")
+    (org-map-entries
+     (lambda ()
+       (let ((id (org-id-get))
+            (props (org-entry-get-multivalued-property
+              (point) gnorb-org-msg-id-key))
+            links group id)
+       (when props
+         ;; If the property is set, we should probably assume that any
+         ;; Gnus links in the subtree are relevant, and should also be
+         ;; collected and associated.
+         (setq links (gnorb-scan-links
+                      (org-element-property :end (org-element-at-point))
+                      'gnus))
+         (dolist (l (plist-get links :gnus))
+           (gnorb-registry-make-entry
+            (second (split-string l "#")) nil nil
+            id (first (split-string l "#"))))
+         (dolist (p props)
+           (setq id )
+           (gnorb-registry-make-entry p nil nil id nil)
+           ;; This function will try to find the group for the message
+           ;; and set that value on the registry entry if it can find
+           ;; it.
+           (unless (gnus-registry-get-id-key p 'group)
+             (gnorb-msg-id-to-group p))
+           (incf count)))))
+     gnorb-org-find-candidates-match
+     'agenda 'archive 'comment)
+    (message "Collecting all relevant Org headings, this could take a while... 
done")
+    ;; Delete the properties if the user has asked us to do so.
+    (if (equal arg '(4))
+       (progn
+         (dolist (f (org-agenda-files))
+           (with-current-buffer (get-file-buffer f)
+             (org-delete-property-globally gnorb-org-msg-id-key)))
+         (message "%d entries created; all Gnorb-specific properties deleted."
+                  count))
+      (message "%d entries created." count))))
+
+(provide 'gnorb-registry)
diff --git a/gnorb-utils.el b/gnorb-utils.el
new file mode 100644
index 0000000..68fe6b6
--- /dev/null
+++ b/gnorb-utils.el
@@ -0,0 +1,311 @@
+;;; gnorb-utils.el --- Common utilities for all gnorb stuff.
+
+;; Copyright (C) 2014  Eric Abrahamsen
+
+;; Author: Eric Abrahamsen <address@hidden>
+;; Keywords:
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(require 'cl)
+(require 'mailcap)
+(require 'gnus)
+;(require 'message)
+(require 'bbdb)
+(require 'org)
+(require 'org-bbdb)
+(require 'org-gnus)
+
+(mailcap-parse-mimetypes)
+
+(defgroup gnorb nil
+  "Glue code between Gnus, Org, and BBDB."
+  :tag "Gnorb")
+
+(make-obsolete-variable
+ 'gnorb-trigger-todo-default
+ "This variable has been superseded by
+`gnorb-org-trigger-actions'"
+ "September 8, 2014" 'set)
+
+(defun gnorb-prompt-for-bbdb-record ()
+  "Prompt the user for a BBDB record."
+  (let ((recs (bbdb-records))
+       name)
+    (while (> (length recs) 1)
+      (setq name
+           (completing-read
+            (format "Filter records by regexp (%d remaining): "
+                    (length recs))
+            (mapcar 'bbdb-record-name recs)))
+      (setq recs (bbdb-search recs name name name nil nil)))
+    (if recs
+       (car recs)
+      (error "No matching records"))))
+
+(defvar gnorb-tmp-dir (make-temp-file "emacs-gnorb" t)
+  "Temporary directory where attachments etc are saved.")
+
+(defvar gnorb-message-org-ids nil
+  "List of Org heading IDs from the outgoing Gnus message, used
+  to mark mail TODOs as done once the message is sent."
+  ;; The send hook either populates this, or sets it to nil, depending
+  ;; on whether the message in question has an Org id header. Then
+  ;; `gnorb-org-restore-after-send' checks for it and acts
+  ;; appropriately, then sets it to nil.
+  )
+
+(defvar gnorb-window-conf nil
+  "Save window configurations here, for restoration after mails
+are sent, or Org headings triggered.")
+
+(defvar gnorb-return-marker (make-marker)
+  "Return point here after various actions, to be used together
+with `gnorb-window-conf'.")
+
+(defcustom gnorb-mail-header "X-Org-ID"
+  "Name of the mail header used to store the ID of a related Org
+  heading. Only used locally: always stripped when the mail is
+  sent."
+  :group 'gnorb
+  :type 'string)
+
+;;; this is just ghastly, but the value of this var is single regexp
+;;; group containing various header names, and we want our value
+;;; inside that group.
+(eval-after-load 'message
+  `(let ((ign-headers-list
+         (split-string message-ignored-mail-headers
+                       "|"))
+        (our-val (concat gnorb-mail-header "\\")))
+     (unless (member our-val ign-headers-list)
+       (setq ign-headers-list
+            `(,@(butlast ign-headers-list 1) ,our-val
+              ,@(last ign-headers-list 1)))
+       (setq message-ignored-mail-headers
+            (mapconcat
+             'identity ign-headers-list "|")))))
+
+(defun gnorb-restore-layout ()
+  "Restore window layout and value of point after a Gnorb command.
+
+Some Gnorb commands change the window layout (ie `gnorb-org-view'
+or incoming email triggering). This command restores the layout
+to what it was. Bind it to a global key, or to local keys in Org
+and Gnus and BBDB maps."
+  (interactive)
+  (when (window-configuration-p gnorb-window-conf)
+    (set-window-configuration gnorb-window-conf)
+    (when (buffer-live-p (marker-buffer gnorb-return-marker))
+      (goto-char gnorb-return-marker))))
+
+(defun gnorb-trigger-todo-action (arg &optional id)
+  "Do the actual restore action. Two main things here. First: if
+we were in the agenda when this was called, then keep us in the
+agenda. Then let the user choose an action from the value of
+`gnorb-org-trigger-actions'."
+  (let ((agenda-p (eq major-mode 'org-agenda-mode))
+       (action (cdr (assoc
+                     (org-completing-read
+                      "Action to take: "
+                      gnorb-org-trigger-actions nil t)
+                     gnorb-org-trigger-actions)))
+       (root-marker (make-marker)))
+    ;; Place the marker for the relevant TODO heading.
+    (cond (agenda-p
+          (setq root-marker
+                (copy-marker
+                 (org-get-at-bol 'org-hd-marker))))
+         ((derived-mode-p 'org-mode)
+          (move-marker root-marker (point-at-bol)))
+         (id
+          (save-excursion
+            (org-id-goto id)
+            (move-marker root-marker (point-at-bol)))))
+    ;; Query about attaching email attachments.
+    (org-with-point-at root-marker
+      (map-y-or-n-p
+       (lambda (a)
+        (format "Attach %s to heading? "
+                (file-name-nondirectory a)))
+       (lambda (a) (org-attach-attach a nil 'mv))
+       gnorb-gnus-capture-attachments
+       '("file" "files" "attach")))
+    (setq gnorb-gnus-capture-attachments nil)
+    (cl-labels
+       ((make-entry
+         (id)
+         (gnorb-registry-make-entry
+          (plist-get gnorb-gnus-message-info :msg-id)
+          (plist-get gnorb-gnus-message-info :from)
+          (plist-get gnorb-gnus-message-info :subject)
+          id
+          (plist-get gnorb-gnus-message-info :group))))
+      ;; Handle our action.
+      (cond ((eq action 'note)
+            (org-with-point-at root-marker
+              (make-entry (org-id-get-create))
+              (call-interactively 'org-add-note)))
+           ((eq action 'todo)
+            (if agenda-p
+                (progn
+                  (org-with-point-at root-marker
+                   (make-entry (org-id-get-create)))
+                  (call-interactively 'org-agenda-todo))
+              (org-with-point-at root-marker
+                (make-entry (org-id-get-create))
+                (call-interactively 'org-todo))))
+           ((eq action 'no-associate)
+            nil)
+           ((eq action 'associate)
+            (org-with-point-at root-marker
+              (make-entry (org-id-get-create))))
+           ((fboundp action)
+            (org-with-point-at root-marker
+              (make-entry (org-id-get-create))
+              (funcall action gnorb-gnus-message-info)))))))
+
+(defun gnorb-pretty-outline (id &optional kw)
+  "Return pretty outline path of the Org heading indicated by ID.
+
+If the KW argument is true, add the TODO keyword into the path."
+  (org-with-point-at (org-id-find id t)
+    (let ((el (org-element-at-point)))
+      (concat
+       (if kw
+          (format "(%s): "
+                  (org-element-property :todo-keyword el))
+        "")
+       (org-format-outline-path
+       (append
+        (list
+         (file-name-nondirectory
+          (buffer-file-name
+           (org-base-buffer (current-buffer)))))
+        (org-get-outline-path)
+        (list
+         (replace-regexp-in-string
+          org-bracket-link-regexp
+          "\\3" (org-element-property :raw-value el)))))))))
+
+(defun gnorb-scan-links (bound &rest types)
+  "Scan from point to BOUND looking for links of type in TYPES.
+
+TYPES is a list of symbols, possible values include 'bbdb, 'mail,
+and 'gnus."
+  ;; this function could be refactored somewhat -- lots of code
+  ;; repetition. It also should be a little faster for when we're
+  ;; scanning for gnus links only, that's a little slow. We should
+  ;; probably use a different regexp based on the value of TYPES.
+  ;;
+  ;; This function should also *not* be responsible for unescaping
+  ;; links -- we don't know what they're going to be used for, and
+  ;; unescaped is safer.
+  (unless (= (point) bound)
+    (let (addr gnus mail bbdb)
+      (while (re-search-forward org-any-link-re bound t)
+       (setq addr (or (match-string-no-properties 2)
+                      (match-string-no-properties 0)))
+       (cond
+        ((and (memq 'gnus types)
+              (string-match "^<?gnus:" addr))
+         (push (substring addr (match-end 0)) gnus))
+        ((and (memq 'mail types)
+              (string-match "^<?mailto:"; addr))
+         (push (substring addr (match-end 0)) mail))
+        ((and (memq 'bbdb types)
+              (string-match "^<?bbdb:" addr))
+         (push (substring addr (match-end 0)) bbdb))))
+      `(:gnus ,(reverse gnus) :mail ,(reverse mail) :bbdb ,(reverse bbdb)))))
+
+(defun gnorb-msg-id-to-link (msg-id)
+  "Given a message id, try to create a full org link to the
+message."
+  (let ((server-group (gnorb-msg-id-to-group msg-id)))
+    (when server-group
+      (org-link-escape (concat server-group "#" msg-id)))))
+
+(defun gnorb-msg-id-to-group (msg-id)
+  "Given a message id, try to find the group it's in.
+
+So far we're checking the registry, then the groups in
+`gnorb-gnus-sent-groups'. Use search engines? Other clever
+methods?"
+  (let (candidates server-group)
+    (catch 'found
+      (when gnorb-tracking-enabled
+       ;; Make a big list of all the groups where this message might
+       ;; conceivably be.
+       (setq candidates
+             (append (gnus-registry-get-id-key msg-id 'group)
+                     gnorb-gnus-sent-groups))
+       (while (setq server-group (pop candidates))
+         (when (and (stringp server-group)
+                    (not
+                     (string-match-p
+                      "\\(nnir\\|nnvirtual\\|UNKNOWN\\)"
+                      server-group))
+                    (ignore-errors
+                      (gnus-request-head msg-id server-group)))
+               (throw 'found server-group))))
+      (when (featurep 'notmuch)
+       nil))))
+
+(defun gnorb-collect-ids (&optional id)
+  "Collect all Org IDs for a subtree.
+
+Starting with the heading under point (or the heading indicated
+by the ID argument), collect its ID property, and the IDs of all
+child headings."
+  (save-excursion
+    (save-restriction
+      (when id
+       (org-id-goto id))
+      (org-narrow-to-subtree)
+      (org-element-map (org-element-parse-buffer)
+         'headline
+       (lambda (hl)
+         (org-element-property :ID hl))))))
+
+;; Loading the registry
+
+(defvar gnorb-tracking-enabled nil
+  "Internal flag indicating whether Gnorb is successfully plugged
+  into the registry or not.")
+
+(defun gnorb-tracking-initialize ()
+  "Start using the Gnus registry to track correspondences between
+Gnus messages and Org headings. This requires that the Gnus
+registry be in use, and should be called after the call to
+`gnus-registry-initialize'."
+  (require 'gnorb-registry)
+  (add-hook
+   'gnus-started-hook
+   (lambda ()
+     (unless (gnus-registry-install-p)
+       (user-error "Gnorb tracking requires that the Gnus registry be 
installed."))
+     (add-to-list 'gnus-registry-extra-entries-precious 'gnorb-ids)
+     (add-to-list 'gnus-registry-track-extra 'gnorb-ids)
+     (add-hook 'org-capture-mode-hook 'gnorb-registry-capture)
+     (add-hook 'org-capture-prepare-finalize-hook 
'gnorb-registry-capture-abort-cleanup)
+     (setq gnorb-tracking-enabled t))))
+
+(provide 'gnorb-utils)
+;;; gnorb-utils.el ends here
diff --git a/gnorb.el b/gnorb.el
new file mode 100644
index 0000000..b702b71
--- /dev/null
+++ b/gnorb.el
@@ -0,0 +1,41 @@
+;;; gnorb.el --- Glue code between Gnus, Org, and BBDB
+
+;; Copyright (C) 2014  Eric Abrahamsen
+
+;; Version: 1
+
+;; Maintainer: Eric Abrahamsen <address@hidden>
+
+;; Author: Eric Abrahamsen <address@hidden>
+;; Keywords: mail org gnus bbdb todo task
+
+;; URL: https://github.com/girzel/gnorb
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Load this file to load everything.
+
+;;; Code:
+
+(require 'gnorb-utils)
+(require 'nngnorb)
+(require 'gnorb-gnus)
+(require 'gnorb-bbdb)
+(require 'gnorb-org)
+(require 'gnorb-registry)
+
+(provide 'gnorb)
+;;; gnorb.el ends here
diff --git a/gnorb.info b/gnorb.info
new file mode 100644
index 0000000..64304bd
--- /dev/null
+++ b/gnorb.info
@@ -0,0 +1,701 @@
+This is gnorb.info, produced by makeinfo version 5.2 from gnorb.texi.
+
+INFO-DIR-SECTION Emacs
+START-INFO-DIR-ENTRY
+* Gnorb: (gnorb).       Glue code for Gnus, Org, and BBDB.
+END-INFO-DIR-ENTRY
+
+
+File: gnorb.info,  Node: Top,  Next: Introduction,  Up: (dir)
+
+Gnorb Manual
+************
+
+* Menu:
+
+* Introduction::
+* Installation::
+* Setup::
+* Email Tracking::
+* Restoring Window Layout::
+* Recent Mails From BBDB Contacts::
+* BBDB posting styles::
+* BBDB Org tagging::
+* Misc BBDB::
+* Misc Org::
+* Misc Gnus::
+* Suggested Keybindings::
+* Wishlist/TODO::
+* Index::
+
+— The Detailed Node Listing —
+
+Email Tracking
+
+* Email-Related Commands::
+* Trigger Actions::
+* Viewing Tracked Messages in *Summary* Buffers::
+* Hinting in Gnus::
+* Message Attachments::
+
+Misc BBDB
+
+* Searching for messages from BBDB contacts::
+* Citing BBDB contacts::
+* User Options::
+
+Misc Org
+
+* Inserting BBDB links::
+* User Options: User Optionsx. 
+
+Misc Gnus
+
+* Viewing Org headlines relevant to a message::
+* User Options: User Optionsxx. 
+
+
+File: gnorb.info,  Node: Introduction,  Next: Installation,  Prev: Top,  Up: 
Top
+
+1 Introduction
+**************
+
+Gnorb provides glue code between the Gnus, Org, and BBDB packages.  It’s
+aimed at supporting email-based project management, and generally making
+it easier to keep track of email communication.
+
+   Much of the code consists of single-use convenience functions, but
+tracking email conversations with Org requires is more complicated, and
+requires a bit of setup.
+
+   Gnorb can be used in a modular fashion, by selectively loading the
+files “gnorb-org”, “gnorb-gnus” or “gnorb-bbdb” instead of plain old
+“gnorb”.  The package as a whole is rather Org-centric, though, and it
+won’t do much of interest without “gnorb-org”.
+
+   This means that Gnorb doesn’t have hard requirements to any of the
+three base libraries.  For the libraries you are using, however, you’ll
+get best results from using the most recent stable version (yes, that
+means BBDB 3).  Some of the features in Gnorb only work with development
+versions of these libraries (those cases are noted below).
+
+
+File: gnorb.info,  Node: Installation,  Next: Setup,  Prev: Introduction,  Up: 
Top
+
+2 Installation
+**************
+
+Gnorb is best installed via the Elpa package manager – look for it in
+‘list-packages’.
+
+   You can also clone the source code from
+<https://github.com/girzel/gnorb>, and put the “gnorb” directory on your
+load-path.  The Github site is also a good place to report bugs and
+other issues.
+
+
+File: gnorb.info,  Node: Setup,  Next: Email Tracking,  Prev: Installation,  
Up: Top
+
+3 Setup
+*******
+
+Loading “gnorb” will make the basic functions available.  Using Gnorb
+for email tracking takes a bit more setup, however:
+
+  1. Email tracking is done via the Gnus registry, so that must be
+     activated with ‘gnus-registry-initialize’.
+  2. It also requires the org-id package to be loaded, and
+     ‘org-id-track-globally’ set to t (that’s the default value, so
+     simply loading the package should be enough).
+  3. Add a nngnorb entry to your ‘gnus-secondary-select-methods’
+     variable.  It will look like (nngnorb “Server name”).  This does
+     nothing but provide a place to hang nnir searches.
+  4. Then put a call to ‘gnorb-tracking-initialize’ in your init files,
+     at some point after the Gnus registry is initialized.
+  5. If you’re not using a local archive method for saving your sent
+     messages (ie you’re using IMAP), you’ll also need to tell Gnorb
+     where to find your sent messages.  Set the variable
+     ‘gnorb-gnus-sent-groups’ to a list of strings; each string should
+     indicate a fully-qualified group name, eg “nnimap+SERVER:GROUP”.
+
+   Lastly, Gnorb doesn’t bind any keys by default; see the *note
+Suggested Keybindings: Suggested Keybindings. section below for
+possibilities.
+
+
+File: gnorb.info,  Node: Email Tracking,  Next: Restoring Window Layout,  
Prev: Setup,  Up: Top
+
+4 Email Tracking
+****************
+
+The most interesting thing Gnorb does is using Org headings to track
+email conversations.  This can mean anything from reminding yourself to
+write to your mother, to conducting delicate business negotiations over
+email, to running an email-based bug tracker.
+
+   Gnorb assists in this process by using the Gnus registry to track
+correspondences between emails and Org headings – specifically, message
+IDs are associated with Org heading ids.  As a conversation develops,
+messages are collected on a heading (and/or its children).  You can
+compose new messages directly from the Org heading, and Gnorb will
+automatically associate your sent message with the conversation.  You
+can open temporary Gnus *Summary* buffers holding all the messages
+associated with an Org subtree, and reply from there.  When you receive
+new messages relevant to a conversation, Gnorb will notice them and
+prompt you to associate them with the appropriate Org heading.
+Attachments on incoming messages can be automatically saved as
+attachments on Org headings, using org-attach.
+
+   In general, the goal is to keep track of whole conversations, reduce
+friction when moving between Gnus and Org, and keep you in the Org
+agenda rather than in Gnus.
+* Menu:
+
+* Email-Related Commands::
+* Trigger Actions::
+* Viewing Tracked Messages in *Summary* Buffers::
+* Hinting in Gnus::
+* Message Attachments::
+
+
+File: gnorb.info,  Node: Email-Related Commands,  Next: Trigger Actions,  Up: 
Email Tracking
+
+4.1 Email-Related Commands
+==========================
+
+Email tracking starts in one of three ways:
+
+  1. With an Org heading that represents an email TODO. Call
+     ‘gnorb-org-handle-mail’ (see below) on the heading to compose a new
+     message, and start the tracking process.
+  2. By calling org-capture on a received message.  Any heading captured
+     from a message will automatically be associated with that message.
+  3. By calling ‘gnorb-gnus-outgoing-do-todo’ in a message composition
+     buffer – see below.
+
+   There are three main email-related commands:
+
+  1. ‘gnorb-org-handle-mail’ is called on an Org heading to compose a
+     new message.  By default, this will begin a reply to the most
+     recent message in the conversation.  If there are no associated
+     messages to reply to (or you call the function with a double prefix
+     arg), Gnorb will look for mailto: or bbdb: links in the heading,
+     and compose a new message to them.
+
+     The sent message will be associated with the Org heading, and
+     you’ll be brought back to the heading and asked to trigger an
+     action on it.
+
+     ‘gnorb-email-subtree’ is an alternative entry-point to
+     ‘gnorb-org-handle-mail’.  It does the same thing as the latter, but
+     first exports the body of the subtree as either text or a file,
+     then inserts the text into the message body, or attaches the file
+     to the message, depending on what you’ve chosen.
+  2. ‘gnorb-gnus-incoming-do-todo’ is called on a message in a Gnus
+     *Summary* buffer.  You’ll be prompted for an Org heading, taken to
+     that heading, and asked to trigger an action on it.
+  3. ‘gnorb-gnus-outgoing-do-todo’ is called in message mode, while
+     composing a new message.
+
+     If called without a prefix arg, a new Org heading will be created
+     after the message is sent, and the sent message associated with it.
+     The new heading will be created as a capture heading, using the
+     template specified by the ‘gnorb-gnus-new-todo-capture-key’ option.
+
+     If you call this function with a prefix arg, you’ll be prompted to
+     choose an existing Org heading instead.  After the the message is
+     sent, you’ll be taken to that heading and prompted to trigger an
+     action on it.
+
+     It’s also possible to call this function *after* a message is sent,
+     in case you forgot.  Gnorb saves information about the most
+     recently sent message for this purpose.
+
+   Because these three commands all express a similar intent, but are
+called in different modes, it can make sense to give each of them the
+same keybinding in the keymaps for Org mode, Gnus summary mode, and
+Message mode, respectively.
+
+
+File: gnorb.info,  Node: Trigger Actions,  Next: Viewing Tracked Messages in 
*Summary* Buffers,  Prev: Email-Related Commands,  Up: Email Tracking
+
+4.2 Trigger Actions
+===================
+
+After calling ‘gnorb-gnus-incoming-do-todo’ on a message, or after
+sending a message associated with an Org heading, you’ll be taken to the
+heading and asked to “trigger an action” on it.  At the moment there are
+four different possibilities: triggering a TODO state-change on the
+heading, taking a note on the heading (both these options will associate
+the message with the heading), associating the message but doing nothing
+else, and lastly, doing nothing at all.
+
+   More actions will be added in the future; it’s also possible to add
+your own action: see the docstring of ‘gnorb-org-trigger-actions’.
+
+
+File: gnorb.info,  Node: Viewing Tracked Messages in *Summary* Buffers,  Next: 
Hinting in Gnus,  Prev: Trigger Actions,  Up: Email Tracking
+
+4.3 Viewing Tracked Messages in *Summary* Buffers
+=================================================
+
+Call ‘gnorb-org-view’ on an Org heading to open an nnir *Summary* buffer
+showing all the messages associated with that heading (this requires
+that you’ve added an nngnorb server to your Gnus backends).  A minor
+mode will be in effect, ensuring that any replies you send to messages
+in this buffer will automatically be associated with the original Org
+heading.  You can also invoke ‘gnorb-summary-disassociate-message’ (“C-c
+d”) to disassociate the message with the Org heading.
+
+   As a bonus, it’s possible to go into Gnus’ *Server* buffer, find the
+line specifying your nngnorb server, and hit “G” (aka
+‘gnus-group-make-nnir-group’).  At the query prompt, enter an Org-style
+tags-todo Agenda query string (eg “+work-computer”, or what have you).
+Gnorb will find all headings matching this query, scan their subtrees
+for gnus links, and then give you a Summary buffer containing all the
+linked messages.  This is dog-slow at the moment; it will get faster.
+
+
+File: gnorb.info,  Node: Hinting in Gnus,  Next: Message Attachments,  Prev: 
Viewing Tracked Messages in *Summary* Buffers,  Up: Email Tracking
+
+4.4 Hinting in Gnus
+===================
+
+When you receive new mails that might be relevant to existing Org TODOs,
+Gnorb can alert you to that fact.  When
+‘gnorb-gnus-hint-relevant-article’ is t (the default), Gnorb will
+display a message in the minibuffer when opening potentially relevant
+messages.  You can then use ‘gnorb-gnus-incoming-to-todo’ to trigger an
+action on the relevant TODO.
+
+   This hinting can happen in the Gnus summary buffer as well.  If you
+use the escape indicated by ‘gnorb-gnus-summary-mark-format-letter” as
+part of your ‘gnus-summary-line-format’, articles that are relevant to
+TODOs will be marked with a special character in the Summary buffer, as
+determined by ‘gnorb-gnus-summary-mark’.  By default, the format letter
+is “g” (meaning it is used as “%ug” in the format line), and the mark is
+“¡”.
+
+
+File: gnorb.info,  Node: Message Attachments,  Prev: Hinting in Gnus,  Up: 
Email Tracking
+
+4.5 Message Attachments
+=======================
+
+Gnorb simplifies the handling of attachments that you receive in emails.
+When you call ‘gnorb-gnus-incoming-do-todo’ on a message, you’ll be
+prompted to re-attach the email’s attachments onto the Org heading,
+using the org-attach library.
+
+   You can also do this as part of the capture process.  Set the new
+:gnus-attachments key to “t” in a capture template that you use on mail
+messages, and you’ll be queried to re-attach the message’s attachments
+onto the newly-captured heading.  Or set
+‘gnorb-gnus-capture-always-attach’ to “t” to have Gnorb do this for all
+capture templates.
+
+   You can also do this using the regular system of MIME commands,
+without invoking the email tracking process.  See *note Suggested
+Keybindings: Suggested Keybindings, below.
+
+   The same process works in reverse: when you send a message from an
+Org heading using ‘gnorb-org-handle-mail’, Gnorb will ask if you want to
+attach the files in the heading’s org-attach directory to the outgoing
+message.
+
+
+File: gnorb.info,  Node: Restoring Window Layout,  Next: Recent Mails From 
BBDB Contacts,  Prev: Email Tracking,  Up: Top
+
+5 Restoring Window Layout
+*************************
+
+Many Gnorb functions alter the window layout and value of point.  In
+most of these cases, you can restore the previous layout using the
+interactive function ‘gnorb-restore-layout’.
+
+
+File: gnorb.info,  Node: Recent Mails From BBDB Contacts,  Next: BBDB posting 
styles,  Prev: Restoring Window Layout,  Up: Top
+
+6 Recent Mails From BBDB Contacts
+*********************************
+
+If you’re using a recent git version of BBDB (circa mid-May 2014 or
+later), you can give your BBDB contacts a special field which will
+collect links to recent emails from that contact.  The default name of
+the field is “messages”, but you can customize that name using the
+‘gnorb-bbdb-messages-field’ option.
+
+   Gnorb will not collect links by default: you need to call
+‘gnorb-bbdb-open-link’ on a contact once to start the process.
+Thereafter, opening mails from that contact will store a link to the
+message.
+
+   Once some links are stored, ‘gnorb-bbdb-open-link’ will open them:
+Use a prefix arg to the function call to select particular messages to
+open.  There are several options controlling how all this works; see the
+gnorb-bbdb user options section below for details.
+
+
+File: gnorb.info,  Node: BBDB posting styles,  Next: BBDB Org tagging,  Prev: 
Recent Mails From BBDB Contacts,  Up: Top
+
+7 BBDB posting styles
+*********************
+
+Gnorb comes with a BBDB posting-style system, inspired by (copied from)
+gnus-posting-styles.  You can specify how messages are composed to
+specific contacts, by matching on contact field values (the same way
+gnus-posting-styles matches on group names).  See the docstring of
+‘gnorb-bbdb-posting-styles’ for details.
+
+   In order not to be too intrusive, Gnorb doesn’t alter the behavior of
+‘bbdb-mail’, the usual mail-composition function.  Instead it provides
+an alternate ‘gnorb-bbdb-mail’, which does exactly the same thing, but
+first processes the new mail according to ‘gnorb-bbdb-posting-styles’.
+If you want to use this feature regularly, you can remap ‘bbdb-mail’ to
+‘gnorb-bbdb-mail’ in the ‘bbdb-mode-map’.
+
+
+File: gnorb.info,  Node: BBDB Org tagging,  Next: Misc BBDB,  Prev: BBDB 
posting styles,  Up: Top
+
+8 BBDB Org tagging
+******************
+
+BBDB contacts can be tagged with the same tags you use in your Org
+files.  This allows you to pop up a *BBDB* buffer alongside your Org
+Agenda when searching for certain tags.  This can happen automatically
+for all Org tags-todo searches, if you set the option
+‘gnorb-org-agenda-popup-bbdb’ to t.  Or you can do it manually, by
+calling the command of the same name.  This command only shows TODOs by
+default: use a prefix argument to show all tagged headings.
+
+   Tags are stored in an xfield named org-tags, by default.  You can
+customize the name of this field using ‘gnorb-bbdb-org-tag-field’.
+
+
+File: gnorb.info,  Node: Misc BBDB,  Next: Misc Org,  Prev: BBDB Org tagging,  
Up: Top
+
+9 Misc BBDB
+***********
+
+* Menu:
+
+* Searching for messages from BBDB contacts::
+* Citing BBDB contacts::
+* User Options::
+
+
+File: gnorb.info,  Node: Searching for messages from BBDB contacts,  Next: 
Citing BBDB contacts,  Up: Misc BBDB
+
+9.1 Searching for messages from BBDB contacts
+=============================================
+
+Call ‘gnorb-bbdb-mail-search’ to search for all mail messages from the
+record(s) displayed.  Currently supports the notmuch, mairix, and namazu
+search backends; set ‘gnorb-gnus-mail-search-backend’ to one of those
+symbol values.
+
+
+File: gnorb.info,  Node: Citing BBDB contacts,  Next: User Options,  Prev: 
Searching for messages from BBDB contacts,  Up: Misc BBDB
+
+9.2 Citing BBDB contacts
+========================
+
+Calling ‘gnorb-bbdb-cite-contact’ will prompt for a BBDB record and
+insert a string of the type “Bob Smith <address@hidden>”.
+
+
+File: gnorb.info,  Node: User Options,  Prev: Citing BBDB contacts,  Up: Misc 
BBDB
+
+9.3 User Options
+================
+
+‘`gnorb-bbdb-org-tag-field’
+     The name of the BBDB xfield, as a symbol, that holds Org-related
+     tags.  Specified as a string with the “:” separator between tags,
+     same as for Org headings.  Defaults to org-tag.
+‘`gnorb-bbdb-messages-field'’
+     The name of the BBDB xfield that holds links to recently-received
+     messages from this contact.  Defaults to ‘messages.
+‘`gnorb-bbdb-collect-N-messages'’
+     Collect at most this many links to messages from this contact.
+     Defaults to 5.
+‘`gnorb-bbdb-define-recent'’
+     What does “recently-received” mean?  Possible values are the
+     symbols seen and received.  When set to seen, the most
+     recently-opened messages are collected.  When set to received, the
+     most recently-received (by Date header) messages are collected.
+     Defaults to seen.
+‘`gnorb-bbdb-message-link-format-multi'’
+     How is a single message’s link formatted in the multi-line BBDB
+     layout format?  Defaults to “%:count.  %D: %:subject” (see the
+     docstring for details).
+‘` gnorb-bbdb-message-link-format-one'’
+     How is a single message’s link formatted in the one-line BBDB
+     layout format?  Defaults to nil (see the docstring for details).
+‘`gnorb-bbdb-posting-styles'’
+     Styles to use for influencing the format of mails composed to the
+     BBDB record(s) under point (see the docstring for details).
+
+
+File: gnorb.info,  Node: Misc Org,  Next: Misc Gnus,  Prev: Misc BBDB,  Up: Top
+
+10 Misc Org
+***********
+
+* Menu:
+
+* Inserting BBDB links::
+* User Options: User Optionsx. 
+
+
+File: gnorb.info,  Node: Inserting BBDB links,  Next: User Optionsx,  Up: Misc 
Org
+
+10.1 Inserting BBDB links
+=========================
+
+Calling ‘gnorb-org-contact-link’ will prompt for a BBDB record and
+insert an Org link to that record at point.
+
+
+File: gnorb.info,  Node: User Optionsx,  Prev: Inserting BBDB links,  Up: Misc 
Org
+
+10.2 User Options
+=================
+
+‘`gnorb-org-after-message-setup-hook'’
+     Hook run in a message buffer after setting up the message, from
+     ‘gnorb-org-handle-mail’ or ‘gnorb-org-email-subtree’.
+‘`gnorb-org-trigger-actions'’
+     List of potential actions that can be taken on headings after a
+     message is sent.  See docstring for details.
+‘`gnorb-org-mail-scan-scope'’
+     The number of paragraphs to scan for mail-related links.  This
+     comes into play when calling ‘gnorb-org-handle-mail’ on a heading
+     with no associated messages, or when ‘gnorb-org-handle-mail’ is
+     called with a prefix arg.
+‘`gnorb-org-find-candidates-match'’
+     When searching all Org files for headings to collect messages from,
+     this option can limit which headings are searched.  It is used as
+     the second argument to a call to ‘org-map-entries’, and has the
+     same syntax as that used in an agenda tags view.
+‘`gnorb-org-email-subtree-text-parameters'’
+     A plist of export parameters corresponding to the EXT-PLIST
+     argument to the export functions, for use when exporting to text.
+‘`gnorb-org-email-subtree-file-parameters'’
+     A plist of export parameters corresponding to the EXT-PLIST
+     argument to the export functions, for use when exporting to a file.
+‘`gnorb-org-email-subtree-text-options'’
+     A list of ts and nils corresponding to Org’s export options, to be
+     used when exporting to text.  The options, in order, are async,
+     subtreep, visible-only, and body-only.
+‘`gnorb-org-email-subtree-file-options'’
+     A list of ts and nils corresponding to Org’s export options, to be
+     used when exporting to a file.  The options, in order, are async,
+     subtreep, visible-only, and body-only.
+‘`gnorb-org-export-extensions'’
+     Correspondence between export backends and their respective (usual)
+     file extensions.
+‘`gnorb-org-capture-collect-link-p'’
+     When this is set to t, the capture process will always store a link
+     to the Gnus message or BBDB record under point, even when the link
+     isn’t part of the capture template.  It can then be added to the
+     captured heading with org-insert-link, as usual.
+‘`gnorb-org-agenda-popup-bbdb'’
+     Set to “t” to automatically pop up the BBDB buffer displaying
+     records corresponding to the Org Agenda tags search underway.  If
+     this is nil you can always do it manually with the command of the
+     same name.
+‘`gnorb-org-bbdb-popup-layout'’
+     Controls the layout of the Agenda-related BBDB popup, takes the
+     same values as bbdb-pop-up-layout.
+
+
+File: gnorb.info,  Node: Misc Gnus,  Next: Suggested Keybindings,  Prev: Misc 
Org,  Up: Top
+
+11 Misc Gnus
+************
+
+* Menu:
+
+* Viewing Org headlines relevant to a message::
+* User Options: User Optionsxx. 
+
+
+File: gnorb.info,  Node: Viewing Org headlines relevant to a message,  Next: 
User Optionsxx,  Up: Misc Gnus
+
+11.1 Viewing Org headlines relevant to a message
+================================================
+
+Call ‘gnorb-gnus-view’ on a message that is associated with an Org
+heading to jump to that heading.
+
+
+File: gnorb.info,  Node: User Optionsxx,  Prev: Viewing Org headlines relevant 
to a message,  Up: Misc Gnus
+
+11.2 User Options
+=================
+
+‘`gnorb-gnus-mail-search-backend'’
+     Specifies the search backend that you use for searching mails.
+     Currently supports notmuch, mairix, and namazu: set this option to
+     one of those symbols.
+‘`gnorb-gnus-capture-always-attach'’
+     Treat all capture templates as if they had the :gnus-attachments
+     key set to “t”.  This only has any effect if you’re capturing from
+     a Gnus summary or article buffer.
+‘`gnorb-trigger-todo-default'’
+     Set to either ‘note or ‘todo to tell ‘gnorb-gnus-incoming-do-todo’
+     what to do by default.  You can reach the non-default behavior by
+     calling that function with a prefix argument.  Alternately, set to
+     ‘prompt to always prompt for the appropriate action.
+‘`gnorb-gnus-trigger-refile-targets'’
+     If you use ‘gnorb-gnus-incoming-do-todo’ on an incoming message,
+     Gnorb will try to locate a TODO heading that’s relevant to that
+     message.  If it can’t, it will prompt you for one, using the refile
+     interface.  This option will be used as the value of
+     ‘org-refile-targets’ during that process: see the docstring of
+     ‘org-refile-targets’ for the appropriate syntax.
+‘`gnorb-gnus-new-todo-capture-key'’
+     Set this to a single-character string pointing at an Org capture
+     template to use when creating TODOs from outgoing messages.  The
+     template is a regular capture template, with a few exceptions.  If
+     Gnus helps you archive outgoing messages (ie you have
+     ‘gnus-message-archive-group’ set to something, and your outgoing
+     messages have a “Fcc” header), a link to that message will be made,
+     and you’ll be able to use all the escapes related to gnus messages.
+     If you don’t archive outgoing messages, you’ll still be able to use
+     the %:subject, %:to, %:toname, %:toaddress, and %:date escapes in
+     the capture template.
+‘`gnorb-gnus-hint-relevant-article'’
+     Set to “t” (the default) to have Gnorb give you a hint in the
+     minibuffer when opening messages that might be relevant to existing
+     Org TODOs.
+‘`gnorb-gnus-summary-mark-format-letter'’
+     The formatting letter to use as part of your
+     ‘gnus-summary-line-format’, to indicate messages which might be
+     relevant to Org TODOs.  Defaults to “g”, meaning it should be used
+     as “%ug” in the format line.
+‘`gnorb-gnus-summary-mark'’
+     The mark used to indicate relevant messages in the Summary buffer,
+     when ‘gnorb-gnus-summary-mark-format-letter’ is present in the
+     format line.  Defaults to “¡”.
+
+
+File: gnorb.info,  Node: Suggested Keybindings,  Next: Wishlist/TODO,  Prev: 
Misc Gnus,  Up: Top
+
+12 Suggested Keybindings
+************************
+
+     (eval-after-load "gnorb-bbdb"
+       '(progn
+          (define-key bbdb-mode-map (kbd "O") 'gnorb-bbdb-tag-agenda)
+          (define-key bbdb-mode-map (kbd "S") 'gnorb-bbdb-mail-search)
+          (define-key bbdb-mode-map [remap bbdb-mail] 'gnorb-bbdb-mail)
+          (define-key bbdb-mode-map (kbd "l") 'gnorb-bbdb-open-link)
+          (global-set-key (kbd "C-c C") 'gnorb-bbdb-cite-contact)))
+
+     (eval-after-load "gnorb-org"
+       '(progn
+          (org-defkey org-mode-map (kbd "C-c C") 'gnorb-org-contact-link)
+          (org-defkey org-mode-map (kbd "C-c t") 'gnorb-org-handle-mail)
+          (org-defkey org-mode-map (kbd "C-c e") 'gnorb-org-view)
+          (org-defkey org-mode-map (kbd "C-c E") 'gnorb-org-email-subtree)
+          (org-defkey org-mode-map (kbd "C-c V") 'gnorb-org-popup-bbdb)
+          (setq gnorb-org-agenda-popup-bbdb t)
+          (eval-after-load "org-agenda"
+            '(progn (org-defkey org-agenda-mode-map (kbd "H") 
'gnorb-org-handle-mail)
+                    (org-defkey org-agenda-mode-map (kbd "V") 
'gnorb-org-popup-bbdb)))))
+
+     (eval-after-load "gnorb-gnus"
+       '(progn
+          (define-key gnus-summary-mime-map "a" 'gnorb-gnus-article-org-attach)
+          (define-key gnus-summary-mode-map (kbd "C-c t") 
'gnorb-gnus-incoming-do-todo)
+          (push '("attach to org heading" . gnorb-gnus-mime-org-attach)
+                gnus-mime-action-alist)
+          ;; The only way to add mime button command keys is by redefining
+          ;; gnus-mime-button-map, possibly not ideal. Ideal would be a
+          ;; setter function in gnus itself.
+          (push '(gnorb-gnus-mime-org-attach "a" "Attach to Org heading")
+                gnus-mime-button-commands)
+          (setq gnus-mime-button-map
+                (let ((map (make-sparse-keymap)))
+                  (define-key map gnus-mouse-2 'gnus-article-push-button)
+                  (define-key map gnus-down-mouse-3 'gnus-mime-button-menu)
+                  (dolist (c gnus-mime-button-commands)
+                    (define-key map (cadr c) (car c)))
+                  map))))
+
+     (eval-after-load "message"
+       '(progn
+          (define-key message-mode-map (kbd "C-c t") 
'gnorb-gnus-outgoing-do-todo)))
+
+
+File: gnorb.info,  Node: Wishlist/TODO,  Next: Index,  Prev: Suggested 
Keybindings,  Up: Top
+
+13 Wishlist/TODO
+****************
+
+   • Provide a command that, when in the Org Agenda, does an email
+     search for messages received in the visible date span, or day under
+     point, etc.  Make it work in the calendar, as well?
+   • Add trigger actions that create new sibling or child headings on
+     the original Org heading.
+   • Allow tagging of Gnus messages, by giving the message’s registry
+     entry an ‘org-tags key.
+   • Provide persistent nngnorb search groups.
+   • Allow automatic org-tagging of BBDB contacts: when messages from a
+     contact are associated with an Org heading, make it possible for
+     the contact to inherit that heading’s tags automatically.
+   • Provide completion when setting Org tags on a BBDB contact.
+   • Provide a ‘gnorb-bbdb-view’ command that opens a *Summary* buffer
+     containing all the tracked messages from the contact(s) under
+     point.
+   • Provide a ‘gnorb-view’ command that takes a tags-todo search phrase
+     (or a single Org heading ID), finds all relevant messages, Org
+     headings, and BBDB records, and sets up a four-pane view: Org
+     Agenda, **Article* SummaryBBDB* buffer, Gnus *buffer, and an *
+     buffer.
+
+
+File: gnorb.info,  Node: Index,  Prev: Wishlist/TODO,  Up: Top
+
+14 Index
+********
+
+
+
+Tag Table:
+Node: Top194
+Node: Introduction1017
+Node: Installation2126
+Node: Setup2540
+Node: Email Tracking3907
+Node: Email-Related Commands5418
+Node: Trigger Actions8239
+Node: Viewing Tracked Messages in *Summary* Buffers9053
+Node: Hinting in Gnus10287
+Node: Message Attachments11295
+Node: Restoring Window Layout12453
+Node: Recent Mails From BBDB Contacts12817
+Node: BBDB posting styles13813
+Node: BBDB Org tagging14729
+Node: Misc BBDB15475
+Node: Searching for messages from BBDB contacts15688
+Node: Citing BBDB contacts16134
+Node: User Options16455
+Node: Misc Org17994
+Node: Inserting BBDB links18169
+Node: User Optionsx18424
+Node: Misc Gnus21161
+Node: Viewing Org headlines relevant to a message21374
+Node: User Optionsxx21689
+Node: Suggested Keybindings24453
+Node: Wishlist/TODO26824
+Node: Index28139
+
+End Tag Table
+
+
+Local Variables:
+coding: utf-8
+End:
diff --git a/gnorb.org b/gnorb.org
new file mode 100644
index 0000000..b03098b
--- /dev/null
+++ b/gnorb.org
@@ -0,0 +1,507 @@
+#+TEXINFO_CLASS: info
+#+TEXINFO_HEADER: @syncodeindex pg cp
+#+TITLE: Gnorb Manual
+#+SUBTITLE: for version 1, updated 3 October, 2014
+#+TEXINFO_DIR_CATEGORY: Emacs
+#+TEXINFO_DIR_TITLE: Gnorb: (gnorb)
+#+TEXINFO_DIR_DESC: Glue code for Gnus, Org, and BBDB
+#+OPTIONS: *:nil num:t toc:nil
+* Introduction
+
+Gnorb provides glue code between the Gnus, Org, and BBDB packages.
+It's aimed at supporting email-based project management, and generally
+making it easier to keep track of email communication.
+
+Much of the code consists of single-use convenience functions, but
+tracking email conversations with Org requires is more complicated,
+and requires a bit of setup.
+
+Gnorb can be used in a modular fashion, by selectively loading the
+files "gnorb-org", "gnorb-gnus" or "gnorb-bbdb" instead of plain old
+"gnorb". The package as a whole is rather Org-centric, though, and it
+won't do much of interest without "gnorb-org".
+
+This means that Gnorb doesn't have hard requirements to any of the
+three base libraries. For the libraries you are using, however, you'll
+get best results from using the most recent stable version (yes, that
+means BBDB 3). Some of the features in Gnorb only work with
+development versions of these libraries (those cases are noted below).
+* Installation
+Gnorb is best installed via the Elpa package manager -- look for it in
+`list-packages'.
+
+You can also clone the source code from
+https://github.com/girzel/gnorb, and put the "gnorb" directory on your
+load-path. The Github site is also a good place to report bugs and
+other issues.
+* Setup
+Loading "gnorb" will make the basic functions available. Using Gnorb
+for email tracking takes a bit more setup, however:
+
+1. Email tracking is done via the Gnus registry, so that must be
+   activated with 'gnus-registry-initialize'.
+2. It also requires the org-id package to be loaded, and
+   `org-id-track-globally' set to t (that's the default value, so
+   simply loading the package should be enough).
+3. Add a nngnorb entry to your `gnus-secondary-select-methods'
+   variable. It will look like (nngnorb "Server name"). This does
+   nothing but provide a place to hang nnir searches.
+4. Then put a call to `gnorb-tracking-initialize' in your init files,
+   at some point after the Gnus registry is initialized.
+5. If you're not using a local archive method for saving your sent
+   messages (ie you're using IMAP), you'll also need to tell Gnorb
+   where to find your sent messages. Set the variable
+   `gnorb-gnus-sent-groups' to a list of strings; each string should
+   indicate a fully-qualified group name, eg "nnimap+SERVER:GROUP".
+
+Lastly, Gnorb doesn't bind any keys by default; see the 
[[id:de1b2579-86c2-4bb1-b77e-3467a3d2b3c7][Suggested
+Keybindings]] section below for possibilities.
+* Email Tracking
+The most interesting thing Gnorb does is using Org headings to track
+email conversations. This can mean anything from reminding yourself to
+write to your mother, to conducting delicate business negotiations
+over email, to running an email-based bug tracker.
+
+Gnorb assists in this process by using the Gnus registry to track
+correspondences between emails and Org headings -- specifically,
+message IDs are associated with Org heading ids. As a conversation
+develops, messages are collected on a heading (and/or its children).
+You can compose new messages directly from the Org heading, and Gnorb
+will automatically associate your sent message with the conversation.
+You can open temporary Gnus *Summary* buffers holding all the messages
+associated with an Org subtree, and reply from there. When you receive
+new messages relevant to a conversation, Gnorb will notice them and
+prompt you to associate them with the appropriate Org heading.
+Attachments on incoming messages can be automatically saved as
+attachments on Org headings, using org-attach.
+
+In general, the goal is to keep track of whole conversations, reduce
+friction when moving between Gnus and Org, and keep you in the Org
+agenda rather than in Gnus.
+** Email-Related Commands
+Email tracking starts in one of three ways:
+
+1. With an Org heading that represents an email TODO. Call
+   `gnorb-org-handle-mail' (see below) on the heading to compose a new
+   message, and start the tracking process.
+2. By calling org-capture on a received message. Any heading captured
+   from a message will automatically be associated with that message.
+3. By calling `gnorb-gnus-outgoing-do-todo' in a message composition
+   buffer -- see below.
+
+There are three main email-related commands:
+
+1. `gnorb-org-handle-mail' is called on an Org heading to compose a
+   new message. By default, this will begin a reply to the most recent
+   message in the conversation. If there are no associated messages to
+   reply to (or you call the function with a double prefix arg), Gnorb
+   will look for mailto: or bbdb: links in the heading, and compose a
+   new message to them.
+   
+   The sent message will be associated with the Org heading, and
+   you'll be brought back to the heading and asked to trigger an
+   action on it.
+   
+   `gnorb-email-subtree' is an alternative entry-point to
+   `gnorb-org-handle-mail'. It does the same thing as the latter, but
+   first exports the body of the subtree as either text or a file,
+   then inserts the text into the message body, or attaches the file
+   to the message, depending on what you've chosen.
+2. `gnorb-gnus-incoming-do-todo' is called on a message in a Gnus
+   *Summary* buffer. You'll be prompted for an Org heading, taken to
+   that heading, and asked to trigger an action on it.
+3. `gnorb-gnus-outgoing-do-todo' is called in message mode, while
+   composing a new message. 
+    
+   If called without a prefix arg, a new Org heading will be created
+   after the message is sent, and the sent message associated with it.
+   The new heading will be created as a capture heading, using the
+   template specified by the `gnorb-gnus-new-todo-capture-key' option.
+   
+   If you call this function with a prefix arg, you'll be prompted to
+   choose an existing Org heading instead. After the the message is
+   sent, you'll be taken to that heading and prompted to trigger an
+   action on it.
+   
+   It's also possible to call this function *after* a message is sent,
+   in case you forgot. Gnorb saves information about the most recently
+   sent message for this purpose.
+
+Because these three commands all express a similar intent, but are
+called in different modes, it can make sense to give each of them the
+same keybinding in the keymaps for Org mode, Gnus summary mode, and
+Message mode, respectively.
+** Trigger Actions
+After calling `gnorb-gnus-incoming-do-todo' on a message, or after
+sending a message associated with an Org heading, you'll be taken to
+the heading and asked to "trigger an action" on it. At the moment
+there are four different possibilities: triggering a TODO state-change
+on the heading, taking a note on the heading (both these options will
+associate the message with the heading), associating the message but
+doing nothing else, and lastly, doing nothing at all.
+
+More actions will be added in the future; it's also possible to
+rearrange or delete existing actions, and add your own: see the
+docstring of `gnorb-org-trigger-actions'.
+** Viewing Tracked Messages in *Summary* Buffers
+:PROPERTIES:
+:END:
+Call `gnorb-org-view' on an Org heading to open an nnir *Summary*
+buffer showing all the messages associated with that heading (this
+requires that you've added an nngnorb server to your Gnus backends). A
+minor mode will be in effect, ensuring that any replies you send to
+messages in this buffer will automatically be associated with the
+original Org heading. You can also invoke
+`gnorb-summary-disassociate-message' ("C-c d") to disassociate the
+message with the Org heading.
+
+As a bonus, it's possible to go into Gnus' *Server* buffer, find the
+line specifying your nngnorb server, and hit "G" (aka
+`gnus-group-make-nnir-group'). At the query prompt, enter an Org-style
+tags-todo Agenda query string (eg "+work-computer", or what have you).
+Gnorb will find all headings matching this query, scan their subtrees
+for gnus links, and then give you a Summary buffer containing all the
+linked messages. This is dog-slow at the moment; it will get faster.
+
+** Hinting in Gnus
+:PROPERTIES:
+:END:
+When you receive new mails that might be relevant to existing Org
+TODOs, Gnorb can alert you to that fact. When
+`gnorb-gnus-hint-relevant-article' is t (the default), Gnorb will
+display a message in the minibuffer when opening potentially relevant
+messages. You can then use `gnorb-gnus-incoming-to-todo' to trigger an
+action on the relevant TODO.
+
+This hinting can happen in the Gnus summary buffer as well. If you use
+the escape indicated by `gnorb-gnus-summary-mark-format-letter" as
+part of your `gnus-summary-line-format', articles that are relevant to
+TODOs will be marked with a special character in the Summary buffer,
+as determined by `gnorb-gnus-summary-mark'. By default, the format
+letter is "g" (meaning it is used as "%ug" in the format line), and
+the mark is "¡".
+** Message Attachments
+:PROPERTIES:
+:END:
+Gnorb simplifies the handling of attachments that you receive in
+emails. When you call `gnorb-gnus-incoming-do-todo' on a message,
+you'll be prompted to re-attach the email's attachments onto the Org
+heading, using the org-attach library.
+
+You can also do this as part of the capture process. Set the
+new :gnus-attachments key to "t" in a capture template that you use on
+mail messages, and you'll be queried to re-attach the message's
+attachments onto the newly-captured heading. Or set
+`gnorb-gnus-capture-always-attach' to "t" to have Gnorb do this for
+all capture templates.
+
+You can also do this using the regular system of MIME commands,
+without invoking the email tracking process. See 
[[id:de1b2579-86c2-4bb1-b77e-3467a3d2b3c7][Suggested
+Keybindings]], below.
+
+The same process works in reverse: when you send a message from an Org
+heading using `gnorb-org-handle-mail', Gnorb will ask if you want to
+attach the files in the heading's org-attach directory to the outgoing
+message.
+** Likely Workflow
+You receive an email from Jimmy, who wants to rent a room in your
+house. "I'll respond to this later," you think.
+
+You capture an Org TODO from the email, call it "Jimmy renting a
+room", and give it a REPLY keyword. Gnorb quietly records the
+correspondence between the email and the TODO, using the Gnus
+registry.
+
+The next day, looking at your Agenda, you see the TODO and decide to
+respond to the email. You call `gnorb-org-handle-mail' on the heading,
+and Gnorb opens Jimmy's email and starts a reply to it.
+
+You tell Jimmy the room's available in March, and send the message.
+Gnorb takes you back to the heading, and asks you to trigger an action
+on it. You choose "todo state", and change the heading keyword to
+WAIT.
+
+Two days later, Jimmy replies to your message, saying that March is
+perfect. When you open his response, Gnorb politely reminds you that
+the message is relevant to an existing TODO. You call
+`gnorb-gnus-incoming-do-todo' on the message, and are again taken to
+the TODO and asked to trigger an action. Again you choose "todo
+state", and change the heading keyword back to REPLY.
+
+You get another email, from Samantha, warning you not to rent the room
+to Jimmy. She even attaches a picture of a room in her house, as it
+looked after Jimmy had stayed there for six months. It's bad. You call
+`gnorb-gnus-incoming-do-todo' on her message, and pick the "Jimmy
+renting a room" heading. This time, you choose "take note" as the
+trigger action, and make a brief note about how bad that room looked.
+Gnorb asks if you'd like to attach the picture to the Org heading. You
+decide you will.
+
+Now it's time to write to Jimmy and say something noncommittal.
+Calling `gnorb-org-handle-mail' on the heading would respond to
+Samantha's email, the most recent of the associated messages, which
+isn't what you want. Instead you call `gnorb-org-view' on the heading,
+which opens up a Gnus *Summary* buffer containing all four messages:
+Jimmy's first, your response, his response to that, and Samantha's
+message. You pick Jimmy's second email, and reply to it normally.
+Gnorb asks if you'd like to send the picture of the room as an
+attachment. You would not. When you send the reply Gnorb tracks that
+as well, and does the "trigger an action" trick again.
+
+In this way Gnorb helps you manage an entire conversation, possibly
+with multiple threads and multiple participants. Mostly all you need
+to do is call `gnorb-gnus-incoming-do-todo' on newly-received
+messages, and `gnorb-org-handle-mail' on the heading when it's time to
+compose a new reply.
+* Restoring Window Layout
+Many Gnorb functions alter the window layout and value of point. In
+most of these cases, you can restore the previous layout using the
+interactive function `gnorb-restore-layout'.
+
+* Recent Mails From BBDB Contacts
+:PROPERTIES:
+:END:
+If you're using a recent git version of BBDB (circa mid-May 2014 or
+later), you can give your BBDB contacts a special field which will
+collect links to recent emails from that contact. The default name of
+the field is "messages", but you can customize that name using the
+`gnorb-bbdb-messages-field' option.
+
+Gnorb will not collect links by default: you need to call
+`gnorb-bbdb-open-link' on a contact once to start the process.
+Thereafter, opening mails from that contact will store a link to the
+message.
+
+Once some links are stored, `gnorb-bbdb-open-link' will open them: Use
+a prefix arg to the function call to select particular messages to
+open. There are several options controlling how all this works; see
+the gnorb-bbdb user options section below for details.
+* BBDB posting styles
+:PROPERTIES:
+:END:
+Gnorb comes with a BBDB posting-style system, inspired by (copied
+from) gnus-posting-styles. You can specify how messages are composed
+to specific contacts, by matching on contact field values (the same
+way gnus-posting-styles matches on group names). See the docstring of
+`gnorb-bbdb-posting-styles' for details.
+
+In order not to be too intrusive, Gnorb doesn't alter the behavior of
+`bbdb-mail', the usual mail-composition function. Instead it provides
+an alternate `gnorb-bbdb-mail', which does exactly the same thing, but
+first processes the new mail according to `gnorb-bbdb-posting-styles'.
+If you want to use this feature regularly, you can remap `bbdb-mail'
+to `gnorb-bbdb-mail' in the `bbdb-mode-map'.
+* BBDB Org tagging
+BBDB contacts can be tagged with the same tags you use in your Org
+files. This allows you to pop up a *BBDB* buffer alongside your Org
+Agenda when searching for certain tags. This can happen automatically
+for all Org tags-todo searches, if you set the option
+`gnorb-org-agenda-popup-bbdb' to t. Or you can do it manually, by
+calling the command of the same name. This command only shows TODOs by
+default: use a prefix argument to show all tagged headings.
+
+Tags are stored in an xfield named org-tags, by default. You can
+customize the name of this field using `gnorb-bbdb-org-tag-field'.
+* Misc BBDB
+** Searching for messages from BBDB contacts
+:PROPERTIES:
+:END:
+Call `gnorb-bbdb-mail-search' to search for all mail messages from the
+record(s) displayed. Currently supports the notmuch, mairix, and
+namazu search backends; set `gnorb-gnus-mail-search-backend' to one of
+those symbol values.
+** Citing BBDB contacts
+:PROPERTIES:
+:END:
+Calling `gnorb-bbdb-cite-contact' will prompt for a BBDB record and
+insert a string of the type "Bob Smith <address@hidden>".
+** User Options
+- `gnorb-bbdb-org-tag-field :: The name of the BBDB xfield, as a
+     symbol, that holds Org-related tags. Specified as a string with
+     the ":" separator between tags, same as for Org headings.
+     Defaults to org-tag.
+- `gnorb-bbdb-messages-field' :: The name of the BBDB xfield that
+     holds links to recently-received messages from this contact.
+     Defaults to 'messages.
+- `gnorb-bbdb-collect-N-messages' :: Collect at most this many links
+     to messages from this contact. Defaults to 5.
+- `gnorb-bbdb-define-recent' :: What does "recently-received" mean?
+     Possible values are the symbols seen and received. When set to
+     seen, the most recently-opened messages are collected. When set
+     to received, the most recently-received (by Date header) messages
+     are collected. Defaults to seen.
+- `gnorb-bbdb-message-link-format-multi' :: How is a single message's
+     link formatted in the multi-line BBDB layout format? Defaults to
+     "%:count. %D: %:subject" (see the docstring for details).
+- ` gnorb-bbdb-message-link-format-one' :: How is a single message's
+     link formatted in the one-line BBDB layout format? Defaults to
+     nil (see the docstring for details).
+- `gnorb-bbdb-posting-styles' :: Styles to use for influencing the
+     format of mails composed to the BBDB record(s) under point (see
+     the docstring for details).
+* Misc Org
+** Inserting BBDB links
+:PROPERTIES:
+:END:
+Calling `gnorb-org-contact-link' will prompt for a BBDB record and
+insert an Org link to that record at point.
+** User Options
+- `gnorb-org-after-message-setup-hook' :: Hook run in a message buffer
+     after setting up the message, from `gnorb-org-handle-mail' or
+     `gnorb-org-email-subtree'.
+- `gnorb-org-trigger-actions' :: List of potential actions that can be
+     taken on headings after a message is sent. See docstring for
+     details.
+- `gnorb-org-mail-scan-scope' :: The number of paragraphs to scan for
+     mail-related links. This comes into play when calling
+     `gnorb-org-handle-mail' on a heading with no associated messages,
+     or when `gnorb-org-handle-mail' is called with a prefix arg.
+- `gnorb-org-find-candidates-match' :: When searching all Org files
+     for headings to collect messages from, this option can limit
+     which headings are searched. It is used as the second argument to
+     a call to `org-map-entries', and has the same syntax as that used
+     in an agenda tags view.
+- `gnorb-org-email-subtree-text-parameters' :: A plist of export
+     parameters corresponding to the EXT-PLIST argument to the export
+     functions, for use when exporting to text.
+- `gnorb-org-email-subtree-file-parameters' :: A plist of export
+     parameters corresponding to the EXT-PLIST argument to the export
+     functions, for use when exporting to a file.
+- `gnorb-org-email-subtree-text-options' :: A list of ts and nils
+     corresponding to Org's export options, to be used when exporting
+     to text. The options, in order, are async, subtreep,
+     visible-only, and body-only.
+- `gnorb-org-email-subtree-file-options' :: A list of ts and nils
+     corresponding to Org's export options, to be used when exporting
+     to a file. The options, in order, are async, subtreep,
+     visible-only, and body-only.
+- `gnorb-org-export-extensions' :: Correspondence between export
+     backends and their respective (usual) file extensions.
+- `gnorb-org-capture-collect-link-p' :: When this is set to t, the
+     capture process will always store a link to the Gnus message or
+     BBDB record under point, even when the link isn't part of the
+     capture template. It can then be added to the captured heading
+     with org-insert-link, as usual.
+- `gnorb-org-agenda-popup-bbdb' :: Set to "t" to automatically pop up
+     the BBDB buffer displaying records corresponding to the Org
+     Agenda tags search underway. If this is nil you can always do it
+     manually with the command of the same name.
+- `gnorb-org-bbdb-popup-layout' :: Controls the layout of the
+     Agenda-related BBDB popup, takes the same values as
+     bbdb-pop-up-layout.
+* Misc Gnus
+** Viewing Org headlines relevant to a message
+:PROPERTIES:
+:END:
+Call `gnorb-gnus-view' on a message that is associated with an Org
+heading to jump to that heading.
+** User Options
+- `gnorb-gnus-mail-search-backend' :: Specifies the search backend
+     that you use for searching mails. Currently supports notmuch,
+     mairix, and namazu: set this option to one of those symbols.
+- `gnorb-gnus-capture-always-attach' :: Treat all capture templates as
+     if they had the :gnus-attachments key set to "t". This only has
+     any effect if you're capturing from a Gnus summary or article
+     buffer.
+- `gnorb-trigger-todo-default' :: Set to either 'note or 'todo to tell
+     `gnorb-gnus-incoming-do-todo' what to do by default. You can
+     reach the non-default behavior by calling that function with a
+     prefix argument. Alternately, set to 'prompt to always prompt for
+     the appropriate action.
+- `gnorb-gnus-trigger-refile-targets' :: If you use
+     `gnorb-gnus-incoming-do-todo' on an incoming message, Gnorb will
+     try to locate a TODO heading that's relevant to that message. If
+     it can't, it will prompt you for one, using the refile interface.
+     This option will be used as the value of `org-refile-targets'
+     during that process: see the docstring of `org-refile-targets'
+     for the appropriate syntax.
+- `gnorb-gnus-new-todo-capture-key' :: Set this to a single-character
+     string pointing at an Org capture template to use when creating
+     TODOs from outgoing messages. The template is a regular capture
+     template, with a few exceptions. If Gnus helps you archive
+     outgoing messages (ie you have `gnus-message-archive-group' set
+     to something, and your outgoing messages have a "Fcc" header), a
+     link to that message will be made, and you'll be able to use all
+     the escapes related to gnus messages. If you don't archive
+     outgoing messages, you'll still be able to use the %:subject,
+     %:to, %:toname, %:toaddress, and %:date escapes in the capture
+     template.
+- `gnorb-gnus-hint-relevant-article' :: Set to "t" (the default) to
+     have Gnorb give you a hint in the minibuffer when opening
+     messages that might be relevant to existing Org TODOs.
+- `gnorb-gnus-summary-mark-format-letter' :: The formatting letter to
+     use as part of your `gnus-summary-line-format', to indicate
+     messages which might be relevant to Org TODOs. Defaults to "g",
+     meaning it should be used as "%ug" in the format line.
+- `gnorb-gnus-summary-mark' :: The mark used to indicate relevant
+     messages in the Summary buffer, when
+     `gnorb-gnus-summary-mark-format-letter' is present in the format
+     line. Defaults to "¡".
+* Suggested Keybindings
+:PROPERTIES:
+:ID:       de1b2579-86c2-4bb1-b77e-3467a3d2b3c7
+:END:
+#+BEGIN_SRC emacs-lisp
+  (eval-after-load "gnorb-bbdb"
+    '(progn
+       (define-key bbdb-mode-map (kbd "O") 'gnorb-bbdb-tag-agenda)
+       (define-key bbdb-mode-map (kbd "S") 'gnorb-bbdb-mail-search)
+       (define-key bbdb-mode-map [remap bbdb-mail] 'gnorb-bbdb-mail)
+       (define-key bbdb-mode-map (kbd "l") 'gnorb-bbdb-open-link)
+       (global-set-key (kbd "C-c C") 'gnorb-bbdb-cite-contact)))
+
+  (eval-after-load "gnorb-org"
+    '(progn
+       (org-defkey org-mode-map (kbd "C-c C") 'gnorb-org-contact-link)
+       (org-defkey org-mode-map (kbd "C-c t") 'gnorb-org-handle-mail)
+       (org-defkey org-mode-map (kbd "C-c e") 'gnorb-org-view)
+       (org-defkey org-mode-map (kbd "C-c E") 'gnorb-org-email-subtree)
+       (org-defkey org-mode-map (kbd "C-c V") 'gnorb-org-popup-bbdb)
+       (setq gnorb-org-agenda-popup-bbdb t)
+       (eval-after-load "org-agenda"
+         '(progn (org-defkey org-agenda-mode-map (kbd "H") 
'gnorb-org-handle-mail)
+                 (org-defkey org-agenda-mode-map (kbd "V") 
'gnorb-org-popup-bbdb)))))
+
+  (eval-after-load "gnorb-gnus"
+    '(progn
+       (define-key gnus-summary-mime-map "a" 'gnorb-gnus-article-org-attach)
+       (define-key gnus-summary-mode-map (kbd "C-c t") 
'gnorb-gnus-incoming-do-todo)
+       (push '("attach to org heading" . gnorb-gnus-mime-org-attach)
+             gnus-mime-action-alist)
+       ;; The only way to add mime button command keys is by redefining
+       ;; gnus-mime-button-map, possibly not ideal. Ideal would be a
+       ;; setter function in gnus itself.
+       (push '(gnorb-gnus-mime-org-attach "a" "Attach to Org heading")
+             gnus-mime-button-commands)
+       (setq gnus-mime-button-map
+             (let ((map (make-sparse-keymap)))
+               (define-key map gnus-mouse-2 'gnus-article-push-button)
+               (define-key map gnus-down-mouse-3 'gnus-mime-button-menu)
+               (dolist (c gnus-mime-button-commands)
+                 (define-key map (cadr c) (car c)))
+               map))))
+
+  (eval-after-load "message"
+    '(progn
+       (define-key message-mode-map (kbd "C-c t") 
'gnorb-gnus-outgoing-do-todo)))
+#+END_SRC
+* Wishlist/TODO
+- Provide a command that, when in the Org Agenda, does an email search
+  for messages received in the visible date span, or day under point,
+  etc. Make it work in the calendar, as well?
+- Add trigger actions that create new sibling or child headings on the
+  original Org heading.
+- Allow tagging of Gnus messages, by giving the message's registry
+  entry an 'org-tags key.
+- Provide persistent nngnorb search groups.
+- Allow automatic org-tagging of BBDB contacts: when messages from a
+  contact are associated with an Org heading, make it possible for the
+  contact to inherit that heading's tags automatically.
+- Provide completion when setting Org tags on a BBDB contact.
+- Provide a `gnorb-bbdb-view' command that opens a *Summary* buffer
+  containing all the tracked messages from the contact(s) under point.
+- Provide a `gnorb-view' command that takes a tags-todo search phrase
+  (or a single Org heading ID), finds all relevant messages, Org
+  headings, and BBDB records, and sets up a four-pane view: Org
+  Agenda, *BBDB* buffer, Gnus *Summary* buffer, and an *Article*
+  buffer.
diff --git a/gnorb.texi b/gnorb.texi
new file mode 100644
index 0000000..643552f
--- /dev/null
+++ b/gnorb.texi
@@ -0,0 +1,654 @@
+\input texinfo    @c -*- texinfo -*-
address@hidden %**start of header
address@hidden ./gnorb.info
address@hidden Gnorb Manual
address@hidden UTF-8
address@hidden en
address@hidden pg cp
address@hidden %**end of header
+
address@hidden Emacs
address@hidden
+* Gnorb: (gnorb).       Glue code for Gnus, Org, and BBDB.
address@hidden direntry
+
address@hidden
address@hidden
address@hidden Gnorb Manual
address@hidden for version 1, updated 3 October, 2014
address@hidden titlepage
+
address@hidden
address@hidden Top
address@hidden Gnorb Manual
address@hidden ifnottex
+
address@hidden
+* Introduction::
+* Installation::
+* Setup::
+* Email Tracking::
+* Restoring Window Layout::
+* Recent Mails From BBDB Contacts::
+* BBDB posting styles::
+* BBDB Org tagging::
+* Misc BBDB::
+* Misc Org::
+* Misc Gnus::
+* Suggested Keybindings::
+* Wishlist/TODO::
+* Index::
+
address@hidden
+--- The Detailed Node Listing ---
+
+Email Tracking
+
+* Email-Related Commands::
+* Trigger Actions::
+* Viewing Tracked Messages in *Summary* Buffers::
+* Hinting in Gnus::
+* Message Attachments::
+
+Misc BBDB
+
+* Searching for messages from BBDB contacts::
+* Citing BBDB contacts::
+* User Options::
+
+Misc Org
+
+* Inserting BBDB links::
+* User Options: User Optionsx. 
+
+Misc Gnus
+
+* Viewing Org headlines relevant to a message::
+* User Options: User Optionsxx. 
address@hidden detailmenu
address@hidden menu
+
address@hidden Introduction
address@hidden Introduction
+
+Gnorb provides glue code between the Gnus, Org, and BBDB packages.
+It's aimed at supporting email-based project management, and generally
+making it easier to keep track of email communication.
+
+Much of the code consists of single-use convenience functions, but
+tracking email conversations with Org requires is more complicated,
+and requires a bit of setup.
+
+Gnorb can be used in a modular fashion, by selectively loading the
+files ``gnorb-org'', ``gnorb-gnus'' or ``gnorb-bbdb'' instead of plain old
+``gnorb''. The package as a whole is rather Org-centric, though, and it
+won't do much of interest without ``gnorb-org''.
+
+This means that Gnorb doesn't have hard requirements to any of the
+three base libraries. For the libraries you are using, however, you'll
+get best results from using the most recent stable version (yes, that
+means BBDB 3). Some of the features in Gnorb only work with
+development versions of these libraries (those cases are noted below).
+
address@hidden Installation
address@hidden Installation
+
+Gnorb is best installed via the Elpa package manager -- look for it in
+`list-packages'.
+
+You can also clone the source code from
address@hidden://github.com/girzel/gnorb}, and put the ``gnorb'' directory on 
your
+load-path. The Github site is also a good place to report bugs and
+other issues.
+
address@hidden Setup
address@hidden Setup
+
+Loading ``gnorb'' will make the basic functions available. Using Gnorb
+for email tracking takes a bit more setup, however:
+
address@hidden
address@hidden
+Email tracking is done via the Gnus registry, so that must be
+activated with `gnus-registry-initialize'.
address@hidden
+It also requires the org-id package to be loaded, and
+`org-id-track-globally' set to t (that's the default value, so
+simply loading the package should be enough).
address@hidden
+Add a nngnorb entry to your `gnus-secondary-select-methods'
+variable. It will look like (nngnorb ``Server name''). This does
+nothing but provide a place to hang nnir searches.
address@hidden
+Then put a call to `gnorb-tracking-initialize' in your init files,
+at some point after the Gnus registry is initialized.
address@hidden
+If you're not using a local archive method for saving your sent
+messages (ie you're using IMAP), you'll also need to tell Gnorb
+where to find your sent messages. Set the variable
+`gnorb-gnus-sent-groups' to a list of strings; each string should
+indicate a fully-qualified group name, eg ``nnimap+SERVER:GROUP''.
address@hidden enumerate
+
+Lastly, Gnorb doesn't bind any keys by default; see the @ref{Suggested 
Keybindings,Suggested
+Keybindings} section below for possibilities.
+
address@hidden Email Tracking
address@hidden Email Tracking
+
+The most interesting thing Gnorb does is using Org headings to track
+email conversations. This can mean anything from reminding yourself to
+write to your mother, to conducting delicate business negotiations
+over email, to running an email-based bug tracker.
+
+Gnorb assists in this process by using the Gnus registry to track
+correspondences between emails and Org headings -- specifically,
+message IDs are associated with Org heading ids. As a conversation
+develops, messages are collected on a heading (and/or its children).
+You can compose new messages directly from the Org heading, and Gnorb
+will automatically associate your sent message with the conversation.
+You can open temporary Gnus *Summary* buffers holding all the messages
+associated with an Org subtree, and reply from there. When you receive
+new messages relevant to a conversation, Gnorb will notice them and
+prompt you to associate them with the appropriate Org heading.
+Attachments on incoming messages can be automatically saved as
+attachments on Org headings, using org-attach.
+
+In general, the goal is to keep track of whole conversations, reduce
+friction when moving between Gnus and Org, and keep you in the Org
+agenda rather than in Gnus.
address@hidden
+* Email-Related Commands::
+* Trigger Actions::
+* Viewing Tracked Messages in *Summary* Buffers::
+* Hinting in Gnus::
+* Message Attachments::
address@hidden menu
+
address@hidden Email-Related Commands
address@hidden Email-Related Commands
+
+Email tracking starts in one of three ways:
+
address@hidden
address@hidden
+With an Org heading that represents an email TODO. Call
+`gnorb-org-handle-mail' (see below) on the heading to compose a new
+message, and start the tracking process.
address@hidden
+By calling org-capture on a received message. Any heading captured
+from a message will automatically be associated with that message.
address@hidden
+By calling `gnorb-gnus-outgoing-do-todo' in a message composition
+buffer -- see below.
address@hidden enumerate
+
+There are three main email-related commands:
+
address@hidden
address@hidden
+`gnorb-org-handle-mail' is called on an Org heading to compose a
+new message. By default, this will begin a reply to the most recent
+message in the conversation. If there are no associated messages to
+reply to (or you call the function with a double prefix arg), Gnorb
+will look for mailto: or bbdb: links in the heading, and compose a
+new message to them.
+
+The sent message will be associated with the Org heading, and
+you'll be brought back to the heading and asked to trigger an
+action on it.
+
+`gnorb-email-subtree' is an alternative entry-point to
+`gnorb-org-handle-mail'. It does the same thing as the latter, but
+first exports the body of the subtree as either text or a file,
+then inserts the text into the message body, or attaches the file
+to the message, depending on what you've chosen.
address@hidden
+`gnorb-gnus-incoming-do-todo' is called on a message in a Gnus
+*Summary* buffer. You'll be prompted for an Org heading, taken to
+that heading, and asked to trigger an action on it.
address@hidden
+`gnorb-gnus-outgoing-do-todo' is called in message mode, while
+composing a new message. 
+
+If called without a prefix arg, a new Org heading will be created
+after the message is sent, and the sent message associated with it.
+The new heading will be created as a capture heading, using the
+template specified by the `gnorb-gnus-new-todo-capture-key' option.
+
+If you call this function with a prefix arg, you'll be prompted to
+choose an existing Org heading instead. After the the message is
+sent, you'll be taken to that heading and prompted to trigger an
+action on it.
+
+It's also possible to call this function *after* a message is sent,
+in case you forgot. Gnorb saves information about the most recently
+sent message for this purpose.
address@hidden enumerate
+
+Because these three commands all express a similar intent, but are
+called in different modes, it can make sense to give each of them the
+same keybinding in the keymaps for Org mode, Gnus summary mode, and
+Message mode, respectively.
+
address@hidden Trigger Actions
address@hidden Trigger Actions
+
+After calling `gnorb-gnus-incoming-do-todo' on a message, or after
+sending a message associated with an Org heading, you'll be taken to
+the heading and asked to ``trigger an action'' on it. At the moment
+there are four different possibilities: triggering a TODO state-change
+on the heading, taking a note on the heading (both these options will
+associate the message with the heading), associating the message but
+doing nothing else, and lastly, doing nothing at all.
+
+More actions will be added in the future; it's also possible to add
+your own action: see the docstring of `gnorb-org-trigger-actions'.
+
address@hidden Viewing Tracked Messages in *Summary* Buffers
address@hidden Viewing Tracked Messages in *Summary* Buffers
+
+Call `gnorb-org-view' on an Org heading to open an nnir *Summary*
+buffer showing all the messages associated with that heading (this
+requires that you've added an nngnorb server to your Gnus backends). A
+minor mode will be in effect, ensuring that any replies you send to
+messages in this buffer will automatically be associated with the
+original Org heading. You can also invoke
+`gnorb-summary-disassociate-message' (``C-c d'') to disassociate the
+message with the Org heading.
+
+As a bonus, it's possible to go into Gnus' *Server* buffer, find the
+line specifying your nngnorb server, and hit ``G'' (aka
+`gnus-group-make-nnir-group'). At the query prompt, enter an Org-style
+tags-todo Agenda query string (eg ``+work-computer'', or what have you).
+Gnorb will find all headings matching this query, scan their subtrees
+for gnus links, and then give you a Summary buffer containing all the
+linked messages. This is dog-slow at the moment; it will get faster.
+
address@hidden Hinting in Gnus
address@hidden Hinting in Gnus
+
+When you receive new mails that might be relevant to existing Org
+TODOs, Gnorb can alert you to that fact. When
+`gnorb-gnus-hint-relevant-article' is t (the default), Gnorb will
+display a message in the minibuffer when opening potentially relevant
+messages. You can then use `gnorb-gnus-incoming-to-todo' to trigger an
+action on the relevant TODO.
+
+This hinting can happen in the Gnus summary buffer as well. If you use
+the escape indicated by `gnorb-gnus-summary-mark-format-letter'' as
+part of your `gnus-summary-line-format', articles that are relevant to
+TODOs will be marked with a special character in the Summary buffer,
+as determined by `gnorb-gnus-summary-mark'. By default, the format
+letter is ``g'' (meaning it is used as ``%ug'' in the format line), and
+the mark is ``¡''.
+
address@hidden Message Attachments
address@hidden Message Attachments
+
+Gnorb simplifies the handling of attachments that you receive in
+emails. When you call `gnorb-gnus-incoming-do-todo' on a message,
+you'll be prompted to re-attach the email's attachments onto the Org
+heading, using the org-attach library.
+
+You can also do this as part of the capture process. Set the
+new :gnus-attachments key to ``t'' in a capture template that you use on
+mail messages, and you'll be queried to re-attach the message's
+attachments onto the newly-captured heading. Or set
+`gnorb-gnus-capture-always-attach' to ``t'' to have Gnorb do this for
+all capture templates.
+
+You can also do this using the regular system of MIME commands,
+without invoking the email tracking process. See @ref{Suggested 
Keybindings,Suggested
+Keybindings}, below.
+
+The same process works in reverse: when you send a message from an Org
+heading using `gnorb-org-handle-mail', Gnorb will ask if you want to
+attach the files in the heading's org-attach directory to the outgoing
+message.
+
address@hidden Restoring Window Layout
address@hidden Restoring Window Layout
+
+Many Gnorb functions alter the window layout and value of point. In
+most of these cases, you can restore the previous layout using the
+interactive function `gnorb-restore-layout'.
+
address@hidden Recent Mails From BBDB Contacts
address@hidden Recent Mails From BBDB Contacts
+
+If you're using a recent git version of BBDB (circa mid-May 2014 or
+later), you can give your BBDB contacts a special field which will
+collect links to recent emails from that contact. The default name of
+the field is ``messages'', but you can customize that name using the
+`gnorb-bbdb-messages-field' option.
+
+Gnorb will not collect links by default: you need to call
+`gnorb-bbdb-open-link' on a contact once to start the process.
+Thereafter, opening mails from that contact will store a link to the
+message.
+
+Once some links are stored, `gnorb-bbdb-open-link' will open them: Use
+a prefix arg to the function call to select particular messages to
+open. There are several options controlling how all this works; see
+the gnorb-bbdb user options section below for details.
+
address@hidden BBDB posting styles
address@hidden BBDB posting styles
+
+Gnorb comes with a BBDB posting-style system, inspired by (copied
+from) gnus-posting-styles. You can specify how messages are composed
+to specific contacts, by matching on contact field values (the same
+way gnus-posting-styles matches on group names). See the docstring of
+`gnorb-bbdb-posting-styles' for details.
+
+In order not to be too intrusive, Gnorb doesn't alter the behavior of
+`bbdb-mail', the usual mail-composition function. Instead it provides
+an alternate `gnorb-bbdb-mail', which does exactly the same thing, but
+first processes the new mail according to `gnorb-bbdb-posting-styles'.
+If you want to use this feature regularly, you can remap `bbdb-mail'
+to `gnorb-bbdb-mail' in the `bbdb-mode-map'.
+
address@hidden BBDB Org tagging
address@hidden BBDB Org tagging
+
+BBDB contacts can be tagged with the same tags you use in your Org
+files. This allows you to pop up a *BBDB* buffer alongside your Org
+Agenda when searching for certain tags. This can happen automatically
+for all Org tags-todo searches, if you set the option
+`gnorb-org-agenda-popup-bbdb' to t. Or you can do it manually, by
+calling the command of the same name. This command only shows TODOs by
+default: use a prefix argument to show all tagged headings.
+
+Tags are stored in an xfield named org-tags, by default. You can
+customize the name of this field using `gnorb-bbdb-org-tag-field'.
+
address@hidden Misc BBDB
address@hidden Misc BBDB
+
address@hidden
+* Searching for messages from BBDB contacts::
+* Citing BBDB contacts::
+* User Options::
address@hidden menu
+
address@hidden Searching for messages from BBDB contacts
address@hidden Searching for messages from BBDB contacts
+
+Call `gnorb-bbdb-mail-search' to search for all mail messages from the
+record(s) displayed. Currently supports the notmuch, mairix, and
+namazu search backends; set `gnorb-gnus-mail-search-backend' to one of
+those symbol values.
+
address@hidden Citing BBDB contacts
address@hidden Citing BBDB contacts
+
+Calling `gnorb-bbdb-cite-contact' will prompt for a BBDB record and
+insert a string of the type ``Bob Smith <bob@@smith.com>''.
+
address@hidden User Options
address@hidden User Options
+
address@hidden @samp
address@hidden `gnorb-bbdb-org-tag-field
+The name of the BBDB xfield, as a
+symbol, that holds Org-related tags. Specified as a string with
+the ``:'' separator between tags, same as for Org headings.
+Defaults to org-tag.
address@hidden `gnorb-bbdb-messages-field'
+The name of the BBDB xfield that
+holds links to recently-received messages from this contact.
+Defaults to `messages.
address@hidden `gnorb-bbdb-collect-N-messages'
+Collect at most this many links
+to messages from this contact. Defaults to 5.
address@hidden `gnorb-bbdb-define-recent'
+What does ``recently-received'' mean?
+Possible values are the symbols seen and received. When set to
+seen, the most recently-opened messages are collected. When set
+to received, the most recently-received (by Date header) messages
+are collected. Defaults to seen.
address@hidden `gnorb-bbdb-message-link-format-multi'
+How is a single message's
+link formatted in the multi-line BBDB layout format? Defaults to
+``%:count. %D: %:subject'' (see the docstring for details).
address@hidden ` gnorb-bbdb-message-link-format-one'
+How is a single message's
+link formatted in the one-line BBDB layout format? Defaults to
+nil (see the docstring for details).
address@hidden `gnorb-bbdb-posting-styles'
+Styles to use for influencing the
+format of mails composed to the BBDB record(s) under point (see
+the docstring for details).
address@hidden table
+
address@hidden Misc Org
address@hidden Misc Org
+
address@hidden
+* Inserting BBDB links::
+* User Options: User Optionsx. 
address@hidden menu
+
address@hidden Inserting BBDB links
address@hidden Inserting BBDB links
+
+Calling `gnorb-org-contact-link' will prompt for a BBDB record and
+insert an Org link to that record at point.
+
address@hidden User Optionsx
address@hidden User Options
+
address@hidden @samp
address@hidden `gnorb-org-after-message-setup-hook'
+Hook run in a message buffer
+after setting up the message, from `gnorb-org-handle-mail' or
+`gnorb-org-email-subtree'.
address@hidden `gnorb-org-trigger-actions'
+List of potential actions that can be
+taken on headings after a message is sent. See docstring for
+details.
address@hidden `gnorb-org-mail-scan-scope'
+The number of paragraphs to scan for
+mail-related links. This comes into play when calling
+`gnorb-org-handle-mail' on a heading with no associated messages,
+or when `gnorb-org-handle-mail' is called with a prefix arg.
address@hidden `gnorb-org-find-candidates-match'
+When searching all Org files
+for headings to collect messages from, this option can limit
+which headings are searched. It is used as the second argument to
+a call to `org-map-entries', and has the same syntax as that used
+in an agenda tags view.
address@hidden `gnorb-org-email-subtree-text-parameters'
+A plist of export
+parameters corresponding to the EXT-PLIST argument to the export
+functions, for use when exporting to text.
address@hidden `gnorb-org-email-subtree-file-parameters'
+A plist of export
+parameters corresponding to the EXT-PLIST argument to the export
+functions, for use when exporting to a file.
address@hidden `gnorb-org-email-subtree-text-options'
+A list of ts and nils
+corresponding to Org's export options, to be used when exporting
+to text. The options, in order, are async, subtreep,
+visible-only, and body-only.
address@hidden `gnorb-org-email-subtree-file-options'
+A list of ts and nils
+corresponding to Org's export options, to be used when exporting
+to a file. The options, in order, are async, subtreep,
+visible-only, and body-only.
address@hidden `gnorb-org-export-extensions'
+Correspondence between export
+backends and their respective (usual) file extensions.
address@hidden `gnorb-org-capture-collect-link-p'
+When this is set to t, the
+capture process will always store a link to the Gnus message or
+BBDB record under point, even when the link isn't part of the
+capture template. It can then be added to the captured heading
+with org-insert-link, as usual.
address@hidden `gnorb-org-agenda-popup-bbdb'
+Set to ``t'' to automatically pop up
+the BBDB buffer displaying records corresponding to the Org
+Agenda tags search underway. If this is nil you can always do it
+manually with the command of the same name.
address@hidden `gnorb-org-bbdb-popup-layout'
+Controls the layout of the
+Agenda-related BBDB popup, takes the same values as
+bbdb-pop-up-layout.
address@hidden table
+
address@hidden Misc Gnus
address@hidden Misc Gnus
+
address@hidden
+* Viewing Org headlines relevant to a message::
+* User Options: User Optionsxx. 
address@hidden menu
+
address@hidden Viewing Org headlines relevant to a message
address@hidden Viewing Org headlines relevant to a message
+
+Call `gnorb-gnus-view' on a message that is associated with an Org
+heading to jump to that heading.
+
address@hidden User Optionsxx
address@hidden User Options
+
address@hidden @samp
address@hidden `gnorb-gnus-mail-search-backend'
+Specifies the search backend
+that you use for searching mails. Currently supports notmuch,
+mairix, and namazu: set this option to one of those symbols.
address@hidden `gnorb-gnus-capture-always-attach'
+Treat all capture templates as
+if they had the :gnus-attachments key set to ``t''. This only has
+any effect if you're capturing from a Gnus summary or article
+buffer.
address@hidden `gnorb-trigger-todo-default'
+Set to either `note or `todo to tell
+`gnorb-gnus-incoming-do-todo' what to do by default. You can
+reach the non-default behavior by calling that function with a
+prefix argument. Alternately, set to `prompt to always prompt for
+the appropriate action.
address@hidden `gnorb-gnus-trigger-refile-targets'
+If you use
+`gnorb-gnus-incoming-do-todo' on an incoming message, Gnorb will
+try to locate a TODO heading that's relevant to that message. If
+it can't, it will prompt you for one, using the refile interface.
+This option will be used as the value of `org-refile-targets'
+during that process: see the docstring of `org-refile-targets'
+for the appropriate syntax.
address@hidden `gnorb-gnus-new-todo-capture-key'
+Set this to a single-character
+string pointing at an Org capture template to use when creating
+TODOs from outgoing messages. The template is a regular capture
+template, with a few exceptions. If Gnus helps you archive
+outgoing messages (ie you have `gnus-message-archive-group' set
+to something, and your outgoing messages have a ``Fcc'' header), a
+link to that message will be made, and you'll be able to use all
+the escapes related to gnus messages. If you don't archive
+outgoing messages, you'll still be able to use the %:subject,
+%:to, %:toname, %:toaddress, and %:date escapes in the capture
+template.
address@hidden `gnorb-gnus-hint-relevant-article'
+Set to ``t'' (the default) to
+have Gnorb give you a hint in the minibuffer when opening
+messages that might be relevant to existing Org TODOs.
address@hidden `gnorb-gnus-summary-mark-format-letter'
+The formatting letter to
+use as part of your `gnus-summary-line-format', to indicate
+messages which might be relevant to Org TODOs. Defaults to ``g'',
+meaning it should be used as ``%ug'' in the format line.
address@hidden `gnorb-gnus-summary-mark'
+The mark used to indicate relevant
+messages in the Summary buffer, when
+`gnorb-gnus-summary-mark-format-letter' is present in the format
+line. Defaults to ``¡''.
address@hidden table
+
address@hidden Suggested Keybindings
address@hidden Suggested Keybindings
+
address@hidden
+(eval-after-load "gnorb-bbdb"
+  '(progn
+     (define-key bbdb-mode-map (kbd "O") 'gnorb-bbdb-tag-agenda)
+     (define-key bbdb-mode-map (kbd "S") 'gnorb-bbdb-mail-search)
+     (define-key bbdb-mode-map [remap bbdb-mail] 'gnorb-bbdb-mail)
+     (define-key bbdb-mode-map (kbd "l") 'gnorb-bbdb-open-link)
+     (global-set-key (kbd "C-c C") 'gnorb-bbdb-cite-contact)))
+
+(eval-after-load "gnorb-org"
+  '(progn
+     (org-defkey org-mode-map (kbd "C-c C") 'gnorb-org-contact-link)
+     (org-defkey org-mode-map (kbd "C-c t") 'gnorb-org-handle-mail)
+     (org-defkey org-mode-map (kbd "C-c e") 'gnorb-org-view)
+     (org-defkey org-mode-map (kbd "C-c E") 'gnorb-org-email-subtree)
+     (org-defkey org-mode-map (kbd "C-c V") 'gnorb-org-popup-bbdb)
+     (setq gnorb-org-agenda-popup-bbdb t)
+     (eval-after-load "org-agenda"
+       '(progn (org-defkey org-agenda-mode-map (kbd "H") 
'gnorb-org-handle-mail)
+               (org-defkey org-agenda-mode-map (kbd "V") 
'gnorb-org-popup-bbdb)))))
+
+(eval-after-load "gnorb-gnus"
+  '(progn
+     (define-key gnus-summary-mime-map "a" 'gnorb-gnus-article-org-attach)
+     (define-key gnus-summary-mode-map (kbd "C-c t") 
'gnorb-gnus-incoming-do-todo)
+     (push '("attach to org heading" . gnorb-gnus-mime-org-attach)
+           gnus-mime-action-alist)
+     ;; The only way to add mime button command keys is by redefining
+     ;; gnus-mime-button-map, possibly not ideal. Ideal would be a
+     ;; setter function in gnus itself.
+     (push '(gnorb-gnus-mime-org-attach "a" "Attach to Org heading")
+           gnus-mime-button-commands)
+     (setq gnus-mime-button-map
+           (let ((map (make-sparse-keymap)))
+             (define-key map gnus-mouse-2 'gnus-article-push-button)
+             (define-key map gnus-down-mouse-3 'gnus-mime-button-menu)
+             (dolist (c gnus-mime-button-commands)
+               (define-key map (cadr c) (car c)))
+             map))))
+
+(eval-after-load "message"
+  '(progn
+     (define-key message-mode-map (kbd "C-c t") 'gnorb-gnus-outgoing-do-todo)))
address@hidden lisp
+
address@hidden Wishlist/TODO
address@hidden Wishlist/TODO
+
address@hidden
address@hidden
+Provide a command that, when in the Org Agenda, does an email search
+for messages received in the visible date span, or day under point,
+etc. Make it work in the calendar, as well?
address@hidden
+Add trigger actions that create new sibling or child headings on the
+original Org heading.
address@hidden
+Allow tagging of Gnus messages, by giving the message's registry
+entry an `org-tags key.
address@hidden
+Provide persistent nngnorb search groups.
address@hidden
+Allow automatic org-tagging of BBDB contacts: when messages from a
+contact are associated with an Org heading, make it possible for the
+contact to inherit that heading's tags automatically.
address@hidden
+Provide completion when setting Org tags on a BBDB contact.
address@hidden
+Provide a `gnorb-bbdb-view' command that opens a *Summary* buffer
+containing all the tracked messages from the contact(s) under point.
address@hidden
+Provide a `gnorb-view' command that takes a tags-todo search phrase
+(or a single Org heading ID), finds all relevant messages, Org
+headings, and BBDB records, and sets up a four-pane view: Org
+Agenda, **Article* SummaryBBDB* buffer, Gnus *buffer, and an *
+buffer.
address@hidden itemize
+
address@hidden Index
address@hidden Index
+
address@hidden Emacs 25.0.50.8 (Org mode 8.3beta)
address@hidden
\ No newline at end of file
diff --git a/nngnorb.el b/nngnorb.el
new file mode 100644
index 0000000..bdaf569
--- /dev/null
+++ b/nngnorb.el
@@ -0,0 +1,375 @@
+;;; nngnorb.el --- Gnorb backend for Gnus
+
+;; This file is in the public domain.
+
+;; Author: Eric Abrahamsen <address@hidden>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This is a backend for supporting Gnorb-related stuff. I'm going to
+;; regret this, I know.
+
+;; It started off just with wanting to collect all the gnus links in a
+;; subtree, and display all the messages in an ephemeral group. But it
+;; doesn't seem possible to create ephemeral groups without
+;; associating them with a server, and which server would that be?
+;; Nnir also provides a nice interface to creating ephemeral groups,
+;; but again, it relies on a server parameter to know which nnir
+;; engine to use, and if you try to fake it it still craps out.
+
+;; So this file is a copy-pasta from nnnil.el -- I'm trying to keep
+;; this as simple as possible. Right now it does nothing but serving
+;; as a place to hang ephemeral groups made with nnir searches of
+;; message from the rest of your gnus installation. Enjoy.
+
+;;; Code:
+
+(eval-and-compile
+  (require 'nnheader)
+  (require 'nnir))
+
+(defvar nngnorb-status-string "")
+
+(defvar nngnorb-attachment-file-list nil
+  "A place to store Org attachments relevant to the subtree being
+  viewed.")
+
+(make-variable-buffer-local 'nngnorb-attachment-file-list)
+
+(gnus-declare-backend "nngnorb" 'none)
+
+(add-to-list 'nnir-method-default-engines '(nngnorb . gnorb))
+
+(add-to-list 'nnir-engines
+            '(gnorb nnir-run-gnorb))
+
+(defun nnir-run-gnorb (query server &optional group)
+  "Run the actual search for messages to display. See nnir.el for
+some details of how this gets called.
+
+As things stand, the query string can be given as one of two
+different things. First is the ID string of an Org heading,
+prefixed with \"id+\". This was probably a bad choice as it could
+conceivably look like an org tags search string. Fix that later.
+If it's an ID, then the entire subtree text of that heading is
+scanned for gnus links, and the messages relevant to the subtree
+are collected from the registry, and all the resulting messages
+are displayed in an ephemeral group.
+
+Otherwise, the query string can be a tags match string, a la the
+Org agenda tags search. All headings matched by this string will
+be scanned for gnus messages, and those messages displayed."
+  ;; During the transition period between using message-ids stored in
+  ;; a property, and the new registry-based system, we're going to use
+  ;; both methods to collect relevant messages. This could be a little
+  ;; slower, but for the time being it will be safer.
+  (save-excursion
+    (let ((q (cdr (assq 'query query)))
+         (buf (get-buffer-create nnir-tmp-buffer))
+         msg-ids org-ids links vectors)
+      (with-current-buffer buf
+       (erase-buffer)
+       (setq nngnorb-attachment-file-list nil))
+      (when (equal "5.13" gnus-version-number)
+       (setq q (car q)))
+      (cond ((string-match "id\\+\\([[:alnum:]-]+\\)$" q)
+            (with-demoted-errors "Error: %S"
+              (org-id-goto (match-string 1 q))
+              (append-to-buffer
+               buf
+               (point)
+               (org-element-property
+                :end (org-element-at-point)))
+              (save-restriction
+                (org-narrow-to-subtree)
+                (setq org-ids
+                      (append
+                       (gnorb-collect-ids)
+                       org-ids))
+                (when org-ids
+                  (with-current-buffer buf
+                    ;; The file list var is buffer local, so set it
+                    ;; (local to the nnir-tmp-buffer) to a full list
+                    ;; of all files in the subtree.
+                    (dolist (id org-ids)
+                      (setq nngnorb-attachment-file-list
+                            (append (gnorb-org-attachment-list id)
+                                    nngnorb-attachment-file-list))))))))
+           ((listp q)
+            ;; be a little careful: this could be a list of links, or
+            ;; it could be the full plist
+            (setq links (if (plist-member q :gnus)
+                            (plist-get q :gnus)
+                          q)))
+           (t (org-map-entries
+               (lambda ()
+                 (push (org-id-get) org-ids)
+                 (append-to-buffer
+                  buf
+                  (point)
+                  (save-excursion
+                    (outline-next-heading)
+                    (point))))
+               q
+               'agenda)))
+      (with-current-buffer buf
+       (goto-char (point-min))
+       (setq links (plist-get (gnorb-scan-links (point-max) 'gnus)
+                              :gnus))
+       (goto-char (point-min))
+       (while (re-search-forward
+               (concat ":" gnorb-org-msg-id-key ": \\([^\n]+\\)")
+               (point-max) t)
+         (setq msg-ids (append (split-string (match-string 1)) msg-ids))))
+      ;; Here's where we maybe do some duplicate work using the
+      ;; registry. Take our org ids and find all relevant message ids.
+      (dolist (i (delq nil org-ids))
+       (let ((rel-msg-id (gnorb-registry-org-id-search i)))
+         (when rel-msg-id
+           (setq msg-ids (append rel-msg-id msg-ids)))))
+      (when msg-ids
+         (dolist (id msg-ids)
+           (let ((link (gnorb-msg-id-to-link id)))
+             (when link
+               (push link links)))))
+      (setq links (delete-dups links))
+      (unless (gnus-alive-p)
+       (gnus))
+      (dolist (m links (when vectors
+                        (nreverse vectors)))
+       (let (server-group msg-id result artno)
+         (setq m (org-link-unescape m))
+         (when (string-match "\\`\\([^#]+\\)\\(#\\(.*\\)\\)?" m)
+           (setq server-group (match-string 1 m)
+                 msg-id (match-string 3 m)
+                 result (ignore-errors (gnus-request-head msg-id 
server-group)))
+           (when result
+            (setq artno (cdr result))
+            (when (and (integerp artno) (> artno 0))
+              (push (vector server-group artno 100) vectors)))))))))
+
+(defvar gnorb-summary-minor-mode-map (make-sparse-keymap)
+  "Keymap for use in Gnorb's *Summary* minor mode.")
+
+(define-minor-mode gnorb-summary-minor-mode
+  "A minor mode for use in nnir *Summary* buffers created by Gnorb.
+
+These *Summary* buffers are usually created by calling
+`gnorb-org-view', or by initiating an nnir search on a nngnorb server.
+
+While active, this mode provides some Gnorb-specific commands,
+and also advises Gnus' reply-related commands in order to
+continue to provide tracking of sent messages."
+  nil " Gnorb" gnorb-summary-minor-mode-map
+  (setq nngnorb-attachment-file-list
+       ;; Copy the list of attached files from the nnir-tmp-buffer to
+       ;; this summary buffer.
+       (buffer-local-value
+        'nngnorb-attachment-file-list
+         (get-buffer nnir-tmp-buffer))))
+
+(define-key gnorb-summary-minor-mode-map
+  [remap gnus-summary-exit]
+  'gnorb-summary-exit)
+
+(define-key gnorb-summary-minor-mode-map (kbd "C-c d")
+  'gnorb-summary-disassociate-message)
+
+;; All this is pretty horrible, but it's the only way to get sane
+;; behavior, there are no appropriate hooks, and I want to avoid
+;; advising functions.
+
+(define-key gnorb-summary-minor-mode-map
+  [remap gnus-summary-very-wide-reply-with-original]
+  'gnorb-summary-very-wide-reply-with-original)
+
+(define-key gnorb-summary-minor-mode-map
+  [remap gnus-summary-wide-reply-with-original]
+  'gnorb-summary-wide-reply-with-original)
+
+(define-key gnorb-summary-minor-mode-map
+  [remap gnus-summary-reply]
+  'gnorb-summary-reply)
+
+(define-key gnorb-summary-minor-mode-map
+  [remap gnus-summary-very-wide-reply]
+  'gnorb-summary-very-wide-reply)
+
+(define-key gnorb-summary-minor-mode-map
+  [remap gnus-summary-reply-with-original]
+  'gnorb-summary-reply-with-original)
+
+(define-key gnorb-summary-minor-mode-map
+  [remap gnus-summary-wide-reply]
+  'gnorb-summary-wide-reply)
+
+(define-key gnorb-summary-minor-mode-map
+  [remap gnus-summary-mail-forward]
+  'gnorb-summary-mail-forward)
+
+(defun gnorb-summary-wide-reply (&optional yank)
+  (interactive
+   (list (and current-prefix-arg
+             (gnus-summary-work-articles 1))))
+  (gnorb-summary-reply yank t))
+
+(defun gnorb-summary-reply-with-original (n &optional wide)
+  (interactive "P")
+  (gnorb-summary-reply (gnus-summary-work-articles n) wide))
+
+(defun gnorb-summary-very-wide-reply (&optional yank)
+  (interactive
+   (list (and current-prefix-arg
+             (gnus-summary-work-articles 1))))
+  (gnorb-summary-reply yank t (gnus-summary-work-articles yank)))
+
+(defun gnorb-summary-reply (&optional yank wide very-wide)
+  (interactive)
+  (gnus-summary-reply yank wide very-wide)
+  (gnorb-summary-reply-hook))
+
+(defun gnorb-summary-wide-reply-with-original (n)
+  (interactive "P")
+  (gnorb-summary-reply-with-original n t))
+
+(defun gnorb-summary-very-wide-reply-with-original (n)
+  (interactive "P")
+  (gnorb-summary-reply
+   (gnus-summary-work-articles n) t (gnus-summary-work-articles n)))
+
+(defun gnorb-summary-mail-forward (n)
+  (interactive "P")
+  (gnus-summary-mail-forward n t)
+  (gnorb-summary-reply-hook))
+
+(defun gnorb-summary-reply-hook (&rest args)
+  "Function that runs after any command that creates a reply."
+  ;; Not actually a "hook"
+  (let* ((msg-id (aref message-reply-headers 4))
+        (org-id (car-safe (gnus-registry-get-id-key msg-id 'gnorb-ids)))
+        (compose-marker (make-marker))
+        (attachments (buffer-local-value
+                      'nngnorb-attachment-file-list
+                      (get-buffer nnir-tmp-buffer))))
+    (when org-id
+      (move-marker compose-marker (point))
+      (save-restriction
+       (widen)
+       (message-narrow-to-headers-or-head)
+       (goto-char (point-at-bol))
+       (open-line 1)
+       (message-insert-header
+        (intern gnorb-mail-header)
+        org-id)
+       (add-to-list 'message-exit-actions
+                    'gnorb-org-restore-after-send t))
+      (goto-char compose-marker))
+    (when attachments
+      (map-y-or-n-p
+       (lambda (a) (format "Attach %s to outgoing message? "
+                          (file-name-nondirectory a)))
+       (lambda (a)
+        (mml-attach-file a (mm-default-file-encoding a)
+                         nil "attachment"))
+       attachments
+       '("file" "files" "attach")))))
+
+(defun gnorb-summary-exit ()
+  "Like `gnus-summary-exit', but restores the gnorb window conf."
+  (interactive)
+  (call-interactively 'gnus-summary-exit)
+  (gnorb-restore-layout))
+
+(defun gnorb-summary-disassociate-message ()
+  "Disassociate a message from its Org TODO.
+
+This is used in a Gnorb-created *Summary* buffer to remove the
+connection between the message and whichever Org TODO resulted in
+the message being included in this search."
+  (interactive)
+  (let* ((msg-id (gnus-fetch-original-field "message-id"))
+        (org-ids (gnus-registry-get-id-key msg-id 'gnorb-ids))
+        chosen)
+    (when org-ids
+      (if (= (length org-ids) 1)
+         ;; Only one associated Org TODO.
+         (progn (gnus-registry-set-id-key msg-id 'gnorb-ids)
+                (setq chosen (car org-ids)))
+       ;; Multiple associated TODOs, prompt to choose one.
+       (setq chosen
+             (cdr
+              (org-completing-read
+               "Choose a TODO to disassociate from: "
+               (mapcar
+                (lambda (h)
+                  (cons (gnorb-pretty-outline h) h))
+                org-ids))))
+       (gnus-registry-set-id-key msg-id 'gnorb-ids
+                                 (remove chosen org-ids)))
+      (message "Message disassociated from %s"
+              (gnorb-pretty-outline chosen)))))
+
+(defvar nngnorb-status-string "")
+
+(defun nngnorb-retrieve-headers (articles &optional group server fetch-old)
+  (with-current-buffer nntp-server-buffer
+    (erase-buffer))
+  'nov)
+
+(defun nngnorb-open-server (server &optional definitions)
+  t)
+
+(defun nngnorb-close-server (&optional server)
+  t)
+
+(defun nngnorb-request-close ()
+  t)
+
+(defun nngnorb-server-opened (&optional server)
+  t)
+
+(defun nngnorb-status-message (&optional server)
+  nngnorb-status-string)
+
+(defun nngnorb-request-article (article &optional group server to-buffer)
+  (setq nngnorb-status-string "No such group")
+  nil)
+
+(defun nngnorb-request-group (group &optional server fast info)
+  (let (deactivate-mark)
+    (with-current-buffer nntp-server-buffer
+      (erase-buffer)
+      (insert "411 no such news group\n")))
+  (setq nngnorb-status-string "No such group")
+  nil)
+
+(defun nngnorb-close-group (group &optional server)
+  t)
+
+(defun nngnorb-request-list (&optional server)
+  (with-current-buffer nntp-server-buffer
+    (erase-buffer))
+  t)
+
+(defun nngnorb-request-post (&optional server)
+  (setq nngnorb-status-string "Read-only server")
+  nil)
+
+(provide 'nngnorb)
+
+;;; nnnil.el ends here



reply via email to

[Prev in Thread] Current Thread [Next in Thread]