Sending/Testing MMS using XMPP

Recently I've been helping out with a fork of mmsd (located here) since MMS is, unfortunately, a crucial thing I need working on a phone in 2021. Anyways, one of the painful things about hacking on mmsd is that receiving a MMS for testing is a manual process. The simplest way to get an MMS is to ask a friend to send you one. Or perhaps purchase a second SIM to use in a second phone to send yourself one. After a while, your friends, family, people you just met, etc will stop responding to your requests to have them send you a MMS. Before you know it, you're sneaking off with your partner's phone while they are sleeping so you can get some work done. This is rock bottom.

A few days back, some folks in the Librem 5 Matrix channel were recommending jmp.chat, a service that gives you a real phone number that you can tie to an XMPP account. This service supports a few different things, including some voicemail thing when using voice chat over XMPP (which I should look at later on..), but the most interesting feature at the moment is that one can use it to send SMS and MMS to real phone numbers. It automatically converts non-text messages (e.g. photos) into MMS. Perfect!

This setup is still a rather manual process though... I'm using Chatty on my phone (running postmarketOS, of course), and unlocking the screen, opening Chatty, opening the "conversation" between the XMPP account tied to jmp.chat and the XMPP bridge to the number associated with my SIM, add an attachment, and finally hitting "send" each time just to test out mmsd still sucks. We can do better!

This evening I learned about sendxmpp, specifically the Golang rewrite of this tool (the original was in Perl). sendxmpp is a commandline tool for sending messages over XMPP, and the Go version of it has a --http-upload option that sends arbitrary binary data in the message using, you guessed it, HTTP. Perfect! Now I can fire off MMS to my phone with one simple line in the trusty terminal emulator:

$ go-sendxmpp -t --http-upload photo.jpg "+1XXXXXXXXXX@cheogram.com"

The recipient address is what jmp.chat uses for bridging the XMPP message to my phone number, the expected format is straight from their FAQ.

Saving sent mails in aerc + notmuch + mailbox.org, part 2

Well, the previous attempt to solve this problem was a bust, for the following reasons:

  • The template only applied to new mails, not replies
  • The round trip thing where messages had to get filed on the server, then pulled down again by mbsync was slow and annoying

After chatting with folks on the #aerc IRC channel, I've learned that my mail provider (Mailbox.org) is doing me a great disservice by not implementing anything server-side for automatically saving mails sent via SMTP into an IMAP Sent folder. Mailbox.org's official stance suggests that this is intentional:

Saving a Mail to Sent must be done by the mailclient. This is not triggered by the server. Please check your settings in your mailclient.

Having this done server-side means that all sent mails via SMTP would be saved, regardless of the client used to save them. SMTP clients (understandably...) have no concept of IMAP or saving things in IMAP folders.

It was mentioned in the #aerc channel that aerc can use an arbitrary command to send mails, and indeed the manpage for aerc-sendmail specifies that aerc will pass the message content over stdin, and pass a list of recipients to the command as arguments. So this second attempt creates a wrapper for msmtp (or whatever you want to use for a SMTP client), copies the sent mail to your maildir's sent folder, and then forwards it to your SMTP client:

#!/usr/bin/env python3
import argparse
import mailbox
import os
import subprocess
import sys


# read stdin early so pdb can be used later
message = sys.stdin.read()
sys.stdin = open("/dev/tty")

ap = argparse.ArgumentParser(description=('Save sent mail before forwarding '
                                          'to smtp client.'))
ap.add_argument('--smtp_exe', '-e', action='store', default='/usr/bin/msmtp',
                help=('SMTP client to run for sending message '
                      '[defaut: %(default)s]'))
ap.add_argument('--maildir', '-m', action='store', default='~/.mail',
                help=('Maildir to copy sent mail into '
                      '[defaut: %(default)s]'))
ap.add_argument('--sent-dir', '-s', action='store', default='sent',
                help=('Directory in mailbox to copy sent mail to '
                      '[defaut: %(default)s]'))
ap.add_argument('recipients', nargs='+')
args = ap.parse_args()

maildir = os.path.expanduser(args.maildir)

sent_path = os.path.join(maildir, args.sent_dir)
if not os.path.exists(sent_path):
    raise RuntimeError(f"ERROR: path does not exist: {sent_path}")

mbox = mailbox.Maildir(sent_path)
mbox.lock()
try:
    msg = mailbox.MaildirMessage(message)
    msg.set_subdir('cur')
    msg.set_flags('S')
    mbox.add(msg)
except Exception as e:
    print(f"Error saving mail: {e}")
finally:
    mbox.flush()
    mbox.unlock()

# send mail using smtp client
cmd = [args.smtp_exe] + args.recipients
p = subprocess.Popen(cmd, stdin=subprocess.PIPE,
                     stdout=subprocess.PIPE, stderr=subprocess.PIPE)

out, err = p.communicate(input=message.encode())
ret = p.returncode
if ret:
    print(err.decode())
else:
    print(out)

Update ~/.config/aerc/accounts.conf to use this command for outgoing:

outgoing          = ~/bin/sendmail -m ~/Mail

I use the following notmuch tagging to +sent to mails, while keeping a +inbox tag to mails I sent to myself:

+sent -- from:<my email here>
-inbox -- tag:inbox and from:<my email here> and not to:<my email here>

The filter I set up in my mail provider's config has been dropped.

Now when I send mails in aerc, they are piped through the wrapper above, which saves a copy of the mail in my maildir's sent subdirectory, and forwards it on to the SMTP server using an external SMTP client (msmtp in my case). The sent mail is then copied to the IMAP Sent folder when mbsync runs, so a copy of the sent mail is then stored in IMAP, which in turn is accessible by any IMAP clients I use.

I hope there's not a part 3 to this saga.