Communication Setup in Emacs

Much ink has been spilled on…how best to spill ink?


Updated: 2026-09-01 Tue 13:49

“There are no ordinary people. You have never talked to a mere mortal. Nations, cultures, arts, civilizations - these are mortal, and their life is to ours as the life of a gnat. But it is immortals whom we joke with, work with, marry, snub and exploit - immortal horrors or everlasting splendors. This does not mean that we are to be perpetually solemn. We must play. But our merriment must be of that kind (and it is, in fact, the merriest kind) which exists between people who have, from the outset, taken each other seriously - no flippancy, no superiority, no presumption.” – C.S. Lewis


<- Elfeed Config <- Email Config <- Ement Config <- GNU Emacs

1. Introduction

D unbar’s number(s) may vary widely between individuals but what man hasn’t been deluged by the onslaught of the information age? Here’s how I try keep things under control. This article details the setup of my communication channels in Emacs on Debian GNU/Linux. Workflow configuration specific to myself is relegated to my dotfiles.

2. Instant Messaging - Meta with Matrix and Ement

Matrix is a protocol for secure, decentralized real time communication in a similar fashion as Email. Three options exist:

  1. Self-host a Matrix server
  2. Hosted Matrix server at beeper
  3. The matrix.org Matrix server (Create account here)

Options 1. and 2. can be bridged to third party services at your convenience while 3. has no bridging capabilities as of this writing. The focus of this article is option 1. although 2. could be possible to use with Ement.el. I am living the dream with Facebook Messenger, WhatsApp, Slack, Discord, Google Voice and SMS (Google Messages) bridged onto a self-hosted Matrix server. A server that has Emacs as the client on my computer and element on mobile. The whole process shouldn’t take you more than 1 hour. My own notes follow, hope that helps.

2.1. Tuwunel server Installation

Prerequisites:

  • A domain name
  • A (VPS) virtual private server with SSH access
  • File editor to modify files on your VPS

I am installing the Tuwunel (as of writing, v1.7.0) implementation of the Matrix protocol on Debian 13. Tuwunel, the official successor to conduwuit, is a single Rust binary with an embedded RocksDB database and prebuilt .deb packages; compared to my old matrix-synapse deployment there is no Python virtualenv and no PostgreSQL to administer. Do note that automatic migration from Synapse is planned but not yet supported, so I started with a fresh database. Replace all instances of matrix.bhw.name with your domain name matrix.example.com. For the older matrix-synapse versions of this article see commit 2f4d9517 (Debian 12, distribution package) or commit befe72fb (Debian 13, building from source). The steps outlined here are only what’s required for a single user to self-host a tuwunel server with user defined bridges. Voice/video calling via MatrixRTC and LiveKit is supported by tuwunel but out of scope for this article.

  # My server environment,
  uname -a
  # Linux oci-a1-flex 6.12.90+deb13.1-cloud-arm64 #1 SMP Debian 6.12.90-2 (2026-05-27) aarch64 GNU/Linux
  # On the server machine, download the prebuilt .deb from
  # https://github.com/matrix-construct/tuwunel/releases
  # Note I am downloading the aarch64 (arm64) build.
  curl -L -O https://github.com/matrix-construct/tuwunel/releases/download/v1.7.0/v1.7.0-release-all-aarch64-v8-linux-gnu-tuwunel.deb
  sudo apt install ./v1.7.0-release-all-aarch64-v8-linux-gnu-tuwunel.deb
  # Upgrading tuwunel later is the same, download the newer .deb and
  # apt install it again. An apt repository is also available,
  # https://matrix-construct.github.io/tuwunel/deploying/debian.html

The package ships a hardened systemd unit, creates a tuwunel system user and stores its embedded RocksDB database under /var/lib/tuwunel/; there is no hand-written service file and no PostgreSQL setup as matrix-synapse required. /etc/tuwunel/tuwunel.toml is where we configure our tuwunel server. Please read through it, and if you are going to copy paste it, the minimal change needed is to server_name. Registration is enabled with a token only long enough to create our user below.

  # Configuration file for Tuwunel.
  #
  # This is a TOML file. For a complete accounting of each option see
  # https://matrix-construct.github.io/tuwunel/configuration/examples.html
  [global]
  server_name = "matrix.bhw.name"
  # Embedded RocksDB database. No PostgreSQL required.
  database_path = "/var/lib/tuwunel"
  port = 8008
  allow_federation = true
  # Set allow_registration to false and restart tuwunel
  # once your account is created below.
  allow_registration = true
  registration_token = "correct-horse-battery-staple"

  [global.well_known]
  client = "https://matrix.bhw.name"

Allow inbound HTTPS traffic on port 443 and port 8448 if you desire to use Matrix federation.

  firewall-cmd --get-services
  firewall-cmd --zone=public --permanent --add-port=443/tcp
  firewall-cmd --zone=public --permanent --add-port=8448/tcp
  firewall-cmd --reload
  firewall-cmd --list-ports

We are going to reverse proxy the Matrix server with Caddy. sudo apt install caddy and make sure /etc/caddy/Caddyfile includes the following content. Start Caddy with systemctl start caddy.service. Tuwunel listens on a single port and there is no separate /_synapse path to proxy.

  matrix.bhw.name, matrix.bhw.name:8448 {
      reverse_proxy localhost:8008
  }

We can now enable the server and perform a quick sanity check by visiting from your web browser https://matrix.bhw.name/_matrix/. You should get an error code response of M_UNRECOGNIZED: Not Found.

  # The .deb ships tuwunel.service, aliased as matrix-tuwunel.service.
  systemctl enable --now tuwunel
  systemctl status tuwunel

Now our aim is to create an administrator user. There is no need for matrix-synapse’s shared secret registration workarounds and database surgery; tuwunel grants the first user to register admin privileges by joining them to the admin room. Opening my web browser to https://app.element.io/#/register – note that we must change the “Homeserver” from matrix.org to https://matrix.bhw.name – I create a user, supplying the registration_token set in tuwunel.toml. Our new user is invited to the admin room, from which the server is administered by messaging !admin commands. With the account created, close registration.

  # In /etc/tuwunel/tuwunel.toml set,
  # allow_registration = false
  systemctl restart tuwunel

Now we can bridge to some 3rd party services!

2.2. WhatsApp Bridge Installation

Link our WhatsApp account and our Matrix server. Download the correct prebuilt executable for your architecture from the WhatsApp Bridge Documentation and extract it to /opt/mautrix-whatsapp/.

# Create folder if it doesn't exist. Note I am downloading arm64 executable.
mkdir /opt/mautrix-whatsapp/
# If updating, make sure to 'systemctl stop mautrix-whatsapp'.
curl -L -o /opt/mautrix-whatsapp/mautrix-whatsapp https://github.com/mautrix/whatsapp/releases/download/v0.2607.0/mautrix-whatsapp-arm64
# curl -L -o /opt/mautrix-gvoice/mautrix-gvoice https://github.com/mautrix/gvoice/releases/download/v0.2605.0/mautrix-gvoice-arm64
# curl -L -o /opt/mautrix-gmessages/mautrix-gmessages https://github.com/mautrix/gmessages/releases/download/v0.2605.0/mautrix-gmessages-arm64
# curl -L -o /opt/mautrix-slack/mautrix-slack https://github.com/mautrix/slack/releases/download/v0.2607.0/mautrix-slack-arm64
# curl -L -o /opt/mautrix-meta/mautrix-meta https://github.com/mautrix/meta/releases/download/v0.2607.0/mautrix-meta-arm64
# curl -L -o /opt/mautrix-discord/mautrix-discord https://github.com/mautrix/discord/releases/download/v0.7.6/mautrix-discord-arm64
cd /opt/mautrix-whatsapp
# Grant executable permissiosn to the "mautrix-whatsapp" go executable.
chmod +x mautrix-whatsapp
# Generate an example config file.
./mautrix-whatsapp -e

Open config.yaml in your favourite editor and you will have to edit the following options inside that file. Since tuwunel brings no PostgreSQL along, I point each bridge at its own SQLite database file; no database setup is required at all. If you do run PostgreSQL, note that each bridge must have its own database.

  # For mautrix-meta you will need to specify
  # network:
  #    mode: facebook (or instagram)
  # https://docs.mau.fi/bridges/general/initial-config.html#mautrix-meta
  homeserver:
      address: http://localhost:8008
      domain: matrix.bhw.name
  appservice:
      database:
          # OR type: postgres
          type: sqlite3-fk-wal
          # OR uri: postgres://dbuser:dbpass@localhost/dbname?sslmode=disable
          uri: file:/var/lib/tuwunel/mautrix-whatsapp-db?_txlock=immediate
  bridge:
      permissions:
          "matrix.bhw.name": user
          "@ben:matrix.bhw.name": admin

Now we can generate an appservice registration file.

  # Generate the appservice registration file.
  cd /opt/mautrix-whatsapp/
  ./mautrix-whatsapp -g

Unlike matrix-synapse, where every registration file is listed under app_service_config_files in homeserver.yaml followed by a homeserver restart, tuwunel registers appservices at runtime from the admin room, the room our first user was invited to upon registration. Send the following message to the admin room, pasting in the contents of registration.yaml.

  !admin appservices register
  ```yaml
  id: whatsapp
  # ...rest of /opt/mautrix-whatsapp/registration.yaml
  ```

The registration persists in tuwunel’s database, surviving restarts, and registering an existing ID replaces the old entry. See also !admin appservices list and !admin appservices unregister <id>. Then create a systemd service file at /etc/systemd/system/mautrix-whatsapp.service

  [Unit]
  Description=mautrix-whatsapp bridge

  [Service]
  Type=exec
  User=root
  WorkingDirectory=/opt/mautrix-whatsapp
  ExecStart=/opt/mautrix-whatsapp/mautrix-whatsapp
  Restart=on-failure
  RestartSec=30s

  [Install]
  WantedBy=multi-user.target

Enable and start the mautrix-whatsapp.service.

  # Will start this service at boot.
  systemctl enable mautrix-whatsapp.service
  systemctl start mautrix-whatsapp.service
  # When updating a bridge, only the bridge service needs restarting;
  # the appservice registration persists in tuwunel's database.

Login to https://app.element.io/#/login and refer to the documentation for initial setup. In my experience, all mautrix bridges are excellent. After this setup tutorial, other bridges such as Meta and Google Messenger are similar; remember to register each bridge’s registration.yaml in the admin room. Also note that mautrix-meta requires extra configuration. For mautrix-gvoice you should login as with mautrix-meta but copy as cURL (bash) the network XHR request, log?format=json&hasfast=true&authuser=0, and paste it to the bot to login.

2.3. Conclusion

Install ement.el and login,

  ;; First login; subsequent logins will used a cached session if
  ;; `ement-save-sessions' is TRUE. Also run `ement-disconnect'
  ;; to save login in `~/.cache/ement.el'
  (ement-connect :user-id "@ben:matrix.bhw.name"
                 :password  "password"
                 :uri-prefix "https://matrix.bhw.name")

My own config is available here. Three parting thoughts. I am somewhat optimistic about Matrix’s longevity as the German armed forces has adopted the standard. Secondly, Emacs users often have to deal with their complex configurations falling apart on mobile phones but this is not that case with the element client available on android. Last but not least, is my thanks for the many hours of love poured in by these open source developers <3.

References

Create a Chat Server Using Matrix Synapse and Element on Debian 11 | Vultr Docs

3. Email - GMail with Mbsync and Mu4e

Mbsync synchronizes email from your IMAP server (Gmail/Outlook/Proton) and stores it locally as specified in ~/.mbsyncrc. Mu uses the open source Xapian to index the local email database. Mu4e (Mu for Emacs) provides an user interface. Sendmail manages the connection with the remote SMTP servers during the sending of email, selecting the server according to the email specified in the “from” field of the outgoing mail. One can specify more than one address.

Operation Analogy (detailed explanation):

  1. Getting mail (the mail truck filling mailbox) : mbsync
  2. Indexing and reading mail (inbox tray & your office desk): mu + mu4e
  3. Sending mail (Dropping envelope off to post office): sendmail
  # My workstation environment,
  uname -a
  # Linux bhw-yoga 6.6.87.2-microsoft-standard-WSL2 #1 SMP PREEMPT_DYNAMIC Thu Jun  5 18:30:46 UTC 2025 x86_64 GNU/Linux
  # Isync is the name of the program, but mbsync is the name of the binary.
  # See and tangle .mbsyncrc in dotfiles.org.
  sudo apt install mu4e gnutls-bin sendmail isync

dotfiles.org is not public, but here is the relevant excerpt. Please note to access the Gmail mail server you need an app password. M-x Woman RET mbsync to read isync’s manpage to understand what’s going on. I do not delete email, instead I mark it as read and move it out of my inbox to “All mail”.

  IMAPAccount gmail
  Host imap.gmail.com
  User myemailaddress@gmail.com
  Pass p@ssword
  SSLType IMAPS
  PipelineDepth 1

  IMAPStore gmail-remote
  Account gmail

  MaildirStore gmail-local
  Path /home/ben/project-jerome/email-archive/
  Inbox /home/ben/project-jerome/email-archive/inbox/
  SubFolders Verbatim

  Channel gmail
  Master :gmail-remote:
  Slave :gmail-local:
  Patterns * ![Gmail]* !inbox "[Gmail]/All Mail"
  Create Slave
  Sync PullNew
  CopyArrivalDate yes
  SyncState *
  # Inital sync will take much longer than consequent syncs.
  mbsync -a -V
  # See mu4e manual section 2.4
  # https://www.djcbsoftware.nl/code/mu/mu4e/
  mu init --maildir=~/project-jerome/email-archive/ --my-address=myemailaddress@gmail.com
  mu index
  • [X] Install the mu4e layer. mu4e=installation-path is “/usr/share/emacs/site-lisp/elpa/mu4e-1.10.8”
  • [X] Install the org layer (for the included org-mime package)

For my usage patterns, see heading “Mu4e Config”. Maybe I’ll self-host someday?

3.1. Viewing HTML email in your internet browser

Microsoft Edge browser disables viewing file:// links by default for security reasons. To configure Microsoft Edge to allow this, we must download and install the Microsoft Edge administrative template. Then we edit the registry as shown in the documentation, creating a new regkey at Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Policies|Microsoft\Edge and creating a new REG_DWORD key IntranetFileLinksEnabled set the data to 1. Then we press WindowsKey + r and run gpedit.msc to open the “Local Group Policy Editor” and navigate to “Computer Configuration/Administrative Templates/Microsoft Edge/Content settings/Allow intranet zone file URL links from Microsoft Edge to open in Windows File Explorer/” and enable it. Last but not least, make sure in Windows settings, that Apps/Default Apps/Microsft Edge has set the default file types or link types for “.html”. Lastly we must prepend file://///wsl$/Debian/ instead of the default file:/// to access Debian files through Windows.

  (setf browse-url-generic-program  "/mnt/c/Windows/System32/cmd.exe"
        browse-url-generic-args     '("/c" "start")
        browse-url-browser-function #'browse-url-generic
        ;; Needed for `mu4e-action-view-in-browser'
        browse-url-filename-alist
        '(("^/\\(ftp@\\|anonymous@\\)?\\([^:/]+\\):/*" . "ftp://\\2/")
          ("^/\\([^:@/]+@\\)?\\([^:/]+\\):/*" . "ftp://\\1\\2/")
          ;; For gnus-article-browse-html-article on Windows Subsystem for Linux.
          ("^/+" . "file://///wsl$/Debian/")))

References

Search-oriented tools for Unix-style mail | Mark J. Nelson Streamline Your E-mail Management with mu4e - Emacs Mail - YouTube Reading IMAP email in Emacs Configure mu4e and msmtp - Tushar Tyagi

4. Web logs & Web feeds - Org Publish and Elfeed

I am aware of two options to read RSS/atom feeds in Emacs. GNUS with the nnrss backend and elfeed. Having used both, I prefer elfeed. If I could wave a magic wand, I would make GNUS non-blocking/giving Emacs a concurrency story a la Common Lisp. Install the elfeed package or layer. A couple of tips,

Org Publish is my preferred software for blogging because it allows my blog articles to continue as my part of my personal notes, integrated with my personal knowledge base and my TODO lists. This reduces the publishing friction to near zero. For single author blogs, I’d say this is the most important factor to keep your articles up to date. A little garden on the world wide web you curate for the sake of the whole online ecosystem.

See the “Elfeed config”, and “Ox Publish Config” headings for usage.

I have ordered these subheadings (Instant Messaging > Email > News & Blogs) by increasing length of both response time and content. You can imagine that various author(s) have created great literature representing the eternal word and truths that we spend the rest of our lives communicating with through words and actions, though often it takes time to digest these. Emacs, of course, can help with that :). A future blog article shall be on just that; how I use Emacs to assist in taking notes on books, spaced repetition and concept mapping.