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:
- Transferring an iOS note to either my
denote
journal or append it to a existing note there; or create a org-mode task out of it. - Edit an iOS note while not being on my phone (I mostly do this with grocery shopping lists) from within Emacs.
- Quickly create a note that'll be accessible on my phone.
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:
- acces my iOS notes app notes...
- ...which allows me to store them to
denote
via capture templates. - edit them when necessary.
- create new notes when needed...
- ... and send arbitrary buffers or regions to the Notes IMAP folder from anywhere inside Emacs.
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.