Delta Chat and Org-Mode
English
Hello everyone,
Today I’d like to share some thoughts about Delta Chat, a decentralized and encrypted messaging system. I’ve been testing it for many months, in different contexts. I’ll share my conclusions later on.
But first, I want to give some context about how I see the current messaging landscape — with its countless options — and how these mix with family, social, and work life.
I’ve been reflecting on privacy for quite some time — sometimes even in a slightly paranoid way — and along the way I came across a phrase that really stuck with me:
"Privacy is the power to selectively reveal oneself to the world."
— Eric Hughes, The Cypherpunk Manifesto (1993)
This quote perfectly sums up what privacy means to me. It’s not about hiding from the world. We all have responsibilities: family, work, taxes… there are things we inevitably have to share. But there are also parts of our lives we prefer to keep private, simply because they’re no one else’s business.
From this perspective, it’s not about completely eliminating WhatsApp, Teams, or even social networks (well… maybe those). It’s more about having options. Being able to choose tools that let us have truly private conversations, whenever and with whomever we want.
Because in the end, nothing is purely black or white. Everything has shades of gray, and privacy shouldn’t be the exception.
The same goes for the so-called “messaging app wars.” Which one should you choose? Well, as always — it depends. In my case, for work I use Teams and WhatsApp. For my hobbies I use Telegram. And for family and private matters, I use Deltachat.
Someone might say: “That’s a lot of apps! I just use one.” I’ve thought about that too, but consolidation is a myth — and let the first stone be cast by anyone who only has one social media app, or one banking app, or one restaurant loyalty app… Why should messaging be any different?
Here’s a great article about this excellent app: Everything You Think You Know About DeltaChat Is Wrong – Makefile.feld
Why Deltachat?
Here are the main reasons:
- Privacy. No personal data is required to register. None.
- Privacy. Messages are end-to-end encrypted, and no metadata is shared.
- Privacy. The protocol has been audited and passed all third-party tests.
- Simplicity. Up and running in under a minute. Contact exchange via QR.
- Simplicity. Based on the email protocol — nothing fancy, and battle-tested. The difference is that encryption is total: no headers or metadata are shared.
- Simplicity. Signing up on a Deltachat server takes 20 seconds.
- Interaction. You can have different apps in chats, from games to shopping lists, tasks, etc.
- Multi-device. Use the same account on as many devices as you want — they just need to be on the same Wi-Fi.
- Profile management. Multiple accounts can be configured, and managing them is very easy. This is useful, for example, for having a separate profile for public groups.
That’s the good things.
What about the bad ones?
Like any software still in active development, there are things to improve. For example, public groups — there’s little control over who joins.
Using email accounts on standard servers (Fastmail, Gmail, etc.) — i.e., those not configured for Deltachat — results in delays in push notifications. I used this setup for a while and noticed it. While the rest of my family got messages almost instantly, I had to refresh the app or I’d miss them entirely. I’d say Deltachat isn’t quite instant messaging.
It’s not a big deal for me, but video/audio calls are still in development. Multimedia (voice messages, photos, and videos) works fine, though there are issues with large video files.
What other geeky things can you do with Deltachat?
One of the most useful features is creating bots to help you with tasks. For example, I use one to manage my tasks.
On Mastodon I met @thibaut, who built a bot that takes messages and stores them in an org file. The article Deltachat inbox bot explains how he uses it and includes a link to the code.
Inside the repository there’s a file called org-commands.el
, which contains the function the bot calls for each incoming message. The data is passed to Emacs from the bot (written in c
):
emacs --batch \ --eval \'(load-file "./org-commands.el\")\' \ --eval=\'(setq attachment-filename \"%s\")\' \ /org/inbox.org -f capture-message
This launches an Emacs instance, loads the org-commands.el
file, passes the attached filename (if any) into attachment-file
, opens the /org/inbox.org
file, and runs the capture-message
function.
All of this runs in a container, which needs a specific configuration outside the scope of this article. The most important part here is org-commands.el
:
(require 'org) (require 'org-attach) (defun capture-message () (let* ((media-filename (and (not (string= attachment-filename "")) attachment-filename)) (task-string (concat "* [ ] " (when media-filename "ATTACHMENT ") (with-temp-buffer (insert-file-contents "/deltachat-db/msg_content.txt") (buffer-string)) "\ncreated ")) make-backup-files) (goto-char (point-max)) (insert task-string) (org-time-stamp-inactive '(16)) (when media-filename (let ((org-attach-method 'mv)) (org-attach-attach media-filename))) (save-buffer)))
How does it help my workflow?
I saw huge potential here. By slightly tweaking the bot, I could tailor it to my needs:
- Logging journal entries of any type, even with photos.
- Collecting web links from my phone.
- Anything else — for example, recording my daily weight.
To do this, the message needs to follow a certain format:
- The first line — specifically its first few characters — defines the type:
- If the first word is
http
, it’s a web link; the rest of the lines (words) will be used astags
daily
is a journal entry; the rest of the words on the line are used astags
peso
is an entry with the date and weight (not documented here)- Anything else becomes a normal TODO entry
- If the first word is
Let’s break this down.
Initial setup
We load the modules we need. Emacs starts fresh each time.
(require 'org) (require 'org-attach) (require 'url) (require 'xml)
Not sure if this is strictly necessary, but at first I had issues with special characters.
(prefer-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(setq default-buffer-file-coding-system 'utf-8)
I set the directory for storing attachments inside the container.
(setq org-attach-id-dir "/attach/")
capture-message
function
This is the modified version of the original function:
media-filename
stays the same.msg-content
is a list with the first line of the message and the rest as separate elements. Thesplit-first-line
function does this.msg-date
is the date of the message, used to timestamp entries.task-string
is the final text to insert into the org file. Thecapture-template
function handles this.
(defun capture-message () (let* ((media-filename (and (not (string= attachment-filename "")) attachment-filename)) (msg-content (split-first-line (with-temp-buffer (insert-file-contents "/deltachat-db/msg_content.txt") (buffer-string)))) (msg-date (or message-date 0)) (task-string (capture-template msg-content media-filename msg-date)) make-backup-files) (goto-char (point-max)) (insert task-string) (when media-filename (let ((org-attach-method 'mv)) (org-attach-attach media-filename))) (save-buffer)))
split-first-line
function
Splits the received text into two elements: the first line and the rest of the message.
(defun split-first-line (text) "Split TEXT into two parts: the first line and the rest of the text. Returns a list with (first-line rest-of-text)." (if (string-match "\n" text) (list (substring text 0 (match-beginning 0)) ;; First line (substring text (match-end 0))) ;; Rest of the text (list text ""))) ;; If no newline, rest is empty
get-tags
function
Formats the given text into orgmode-style tags, turning each word into a tag.
(defun get-tags-data (tags) (let ((otags (mapconcat 'identity (delete "" (split-string tags " ")) ":"))) (message "%s" otags) (if (not (string= otags "")) (format " :%s:" otags) "")))
capture-template
function
Returns the formatted text for the org file entry. It takes three arguments:
msg
, the list returned bysplit-first-line
- The attached file in
attach
- The message date in
msg-timestamp
(defun capture-template (msg attach msg-timestamp) "Build the templates for the different choices. 1. if beging with http, gets the link, and the second line are the tags space separted. 2. If first line begin with daily capture a Daily entry. Gets the rest of the line as tags for the heading. The second line as head of the entry. The rest as the content of the journal entry. 3. Any other message is a TODO Task." (let* ((first-line (car msg)) (other-lines (cadr msg)) (create-date (format-time-string "[%Y-%m-%d %a %H:%M]\n" msg-timestamp "Europe/Madrid")) web-data task-txt url-title url-description tags) (cond ((and (length> first-line 6) (string= "http" (downcase (substring-no-properties first-line 0 4)))) (setq web-data (get-url-data first-line) url-title (or (cdr (assoc 'title web-data)) "No title") url-description (cdr (assoc 'description web-data)) tags (get-tags-data other-lines)) (concat "* " url-title tags "\n:PROPERTIES:\n:CREATED: " create-date ":WEBLINK: " first-line "\n" ":END:\n" "[[" first-line "][" url-title "]]\n" (when url-description (concat "#+begin_quote\n" url-description "\n#+end_quote\n")) )) ((and (length> first-line 6) (string= "daily" (downcase (substring-no-properties first-line 0 5)))) (setq tags (and (length> first-line 6) (get-tags-data (substring-no-properties first-line 6))) msg (split-first-line other-lines)) (concat "* " (format-time-string "%H:%M " msg-timestamp "Europe/Madrid") (car msg) tags "\n:PROPERTIES:\n:CREATED: " create-date ":END:\n" (cadr msg))) (t (concat "* TODO " first-line (when attach ":ATTACH:") "\n:PROPERTIES:\n:CREATED: " create-date ":END:\n" other-lines))) ))
Special functions for documenting web links
These functions don’t currently work properly for me, but I use them in my workflow to complete the information before classifying a link.
With get-url-data
, we create a buffer with the url
content so we can search for the necessary info — usually the title and the description
meta tag, if present.
(defun get-url-data (url) (let ((data '()) (web-buffer (condition-case nil (url-retrieve-synchronously url t) (error nil)))) (when web-buffer (with-current-buffer web-buffer (push (search-info "<title>" nil) data) (push (search-info "<meta name=[\"]?description[\"]? " t) data))) data))
With search-info
, we look for data inside the buffer. The approach is to place the cursor right before what we need and copy the characters until the end of the <title>
or meta
tag. We differentiate because the closing tags are different.
(defun search-info (str meta) "Search STR tag and get the content, if META is not-nil, gets content attributte of the tag. " (let ((description nil) beg-tag end-tag) (goto-char (point-min)) (setq beg-tag (search-forward-regexp str nil t)) (when beg-tag (cond (meta (let* ((end-tag (search-forward ">" nil t)) (full-tag (buffer-substring-no-properties (- beg-tag (length str)) end-tag)) (parse-tag (libxml-parse-html-region (- beg-tag (length str)) end-tag)) ) (setq description (cons 'description (dom-find-meta-description parse-tag))))) (t (setq end-tag (1- (search-forward "<" nil t))) (setq description (cons 'title (decode-entities (buffer-substring-no-properties beg-tag end-tag)))))) )))
Function to re-encode special characters.
(defun decode-entities (html) (with-temp-buffer (save-excursion (insert html)) (xml-parse-string)))
Helper function for meta
search using the dom
module
(defun dom-find-meta-description (tree) "Finds the content of <meta name=\"description\"> using dom.el." (let ((meta (dom-by-tag tree 'meta))) (when-let ((meta-node (seq-find (lambda (el) (string= (dom-attr el 'name) "description")) meta))) (dom-attr meta-node 'content))))
Conclusions
I think I’m going to stick with Deltachat and try to bring as many people on board as I can. Yes, it’s still in development, but the peace of mind knowing my messages are only read by the intended recipients is worth it.
By the way, one of the conversations I had with my kids was: “Another app, Dad?!” — which ended quickly when I grabbed their phones and found the 200 apps they have. Among them: Snapchat, Instagram, WhatsApp… all doing basically the same thing. I don’t think one more will hurt.
The integration I now have with org-mode
is something I’ve never had with any other app. In the future, I’d like to be able to send messages from Emacs as well. But that’s another story.
Note: This time I translated with some help, so please forgive me if it’s not perfect.