commit 5a12acfccb3a471082d49aa862d5845bd0edd95e
parent a41cecf8414e7b593e6ea0ba0d77b81eea93c57f
Author: Dan Callaghan <djc@djc.id.au>
Date: Sat, 3 Oct 2026 19:29:12 +1000
add a script to generate screenshots
Diffstat:
7 files changed, 591 insertions(+), 0 deletions(-)
diff --git a/contrib/nix/ruby4.0-shell.nix b/contrib/nix/ruby4.0-shell.nix
@@ -37,4 +37,5 @@ pkgs.mkShell {
gems.wrappedRuby
pkgs.pandoc
];
+ passthru = { inherit pkgs gems; };
}
diff --git a/contrib/screenshot-generator/.gitignore b/contrib/screenshot-generator/.gitignore
@@ -0,0 +1 @@
+result
diff --git a/contrib/screenshot-generator/default.nix b/contrib/screenshot-generator/default.nix
@@ -0,0 +1,250 @@
+let
+ sup-shell = import ../nix/ruby4.0-shell.nix;
+ pkgs = sup-shell.pkgs;
+ fileset = pkgs.lib.fileset;
+ sup = fileset.toSource {
+ root = ../..;
+ fileset = fileset.difference ../.. ../../contrib;
+ };
+ ht = pkgs.rustPlatform.buildRustPackage (finalAttrs: {
+ pname = "ht";
+ version = "0.4.0";
+ src = pkgs.fetchFromGitHub {
+ owner = "andyk";
+ repo = "ht";
+ tag = "v${finalAttrs.version}";
+ hash = "sha256-LKa5nnGhQITf0SDzE17NUJ9KQ6Soq6jOBWoTONRxiCc=";
+ };
+ cargoHash = "sha256-FlR7bvQAZZIJaONOGsZUtSuUOvAVeDXJQaEkvXYxp1w=";
+ });
+ deps = with pkgs; [
+ sup-shell.gems.wrappedRuby
+ gnupg
+ ncurses
+ which
+ rsync
+ libfaketime
+ ht
+ ];
+
+ now = "2026-04-21 20:00:00";
+ lists.supmua = pkgs.fetchgit {
+ name = "supmua-list";
+ url = "https://supmua.dev/lists/supmua/0";
+ rev = "5bb23abca5490dc0d946312a8dbc3ece92bbfa95";
+ hash = "sha256-c25+ZfDOz8oSx95p1Mq7LI35n1F4IFDWog4YZUn644M=";
+ deepClone = true;
+ };
+ lists.sup-devel = pkgs.fetchgit {
+ name = "sup-devel-list";
+ url = "https://supmua.dev/lists/sup-devel/0";
+ rev = "5e44841d194d5bfb581022d5951e0964a1d85edd";
+ hash = "sha256-g9tPF97noIVoc3xfWM6D+aNDWOyn2SX967YsO4jabpM=";
+ deepClone = true;
+ };
+ lists.sup-talk = pkgs.fetchgit {
+ name = "sup-talk-list";
+ url = "https://supmua.dev/lists/sup-talk/0";
+ rev = "93f1725cde6bb046a7a086eb1b99342a3371802c";
+ hash = "sha256-ksbTPTcE7ueGKWjC+rC1xkiRF4/MpJcSqJhoA735G/Y=";
+ deepClone = true;
+ };
+ lists-as-maildir = builtins.mapAttrs (
+ name: pirepo:
+ pkgs.stdenv.mkDerivation {
+ name = "${name}-as-maildir";
+ nativeBuildInputs = with pkgs; [ git ];
+ buildCommand = ''
+ mkdir -p $out/{cur,new,tmp}
+ export GIT_DIR=${pirepo}/.git
+ git rev-list HEAD | while read sha ; do
+ git show $sha:m >$out/new/$sha:2,S
+ done
+ '';
+ }
+ ) lists;
+ sup-is-plain-text = ./sup-is-plain-text.msg;
+ sup-is-plain-text-applied = ./sup-is-plain-text-applied.msg;
+ misc-maildir = pkgs.stdenv.mkDerivation {
+ name = "sup-screenshots-misc-maildir";
+ nativeBuildInputs = with pkgs; [
+ curl
+ python3
+ ];
+ buildCommand = ''
+ mkdir -p $out/{cur,new,tmp}
+ cp ${sup-is-plain-text} $out/cur/sup-is-plain-text:2,S
+ cp ${sup-is-plain-text-applied} $out/cur/sup-is-plain-text-applied:2,S
+ '';
+ };
+
+ # Build a fake .sup directory for screenshots.
+ sup-base = pkgs.stdenv.mkDerivation {
+ name = "sup-base-for-screenshots";
+ nativeBuildInputs = deps;
+ env = {
+ TZ = "UTC";
+ USER = "nobody";
+ LANG = "C.UTF-8";
+ };
+ buildCommand = ''
+ mkdir -p $out
+
+ cat >$out/config.yaml <<EOF
+ ---
+ :accounts:
+ :default:
+ :name: Sup User
+ :email: sup@example.invalid
+ :sendmail: /bin/false
+ :sync_back_to_maildir: false
+ :editor: /bin/false
+ :poll_interval: 86400
+ EOF
+
+ cat >$out/sources.yaml <<EOF
+ ---
+ - !<tag:supmua.org,2006-10-01/Redwood/Maildir>
+ uri: maildir://${lists-as-maildir.supmua}
+ usual: true
+ archived: false
+ id: 1
+ labels: [supmua]
+ - !<tag:supmua.org,2006-10-01/Redwood/Maildir>
+ uri: maildir://${lists-as-maildir.sup-devel}
+ usual: true
+ archived: false
+ id: 2
+ labels: [sup-devel]
+ - !<tag:supmua.org,2006-10-01/Redwood/Maildir>
+ uri: maildir://${lists-as-maildir.sup-talk}
+ usual: true
+ archived: false
+ id: 3
+ labels: [sup-talk]
+ - !<tag:supmua.org,2006-10-01/Redwood/Maildir>
+ uri: maildir://${misc-maildir}
+ usual: true
+ archived: false
+ id: 4
+ labels: []
+ EOF
+
+ export SUP_BASE=$(readlink -f $out)
+ faketime -m "${now}" ruby ${sup}/bin/sup-sync
+ '';
+ };
+
+ # For each scene in scenes.yaml, drive sup in ht (a headless terminal)
+ # and capture the final screen state.
+ screenshots-ansi = pkgs.stdenv.mkDerivation {
+ name = "sup-screenshots-ansi";
+ nativeBuildInputs = deps;
+ env = {
+ TZ = "UTC";
+ USER = "nobody";
+ LANG = "C.UTF-8";
+ };
+ buildCommand = ''
+ faketime -m "${now}" ruby ${./shoot.rb} \
+ --sup-base=${sup-base} \
+ --sup=${sup}/bin/sup \
+ --scenes-yaml=${./scenes.yaml} \
+ --out=$out
+ '';
+ };
+
+ # Render the ANSI screens in a headless Wayland terminal.
+ iosevka = pkgs.iosevka-bin.override { variant = "SS09"; };
+ fonts-conf = pkgs.writeText "sup-screenshots-fonts.conf" ''
+ <?xml version="1.0"?>
+ <!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
+ <fontconfig>
+ <dir>${iosevka}/share/fonts</dir>
+ <dir>${pkgs.dejavu_fonts.minimal}/share/fonts</dir>
+ <cachedir prefix="xdg">fontconfig</cachedir>
+ <config></config>
+ </fontconfig>
+ '';
+ sway-conf = pkgs.writeText "sup-screenshots-sway.conf" ''
+ output HEADLESS-1 mode 3840x2160 scale 2
+ output HEADLESS-1 background #000000 solid_color
+ default_border none
+ default_floating_border none
+ gaps inner 0
+ gaps outer 0
+ xwayland disable
+ '';
+ foot-conf = pkgs.writeText "sup-screenshots-foot.ini" ''
+ [main]
+ font=Iosevka Term:size=12
+ [colors-dark]
+ # https://github.com/tinted-theming/tinted-terminal/blob/main/themes/foot/base16-pop.ini
+ foreground=d0d0d0
+ background=000000
+ regular0=000000 # black
+ regular1=eb008a # red
+ regular2=37b349 # green
+ regular3=f8ca12 # yellow
+ regular4=0e5a94 # blue
+ regular5=b31e8d # magenta
+ regular6=00aabb # cyan
+ regular7=d0d0d0 # white
+ bright0=505050 # bright black
+ bright1=eb008a # bright red
+ bright2=37b349 # bright green
+ bright3=f8ca12 # bright yellow
+ bright4=0e5a94 # bright blue
+ bright5=b31e8d # bright magenta
+ bright6=00aabb # bright cyan
+ bright7=ffffff # bright white
+ 16=f29333
+ 17=7a2d00
+ 18=202020
+ 19=303030
+ 20=b0b0b0
+ 21=e0e0e0
+ '';
+ screenshots = pkgs.stdenv.mkDerivation {
+ name = "sup-screenshots";
+ nativeBuildInputs = with pkgs; [
+ dbus
+ foot
+ ghostty
+ grim
+ jq
+ sway
+ procps
+ ];
+ env = {
+ LANG = "C.UTF-8";
+ FONTCONFIG_FILE = fonts-conf;
+ WLR_BACKENDS = "headless";
+ WLR_RENDERER = "pixman";
+ LIBGL_ALWAYS_SOFTWARE = "1";
+ WLR_LIBINPUT_NO_DEVICES = "1";
+ };
+ buildCommand = ''
+ export HOME=$(mktemp -d)
+ export XDG_RUNTIME_DIR=$HOME/run
+ mkdir -p $XDG_RUNTIME_DIR
+ export SWAYSOCK=$XDG_RUNTIME_DIR/sway.sock
+ dbus-run-session --config-file ${pkgs.dbus}/share/dbus-1/session.conf sway -c ${sway-conf} &
+ while ! test -S $SWAYSOCK ; do sleep 0.1 ; done
+ export WAYLAND_DISPLAY=wayland-1
+
+ mkdir -p $out
+ for f in ${screenshots-ansi}/* ; do
+ rm -f done
+ foot -c ${foot-conf} -W 120x28 --hold sh -c "echo -ne '\e[H\e[?25l' ; cat $f ; touch done" &
+ foot_pid=$!
+ while ! test -f done ; do sleep 0.1 ; done
+ geometry=$(swaymsg -t get_tree | jq -r 'recurse(.nodes[]?) | select(.app_id == "foot").geometry | "\(.x),\(.y) \(.width)x\(.height)"')
+ grim -g "$geometry" $out/$(basename ''${f%.ansi}).png
+ kill -TERM $foot_pid
+ wait $foot_pid || :
+ done
+ '';
+ };
+in
+screenshots
diff --git a/contrib/screenshot-generator/scenes.yaml b/contrib/screenshot-generator/scenes.yaml
@@ -0,0 +1,51 @@
+---
+- name: inbox
+ steps:
+ - keys: ["N", "N", "N"]
+
+- name: search-results
+ steps:
+ - keys: ["F"]
+ wait: 'Search all messages'
+ - keys: ["maildir", "Enter"]
+ wait: '\[search-results-mode\]'
+ - keys: ["N", "N", "N"]
+ - keys: ["a", "j", "a", "j", "a", "j", "a", "j", "a", "j", "a", "j", "a", "j", "a", "j", "a", "j", "a", "j", "a", "N", "a", "j", "a", "j", "a", "j", "a", "N", "a", "N", "a", "j", "a", "j", "a", "j", "a", "j", "a", "j", "a", "j", "a"]
+ - keys: ["1", "j", "j", "j"]
+
+- name: thread-view
+ steps:
+ - keys: ["j", "j", "j", "j", "j", "j", "j", "j", "j", "j", "N", "k"]
+ - keys: ["Enter"]
+ wait: '\[thread-view-mode\].*line \d+ of \d+'
+ - keys: ["Enter", "Enter", "Enter", "Enter", "Enter"]
+ - keys: ["K", "K", "K", "K", "K", "Left", "Left"]
+
+- name: reply
+ steps:
+ - keys: ["j", "j", "j", "j", "j", "j", "j"]
+ - keys: ["G"]
+ wait: '\[reply-mode\]'
+ - keys: ["PageDown", "k", "k", "k"]
+
+- name: help
+ steps:
+ - keys: ["?"]
+ wait: '\[help-mode\]'
+
+- name: log
+ steps:
+ - keys: ["F"]
+ wait: "Search all messages"
+ - keys: ["maildir", "Enter"]
+ wait: '\[search-results-mode\]'
+ - keys: ["!", "!"]
+ - keys: [";", "j", "j", "Enter"]
+ wait: '\[log-mode\]'
+
+- name: console
+ steps:
+ - keys: ["~"]
+ wait: '\[console-mode\]'
+ - keys: ['Index.parse_query "is:unread"', "Enter"]
+ - keys: ['Index.num_results_for :label => "supmua"', "Enter"]
diff --git a/contrib/screenshot-generator/shoot.rb b/contrib/screenshot-generator/shoot.rb
@@ -0,0 +1,127 @@
+require 'fileutils'
+require 'json'
+require 'open3'
+require 'optparse'
+require 'shellwords'
+require 'tmpdir'
+require 'yaml'
+
+SIZE = "120x28" # terminal grid dimensions
+IDLE = 0.3 # settling time in seconds for the terminal
+
+class Session
+ def initialize command, env
+ ht_command = ["ht", "--size", SIZE,
+ "--subscribe", "init,output,snapshot",
+ "--", Shellwords.join(command)]
+ @stdin, @stdout, @process = Open3.popen2 env, *ht_command
+ @stdin.sync = true
+ raise "ht did not send init event" unless read_event["type"] == "init"
+ end
+
+ def write command
+ @stdin.puts JSON.generate(command)
+ end
+
+ def read_event
+ line = @stdout.gets or return nil
+ JSON.parse line
+ end
+
+ def send_keys keys
+ write :type => "sendKeys", :keys => keys
+ end
+
+ ## Wait until at least IDLE seconds has passed with no more events.
+ def settle
+ loop do
+ return unless IO.select([@stdout], nil, nil, IDLE)
+ return unless read_event
+ end
+ end
+
+ def snapshot
+ write :type => "takeSnapshot"
+ loop do
+ event = read_event
+ return event["data"] if event["type"] == "snapshot"
+ end
+ end
+
+ def expect pattern
+ settle
+ screen = snapshot
+ raise "/#{pattern.source}/ not found" unless screen["text"] =~ pattern
+ screen
+ end
+
+ def dump_screen
+ $stderr.puts "--- screen was ---"
+ $stderr.puts snapshot["text"]
+ $stderr.puts "------------------"
+ rescue
+ $stderr.puts "--- no screen ---"
+ end
+
+ def close
+ @stdin.close unless @stdin.closed?
+ @stdout.read
+ @stdout.close
+ end
+end
+
+options = {}
+OptionParser.new do |parser|
+ parser.on "--sup-base PATH"
+ parser.on "--sup PATH"
+ parser.on "--scenes-yaml PATH"
+ parser.on "--out PATH"
+end.parse! into: options
+
+FileUtils.mkdir_p options[:out]
+
+scenes = YAML.load_file options[:"scenes-yaml"]
+scenes.each do |scene|
+ name = scene["name"]
+ steps = scene["steps"] || []
+
+ ## Make a fresh copy of SUP_BASE for this run of sup.
+ base = Dir.mktmpdir "sup-screenshots-#{name}-"
+ FileUtils.cp_r Dir.glob(File.join(options[:"sup-base"], "*")), base
+ FileUtils.chmod_R "u+w", base
+
+ puts "[#{name}] starting sup"
+ session = nil
+ begin
+ session = Session.new(
+ ["ruby", options[:sup], "--no-threads", "--no-initial-poll"],
+ {"SUP_BASE" => base, "SUP_LOG_LEVEL" => "debug"})
+
+ screen = session.expect /\[inbox-mode\]/
+
+ steps.each do |step|
+ keys = step["keys"]
+ puts "[#{name}] send #{keys.inspect}"
+ session.send_keys keys
+
+ screen = if step["wait"].nil?
+ session.settle
+ session.snapshot
+ else
+ session.expect Regexp.new(step["wait"])
+ end
+ end
+
+ ansi = screen["seq"]
+ ansi = ansi.gsub "\u009b", "\e[" # map CSI emitted by ht to ESC [
+ path = File.join options[:out], "#{name}.ansi"
+ File.write path, ansi
+ puts "[#{name}] wrote #{path}"
+ rescue RuntimeError => e
+ $stderr.puts "[#{name}] #{e.message}"
+ session.dump_screen if session
+ raise
+ ensure
+ session.close if session
+ end
+end
diff --git a/contrib/screenshot-generator/sup-is-plain-text-applied.msg b/contrib/screenshot-generator/sup-is-plain-text-applied.msg
@@ -0,0 +1,83 @@
+Authentication-Results: mail-a.sr.ht; dkim=pass header.d=ddevault.org header.i=@ddevault.org
+Received: from mx.freebitcoin.gay (mx.freebitcoin.gay [92.65.121.143])
+ by mail-a.sr.ht (Postfix) with ESMTPS id 13F73200E0
+ for <~sircmpwn/public-inbox@lists.sr.ht>; Mon, 20 Apr 2026 07:26:50 +0000 (UTC)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ddevault.org;
+ s=_bikeshed; t=1776670002;
+ h=from:from:reply-to:subject:subject:date:date:message-id:message-id:
+ to:to:cc:mime-version:mime-version:content-type:content-type:
+ in-reply-to:in-reply-to:references:references;
+ bh=/yTIwruVpbRgzboJc+lUtE3TsCGqQmxSUE8vJiuBYB0=;
+ b=TfqhiwTaygLnt3sbmZNT1qMNIcaPYGPyTMRJpNOsfALmwG5g91xf1RVO8BpnSB4D45LZMP
+ h26PRpF2bBW8oRYRfaSsMwiSl0T2ucPlH08rIWPNBuq4roZAvPH+Ga6RKp9RcNhSsMMvwh
+ 2h9/1KDKFNUA2P/kdRjlifSjaCsnmKc=
+Received: from taiga (mx.freebitcoin.gay [92.65.121.143])
+ by mail.freebitcoin.gay (OpenSMTPD) with ESMTPSA id 7314ef4d (TLSv1.3:TLS_AES_256_GCM_SHA384:256:NO);
+ Mon, 20 Apr 2026 09:26:42 +0200 (CEST)
+Received: by taiga (Postfix, from userid 1000)
+ id 9EFA3BA002F; Mon, 20 Apr 2026 09:26:49 +0200 (CEST)
+Mime-Version: 1.0
+Content-Type: multipart/signed;
+ boundary=a036da96ce29d60f01d793f8b11ad804cb7659b0e414c63118649b669cdc;
+ micalg=pgp-sha512; protocol="application/pgp-signature"
+Date: Mon, 20 Apr 2026 09:26:49 +0200
+Message-Id: <DHXSYEQU1SFY.1W0NFHNML13VB@ddevault.org>
+From: "Drew DeVault" <drew@ddevault.org>
+To: "Dan Callaghan" <djc@djc.id.au>, <~sircmpwn/public-inbox@lists.sr.ht>
+Subject: Re: [PATCH] add Sup to the list of plain text certified mail
+ clients
+X-Sourcehut-Patchset-Update: APPLIED
+References: <20260411092525.498926-1-djc@djc.id.au>
+In-Reply-To: <20260411092525.498926-1-djc@djc.id.au>
+
+--a036da96ce29d60f01d793f8b11ad804cb7659b0e414c63118649b669cdc
+Content-Type: multipart/mixed;
+ boundary=1d16b06c6066296315555c6776a0e1d688a41caf8c9cea66a5cdcdcfd42a
+
+--1d16b06c6066296315555c6776a0e1d688a41caf8c9cea66a5cdcdcfd42a
+Content-Type: multipart/alternative;
+ boundary=a95442b43616d7ee1f8d1e2ae129e5a85862edd92378eb2f25ff009758ef
+
+--a95442b43616d7ee1f8d1e2ae129e5a85862edd92378eb2f25ff009758ef
+Content-Transfer-Encoding: quoted-printable
+Content-Disposition: inline
+Content-Type: text/plain; charset=UTF-8
+
+Thanks!
+
+To git@git.sr.ht:~sircmpwn/useplaintext.email
+ e22de97..70ed7d5 master -> master
+
+--a95442b43616d7ee1f8d1e2ae129e5a85862edd92378eb2f25ff009758ef--
+
+--1d16b06c6066296315555c6776a0e1d688a41caf8c9cea66a5cdcdcfd42a
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename=42F3F1862E3CC4B8.asc
+Content-Type: application/pgp-keys; charset=UTF-8
+
+LS0tLS1CRUdJTiBQR1AgUFVCTElDIEtFWSBCTE9DSy0tLS0tCgptRE1FWjdZOWt4WUpLd1lCQkFI
+YVJ3OEJBUWRBL3BQeTZYK25OTDVUMlFhSktFTTA4eE4vS3o3d0ZUQVpvSDVZCnJpVjl4MW0wSUVS
+eVpYY2dSR1ZXWVhWc2RDQThaSEpsZDBCa1pHVjJZWFZzZEM1dmNtYytpSk1FRXhZS0FEc0MKR3dN
+RkN3a0lCd0lDSWdJR0ZRb0pDQXNDQkJZQ0F3RUNIZ2NDRjRBV0lRU2Z0ZWMzM0NXeW5ZN3NScEZD
+OC9HRwpManpFdUFVQ1o3WTl3d0FLQ1JCQzgvR0dManpFdU1FRUFQOURIKzFMZ3ZUcVpETFo4YmFi
+QjVDZHA1eTBaVytRClR4NzhtaDFMOGpKZDZnRUFzc21nc0ltY3JadjRhZFAyVVc1UlU1QkhDZTlL
+VWR4MER5VjgzUXdsRkFTNE9BUm4KdGoyVEVnb3JCZ0VFQVpkVkFRVUJBUWRBR0FIOWRsYUNPTm9Y
+cG1RZ0hvUWdZSTJ0UytWTTNtelU4STJQeVZZUQoxR1VEQVFnSGlIZ0VHQllLQUNBQ0d3d1dJUVNm
+dGVjMzNDV3luWTdzUnBGQzgvR0dManpFdUFVQ1o3WTkxZ0FLCkNSQkM4L0dHTGp6RXVIY25BUDR5
+ak9pTTB5cWtTVDZ5WHpEVVd6ZTdCOUltMjRGOEhWeCt3TnFjRGFEdGNBRC8KZktRaHowU0NQaWJs
+TzZsYzdNRlV2bGFPejJ2ODdVcFVZUmh6UGRnUXF3az0KPWVtcmIKLS0tLS1FTkQgUEdQIFBVQkxJ
+QyBLRVkgQkxPQ0stLS0tLQo=
+--1d16b06c6066296315555c6776a0e1d688a41caf8c9cea66a5cdcdcfd42a--
+
+--a036da96ce29d60f01d793f8b11ad804cb7659b0e414c63118649b669cdc
+Content-Type: application/pgp-signature; name="signature.asc"
+
+-----BEGIN PGP SIGNATURE-----
+
+iHUEABYKAB0WIQSftec33CWynY7sRpFC8/GGLjzEuAUCaeXVOQAKCRBC8/GGLjzE
+uJRIAPwOx55M404obNqpVNWkeipGH68mQlprxiQqztCT6jG5qgD+LUPSDChxMpmo
+ogLdO5Rb3UDRet4xWy0fjSAxT34AHgA=
+=tPvL
+-----END PGP SIGNATURE-----
+
+--a036da96ce29d60f01d793f8b11ad804cb7659b0e414c63118649b669cdc--
diff --git a/contrib/screenshot-generator/sup-is-plain-text.msg b/contrib/screenshot-generator/sup-is-plain-text.msg
@@ -0,0 +1,78 @@
+Authentication-Results: mail-a.sr.ht; dkim=none
+Received: from smtp02.aussiebb.com.au (unknown [121.200.0.93])
+ by mail-a.sr.ht (Postfix) with ESMTPS id C7D921FFE2
+ for <~sircmpwn/public-inbox@lists.sr.ht>; Sat, 11 Apr 2026 09:26:03 +0000 (UTC)
+Received: from localhost (localhost.localdomain [127.0.0.1])
+ by smtp02.aussiebb.com.au (Postfix) with ESMTP id 3E06E101B25
+ for <~sircmpwn/public-inbox@lists.sr.ht>; Sat, 11 Apr 2026 19:25:55 +1000 (AEST)
+X-Virus-Scanned: Debian amavis at smtp02.aussiebb.com.au
+Received: from smtp02.aussiebb.com.au ([127.0.0.1])
+ by localhost (smtp02.aussiebb.com.au [127.0.0.1]) (amavis, port 10024)
+ with ESMTP id DMHmZ0WHyx_y for <~sircmpwn/public-inbox@lists.sr.ht>;
+ Sat, 11 Apr 2026 19:25:55 +1000 (AEST)
+Received: by smtp02.aussiebb.com.au (Postfix, from userid 116)
+ id 2676A101CFB; Sat, 11 Apr 2026 19:25:55 +1000 (AEST)
+X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on
+ smtp02.aussiebb.com.au
+X-Spam-Level: *
+X-Spam-Status: No, score=2.0 required=10.0 tests=DMARC_PASS,FORGED_SPF_HELO,
+ KHOP_HELO_FCRDNS,SPF_HELO_PASS,URIBL_BLOCKED autolearn=disabled
+ version=4.0.0
+Received: from djc.id.au (honeycomb.djc.id.au [IPv6:2403:5818:89c3::35])
+ (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256)
+ (No client certificate requested)
+ by smtp02.aussiebb.com.au (Postfix) with ESMTPS id 2B9AF101B25
+ for <~sircmpwn/public-inbox@lists.sr.ht>; Sat, 11 Apr 2026 19:25:54 +1000 (AEST)
+Received: by djc.id.au (Postfix, from userid 1000)
+ id 9B20A3550383; Sat, 11 Apr 2026 19:25:53 +1000 (AEST)
+From: Dan Callaghan <djc@djc.id.au>
+To: ~sircmpwn/public-inbox@lists.sr.ht
+Cc: Dan Callaghan <djc@djc.id.au>
+Subject: [PATCH] add Sup to the list of plain text certified mail clients
+Date: Sat, 11 Apr 2026 19:25:25 +1000
+Message-ID: <20260411092525.498926-1-djc@djc.id.au>
+X-Mailer: git-send-email 2.51.2
+MIME-Version: 1.0
+Content-Transfer-Encoding: 8bit
+
+Sup is a mature TUI mail client, originally released in 2008 and still
+actively maintained. It favours displaying text/plain parts, and
+outgoing mails are composed in plain text. It uses traditional reply
+quoting style and encourages bottom posting.
+---
+ es/index.html | 3 +++
+ index.html | 3 +++
+ 2 files changed, 6 insertions(+)
+
+diff --git a/es/index.html b/es/index.html
+index 7697315..f42aee3 100644
+--- a/es/index.html
++++ b/es/index.html
+@@ -118,6 +118,9 @@ <h2 id="recommended-clients"><a href="#recommended-clients">
+ <li>
+ <a rel="noopener" href="https://www.squirrelmail.org/">SquirrelMail</a> (Web)
+ </li>
++ <li>
++ <a rel="noopener" href="https://supmua.dev/">Sup</a> (TUI)
++ </li>
+ <li>
+ <a rel="noopener" href="https://github.com/wanderlust/wanderlust">Wanderlust</a> (emacs)
+ </li>
+diff --git a/index.html b/index.html
+index e38cb60..3244b16 100644
+--- a/index.html
++++ b/index.html
+@@ -121,6 +121,9 @@ <h2 id="recommended-clients"><a href="#recommended-clients">
+ <li>
+ <a rel="noopener" href="https://www.squirrelmail.org/">SquirrelMail</a> (Webmail)
+ </li>
++ <li>
++ <a rel="noopener" href="https://supmua.dev/">Sup</a> (TUI)
++ </li>
+ <li>
+ <a rel="noopener" href="https://github.com/wanderlust/wanderlust">Wanderlust</a> (emacs)
+ </li>
+--
+2.51.2
+