My Gnus Workflow For Working With iOS Notes (March, 2025)

Old habits die hard - and ever since my iPod Touch 3G days I've been keeping the iOS notes app around when I have to quickly jot down something with no other computer, and more importantly no pen and paper, in sight than the one in my pocket - my phone.

Usually I simply want to focus on writing down something quickly, without overhead - and in this scenario I consider markup or having to deal with VCS overhead - which is also the reason, why I never adapted one of the fancier mobile notes applications supporting org-mode and alike and why the stock one always worked for me well enough. (side note: I do access my denote-Zettelkasten via Working Copy (a git client for iOS that supports rendering .org files) which I use for reading my notes from there - I would never use it for editing text as that's cumbersome).

However, as I'm doing most of my organizing and planning in Org-Mode, there are some things I want to do from time to time:

Luckily, the iOS notes app speaks IMAP and as notes are stored on my mailserver, we can use gnus to acces them. In this post I want to briefly describe how I work with iOS notes that are stored on my mailserver from Emacs utilizing gnus for most things.

Accessing Notes In Gnus

This is the most straight forward part as it's just an IMAP-folder, so we're able to work with it as with any other arbitrary IMAP-folder as well. I usually rely on org-capture and my capture templates for then capturing the information to denote.

Editing Notes In Gnus

Is possible via gnus-summary-edit-article (bound to B w in a standard configuration).

Creating Notes In Gnus

Is a bit tricky as using gnus-summary-create-article isn't enough - when we edit an existing note via B w we can easily see why if we have a look at the mail header:

X-Uniform-Type-Identifier: com.apple.mail-note
X-Mail-Created-Date: Sun, 23 Mar 2025 10:07:20 +0100
X-Universally-Unique-Identifier: 5F1B4029-D507-4259-9752-093737237D7F

as we have to set X-Uniform-Type-Identifier to com.apple.mail-note, add a X-Mail-Created-Date (message-make-date already returns the right format), and a UUID as X-Universally-Unique-Identifier.

Side Note: Generating Random-Ish Uuidv4 In Elisp

Speaking of UUIDv4, Emacs doesn't offer a built-in function to generate those, this is how I handle the generation of those:

(defun wilko/makeshift-uuid ()
  "Generate a UUID (version 4) somewhat randomly."
  (let ((template "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
        (pos 0)
        (entropy (secure-hash 'sha1 (mapconcat #'prin1-to-string
                           (list (user-uid)
                             (emacs-pid)
                             (current-time)
                             (random (expt 2 32)))
                           "-"))))
    (replace-regexp-in-string
     "[xy]"
     (lambda (c)
       (let ((char (aref entropy pos)))
     (setq pos (1+ pos))
     (cond
          ((char-equal (aref c 0) ?x) (char-to-string char))
          ((char-equal (aref c 0) ?y)
           (format "%x" (+ #x8 (random 4))))
          (t ""))))
     template)))

Our Gnus-Summary-Create-Apple-Note Function...

so with having this solved, we can write a modified gnus-summary-create-article function that inserts these headers:

  (defun wilko/gnus-summary-create-apple-note ()
    "Create an gnus article in apple note format."
    (interactive nil gnus-summary-mode)
    (let ((group gnus-newsgroup-name)
          group-art
      (created-date (message-make-date))
          (uuid (wilko/makeshift-uuid)))
      (unless (gnus-check-backend-function 'request-accept-article group)
    (error "%s does not support article importing" group))
      (with-current-buffer (gnus-get-buffer-create " *import file*")
    (erase-buffer)
    (goto-char (point-min))
    (insert "From: " (format "%s <%s>\n" user-full-name user-mail-address)
        "Subject: " (read-string "Subject: ") "\n"
        "Date: " created-date "\n"
        "Message-ID: " (message-make-message-id) "\n"
        "X-Uniform-Type-Identifier: com.apple.mail-note\n"
        "X-Mail-Created-Date: " created-date "\n"
        "X-Universally-Unique-Identifier: " uuid "\n\n")
    (setq group-art (gnus-request-accept-article group nil t))
    (kill-buffer (current-buffer)))
      (setq gnus-newsgroup-active (gnus-activate-group group))
      (forward-line 1)
      (gnus-summary-goto-article (cdr group-art) nil t)
      (gnus-summary-edit-article)))

... And Some Advice

write us a small advice that makes sure to call this function instead of gnus-summary-create-article when in a Notes summary buffer so we don't break our B I binding:

(defun wilko/gnus-summary-create-article-advice (orig-fn &rest args)
  "Use `wilko/gnus-summary-create-apple-note' if in a Notes group."
  (if (and (derived-mode-p 'gnus-summary-mode)
           gnus-newsgroup-name
           (string-match-p "Notes" gnus-newsgroup-name))
      (wilko/gnus-summary-create-apple-note)
    (apply orig-fn args)))

(advice-add 'gnus-summary-create-article :around #'wilko/gnus-summary-create-article-advice)

and call it a day.

Wouldn't It Be Cool To Just Say Buffer-Or-Region To Note?

I noticed that my primary usecase for my wilko/gnus-summary-create-apple-note function is, so that I can copy-paste either whole buffers or selections into a note - so instead of having to go through the whole gnus dance of doing so - I, somewhere along diving way too much into this rabbit whole in a moment of caffeine-filled-adhd-hyperfocus-on-hacking-away-elisp-bits, came up with a function to do this:

(defun wilko/store-region-or-buffer-as-apple-note ()
  "Store the current region or buffer as an Apple Note in GNUS."
  (interactive)
  (let* ((group "nnimap+wmeyer.eu:Notes")
         (subject (buffer-name))
         (content (if (use-region-p)
                      (buffer-substring-no-properties (region-beginning) (region-end))
                    (buffer-substring-no-properties (point-min) (point-max))))
         (created-date (message-make-date))
         (uuid (wilko/makeshift-uuid)))
    (unless (gnus-check-backend-function 'request-accept-article group)
      (error "%s does not support article importing" group))
    (with-current-buffer (gnus-get-buffer-create " *import file*")
      (erase-buffer)
      (goto-char (point-min))
      (insert "From: " (format "%s <%s>\n" user-full-name user-mail-address)
              "Subject: " subject "\n"
              "Date: " created-date "\n"
              "Message-ID: " (message-make-message-id) "\n"
              "X-Uniform-Type-Identifier: com.apple.mail-note\n"
              "X-Mail-Created-Date: " created-date "\n"
              "X-Universally-Unique-Identifier: " uuid "\n\n"
              content)
      (gnus-request-accept-article group nil t)
      (kill-buffer (current-buffer)))
    (message "Stored %s as note in %s"
             (if (use-region-p) "selection" "buffer") group)))

Calling it on a buffer or a selection, wait a few seconds, and the note's there - which almost feels too good to be true.

Wrapping Up

So with Gnus I'm able to:

All that in a pretty convient way without having to put too much thinking into making things work as its IMAP after all and Gnus is easily extensible and moldable to cover this usecase.