Blog

  • Mammoth Mail: Self-Hosted Email for AI Agents and Automations

    I’ve spent a lot of this year giving AI agents real work to do, and email keeps coming up as the thing that separates a useful agent from an expensive chat window.

    An agent that can read your inbox, draft a reply and send it when you approve is genuinely useful. An agent that can only talk at you in a browser tab is not. The problem is the middle bit: the moment you give an agent a real email address, you’ve given it your identity, and the moment it can send, you’ve given it your reputation.

    Most sensible people stop there. Mammoth Mail is my attempt at making that trade-off unnecessary.

    Self-hosted email for AI agents and automations. Your inboxes, your infrastructure, your control — with scoped agent access and human approval when it matters.

    → Get Mammoth Mail on GitHub  ·  Read more on HGO Labs

    Three things that kept annoying me

    The first was approval. Just about everything out there self hosted or connectors for common mail tools are all or nothing – you either give the agent full permission to the mailbox or none – so if it can read it can send. Gating that permission in code (not prompting) means you control what your agents can do within your mailbox.

    The second was discovery. Handing an agent an API usually means hand-writing a chunk of prompt to explain the API to it. If the mail server can describe itself — what it is, what the key is allowed to do, what the endpoints are — then giving an agent email access should be two things: a URL and a key.

    The third was the mailbox itself. I wanted real inboxes on a domain I control, not a shared third-party address with a vendor’s name in it. What I didn’t want was to become a mail server administrator to get there.

    Approve a send without leaving your inbox

    Mammoth Mail separates reading from sending, and the separation is enforced server-side rather than by convention. An assistant API key can read messages, search the archive, and prepare drafts. It cannot send. You can of course also grant the API key owner status if you do want to give an agent or automation full control of a specific mailbox.

    Sending is a separate thing you grant. You can either leave drafts sitting in a folder for review, or have Mammoth Mail email you an approval request when the agent wants to send something. The second one is the feature I built for myself: the request lands in whatever mail client is already in my pocket, and a tap either sends the message or kills it.

    What that means in practice is that an agent can be genuinely capable and still be unable to embarrass you. It can read the thread, understand the context, write something good, and then wait — and the waiting costs you about three seconds.

    An agent should be able to find the API itself

    Give an agent a Mammoth Mail URL and an API key, and it can work out the rest. There’s a well-known discovery document, an agent setup guide, an endpoint that reports what the key is permitted to do, and a full OpenAPI specification. No bespoke prompt scaffolding, no copying curl examples into a system message.

    The part I care about more than the discovery is what’s behind it: own-domain mailboxes and connected Gmail, Outlook and IMAP mailboxes all speak one message schema and one mailbox API. Folders are where a message lives, labels are additive, and pagination is the same everywhere. It means an agent doesn’t need a different integration path for every kind of inbox, and it means I only had to document this once.

    Email on your own domain, without opening ports

    Receiving mail is where self-hosting usually gets ugly. Opening inbound SMTP to your own network is the standard answer, and it’s the reason most people don’t self-host mail.

    Tusk MX is the way around it. You point your MX records at Tusk, and Tusk forwards mail to your Mammoth Mail instance over a connection your instance starts. No inbound ports, no exposed SMTP listener, nothing new published to the internet.

    If you already have mail infrastructure, you don’t have to use it — Mailgun, Cloudflare Email Routing, Resend and Postmark all plug in as inbound providers, and there’s a Direct MX mode if you do want to receive on port 25 yourself. On the way out, bring your own Mailgun, Brevo, Resend or SMTP relay, or send direct.

    Wiring an agent in should take minutes, not an afternoon

    Most of the pain in agent email isn’t the mail server, it’s the connection between the two. The usual shape is a connector that polls a mailbox on a timer, plus a pile of settings to tell it how often, which folders, how much to fetch, and what to do when it gets it wrong. It works right up until you want a reply to arrive in seconds, and then you’re tuning intervals and reading logs.

    The Hermes connector goes the other way. It’s five lines in a .env file:

    GATEWAY_RELAY_URL=https://mail.example.com
    GATEWAY_RELAY_ID=gw-...
    GATEWAY_RELAY_SECRET=...
    GATEWAY_RELAY_PLATFORMS=email
    GATEWAY_RELAY_ALLOW_DIRECT_PLATFORMS=true
    

    No plugin to install, no polling to configure. Hermes opens an outbound WebSocket to Mammoth Mail and holds it, so mail arrives as an event rather than as the result of a scheduled check. Unacknowledged messages are replayed when it reconnects, which means a restart or a dropped connection doesn’t quietly lose the email your agent was about to answer. Nothing needs to be exposed on the agent’s host either — the connection is outbound, so there’s no inbound port and no certificate to manage on that side.

    Then it just works the way you’d hope: someone emails the agent, the agent answers them by email. Restart the gateway and the relay reconnects.

    The OpenClaw connector is the same transport and deliberately still experimental — the plugin ships in the repository but isn’t published to npm or ClawHub yet, so you build it locally and install it against the OpenClaw host. Expect rough edges and please report what breaks. Between the two, the point is that connecting an agent to a mailbox should be a configuration step, not an integration project.

    Getting it running

    One Docker Compose file, one container, no repository checkout. Set an admin address, a password, and a real BASE_URL, then docker compose up -d.

    That BASE_URL is worth emphasising, because it’s the single most common way to end up with a broken install: a lot of the sending configuration depends on it being the actual URL you use to reach the instance. The other common failure is assuming sending works because drafting works. Outbound needs its own provider configured. If you skip it, the agent will happily “send” a reply that never leaves the box.

    Does the world need another mail server?

    No, plainly. There are decades of excellent mail servers out there and I haven’t written a better one. What I’ve written is a mail server for a use case that didn’t exist when most of them were designed: a mailbox whose primary user is software, and whose owner is someone who isn’t a system administrator.

    That use case changes the priorities. Human approval becomes a first-class concept instead of an afterthought. Machine-readable discovery matters more than an admin panel. Port exposure becomes something to design out rather than document. Those are the decisions the project is built around — there’s a fairly long design-decision record in the repository if you want to see the reasoning rather than take my word for it.

    Free, open source, and in beta

    Mammoth Mail is licensed under AGPL-3.0 and runs as a single Go binary in a single container.

    It’s on GitHub — stars, issues and pull requests all welcome. If you’re running agents that need to send email and you’ve been putting off giving them a real inbox, this is the thing that lets you stop putting it off.

    → Get Mammoth Mail on GitHub  ·  Read more on HGO Labs

  • Tiller Router: Self-Hosted LLM Router with Model Fallback

    I’ve been doing a lot of experimenting with AI agents and coding tools lately, and one of the recurring frustrations has been managing which AI models they use.

    Between OpenCode, Hermes Agent and various other tools, I’ve accumulated a fair collection of API keys across different LLM providers. Some offer inexpensive models, others have generous free tiers, and some are better suited to particular tasks.

    The problem is that every tool seems to have its own way of configuring models. Some have a nice model picker, others require configuration files, and some make changing models unnecessarily difficult.

    Then there’s the problem of providers running out of credits, hitting rate limits or simply being unavailable.

    None of these are particularly difficult problems individually, but when you’re regularly switching between models and providers, it gets old fast.

    So I’ve built Tiller Router, a free, open-source, self-hosted LLM router that sits between your AI tools and your model providers.

    GitHub: https://github.com/dellarb/tiller-router

    One place to manage your models

    The basic idea is pretty straightforward.

    Instead of configuring all your provider credentials and preferred models in every application, you point your AI tools at Tiller Router.

    You add your providers and API keys once, then use Tiller’s web interface to decide which models each application should use.

    The clever bit is that you can change the model being served to an application without having to touch the application’s configuration.

    For example, I can have OpenCode configured to use Tiller with a single API key. Through Tiller’s interface, I can switch that client between a DeepSeek model, GLM or something from OpenRouter.

    OpenCode doesn’t need to know anything has changed. Its next request simply goes to whichever model I’ve selected.

    This is particularly useful when experimenting with different models or trying to make better use of the various free and inexpensive API offerings.

    One model, or a whole catalogue

    Tiller supports two different ways of providing models to clients.

    Single model mode is my favourite. Give an application one API key and Tiller will serve whichever model you’ve selected for it, regardless of the model the application actually requests.

    That means you can effectively take control of model selection away from the application entirely. Useful for agents, coding tools and applications with clunky model configuration.

    Catalogue mode works more like a traditional LLM gateway. You choose which models a particular API key can access, and Tiller exposes those through its model list endpoint.

    This is handy when you still want to select models within an application, but don’t want to scroll through hundreds of models you’ll never use.

    Virtual models and automatic fallback

    This was the other big reason I wanted to build Tiller.

    There’s some excellent value available through free and low-cost LLM providers, but the catch is that they’re not always reliable. Rate limits, timeouts and provider outages can bring an agent to a halt halfway through a task.

    Tiller lets you create virtual models which can be backed by multiple real models in a priority order.

    For example, I might create a virtual model called coding configured as follows:

    1. GLM via Z.ai
    2. DeepSeek
    3. Claude via OpenRouter

    My coding agent only sees the coding model.

    If the first provider fails or times out, Tiller can automatically try the next provider in the list. This happens without needing to change anything in the agent.

    There are some sensible limitations here. Once a model has started streaming a response back to the client, Tiller can’t transparently replace it halfway through. Fallback is designed to handle failures before the response has begun.

    You can also assign a virtual model to a single-model client, combining both features.

    A proper web interface

    I’ve tried to keep the interface focused on the things I actually want to do regularly.

    You can add providers, manage API keys, browse available models, configure virtual models and steer your connected clients from the one interface.

    There’s also an activity view showing which clients are making requests, which models and providers are being used, response times, token usage and when a fallback has occurred.

    I find this particularly helpful when an agent has been running for a while and I want to see what it’s actually been doing.

    Getting it running

    Tiller is designed to be self-hosted and runs as a single Docker container with persistent data stored in a local directory.

    If you’ve already got Docker running, it’s a fairly simple setup.

    Create a directory and add the following docker-compose.yml:

    services:
      tiller-router:
        image: ghcr.io/dellarb/tiller-router:latest
        container_name: tiller-router
        ports:
          - "8080:8080"
        volumes:
          - ./data:/data
        restart: unless-stopped

    Start it with:

    docker compose up -d

    Then open http://localhost:8080 and follow the initial setup to create your administrator account.

    From there, add your LLM providers, create a client API key and point your preferred AI tool at Tiller.

    For access outside your local network, I’d recommend putting it behind an HTTPS reverse proxy and following the repository’s configuration guidance.

    The source code and full installation instructions are on GitHub.

    Does the world need another LLM router?

    Probably not!

    There are already some very capable LLM gateways available, particularly if you need sophisticated routing rules, load balancing or enterprise-level features.

    But I kept finding that the thing I really wanted was much simpler.

    I wanted to connect an application once, choose which model it uses from a web interface, and be able to change that model whenever I liked. Ideally with a fallback so a rate-limited provider wouldn’t interrupt whatever I was working on.

    Tiller isn’t trying to automatically work out the smartest or cheapest model for every request. It gives you direct control over that decision.

    It’s also deliberately lightweight, with no need to set up a separate database server or a pile of supporting services.

    Free and open source

    Tiller Router is available on GitHub under the AGPL-3.0 licence.

    It’s currently in public beta, so there will almost certainly be bugs and some rough edges, particularly with the huge variety of LLM providers and clients out there.

    I’ve been building it around my own use cases, but I’m keen to hear whether others find it useful, what clients work well, and where things could be improved.

    If you’re running a collection of AI tools and are tired of constantly fiddling with API keys and model settings, give it a go.

    Source code, Docker installation and issue tracker:

    https://github.com/dellarb/tiller-router

  • The pains of mixed GST Invoices

    If you process invoices for a not-for-profit, community group, or small organisation, you’ve probably run into this: an invoice arrives with a mix of taxable and GST-free items. The supplier has helpfully totalled it all up — one grand total, one GST figure — and left you to work out the individual line amounts yourself.

    Most accounting software, including the popular cloud platforms, wants you to enter bills line by line with each item’s ex-GST amount. That means you need to back-calculate from the totals before you can even start entering the bill. For a single invoice it’s maybe a few minutes of work. When you’re processing a stack of them, it gets old fast.

    The maths isn’t hard — it’s just tedious

    The logic is straightforward enough. The GST amount tells you exactly what was charged on the taxable portion — divide it by 10% and you have the ex-GST value of those items. Subtract that from the invoice’s ex-GST total and you’re left with the GST-free portion. Write it all out once and it’s obvious. Do it for the fifteenth invoice this month and it’s just noise.

    A quick solution

    I put together a simple tool that does this calculation in seconds. Enter the invoice total and the GST amount — either inc or ex GST depending on what your invoice shows — and it spits out the two line items ready to enter. It runs in the browser with no login, no account, nothing to install.

  • Using NFS Volumes on OpenMediaVault with Portainer

    Yep this is another niche tech note after I spend ages figuring it out.

    So I wanted to use an NFS share hosted on OpenMediaVault as a volume for a Docker container running on Portainer. It turns out there are a few default things that need adjusting to make this work and if you don’t get it right it just puts the volume on the local drive.

    To use an NFS share do the following

    1. Name the volume
    2. Select NFS Volume
    3. Enter the hostname
    4. Select NFS instead of NFS4 in the version field
    5. add a colon before /export/sharename so it is :/export/sharename

    That’s it – now you can successfully use the NFS share as docker volume without a pile of errors!

  • Simple Repository Setup for Proxmox

    UPDATE (October 2026): this is a PVE 6 / Debian buster note from 2019. It is left here as a historical record because the shape of the fix hasn’t changed, but the specifics have.

    Everything below refers to Proxmox VE 6 and Debian buster. Buster is end-of-life, and on current Proxmox (VE 8/9) these instructions don’t apply:

    • pve-enterprise.list was replaced by the deb822 .sources format — on VE 9 the rm in step 1 silently deletes nothing useful.
    • ftp.au.debian.org was retired in favour of deb.debian.org; release names have moved buster → bullseye → bookworm → trixie.
    • security.debian.org is now debian-security.

    For current releases, use the official source: Proxmox — Package Repositories. The principle still holds — disable the enterprise repo, enable pve-no-subscription — but take the exact lines from that page rather than from below.

    For those of us using the great OS Proxmox to manage virtualisation one of the first challenges is fixing the default repositories as it will throw errors such as “401 Unauthorised” and “Failed to fetch https://enterprise.proxmox.com/debian/pve/dists/buster/InRelease”

    This is caused by Proxmox shipping with some enterprise repositories switched on by default and is fairly quick and easy to resolve for anyone who wants to use the free repositories for development work. Just follow the below two steps. This is based off an install in August 2019 of Proxmox VE6.0-4.

    Step 1

    Remove the enterprise repository pointer by running “rm /etc/apt/sources.list.d/pve-enterprise.list”

    Step 2

    Edit the main sources list to contain the correct and free repositories which won’t generate any errors. Edit the file /etc/apt/sources.list to match the below content:

    deb http://ftp.au.debian.org/debian buster main contrib
    
    deb http://ftp.au.debian.org/debian buster-updates main contrib
    
    # PVE pve-no-subscription repository provided by proxmox.com,
    # NOT recommended for production use
    deb http://download.proxmox.com/debian/pve buster pve-no-subscription
    
    # security updates
    deb http://security.debian.org buster/updates main contrib

    That’s it! Happy apting.

  • Automatic Software In The Loop (SITL) setup for ArduPilot

    UPDATE (October 2026): the original install command has been replaced. This post was written in August 2019 and told you to run a setup script from my personal Bitbucket repo:

    curl -sSL https://bitbucket.org/dellarb/ardupilot-sitl/raw/master/sitl_install.sh | sudo bash

    Do not run that, and please don’t run anything shaped like it. Three things were wrong with it: it piped a remote script straight into sudo bash, the script targeted Ubuntu 14.04 (end-of-life since 2019), and it pointed at a personal repo that is no longer maintained. ArduPilot moved to GitHub years ago, and the project maintains its own supported setup script. The instructions below now use that instead.

    For those who use and develop using the ArduPilot autopilot software (as featured on DIY Drones and ArduPilot Discourse), a powerful simulation tool is available that can replicate all of the features of the autopilot without flying an actual aircraft. This is extremely useful for testing complex missions or parameter configurations without risking real hardware.

    Flying a simulated ArduPlane UAV around Canberra from the comfort of my desk in Perth.
    Flying a simulated ArduPlane UAV around Canberra from the comfort of my desk in Perth.

    Setting it up used to be a bit tricky. The official instructions are at Setting up SITL on Linux, and the source lives at github.com/ArduPilot/ardupilot.

    (more…)

  • Download all attachments from Trello card as a zip file

    **Unfortunately Trello changed their service in a way this no longer functions – I hope it was helpful to people over all this time**

    Trello is amazing tool for co-ordination, planning and collaboration with the ability to assign cards, create due dates and checklists and also attach files. I have often found myself wanting to download all of the files associated with a card but have not found an easy way to do this without manually clicking and downloading on each one.

    To simplify this I’ve created an online attachment downloading tool that you can export your card information to and it will create a single zip file of all attachments on the card:

  • Using OpenVPN on LXC Hosts

    A common problem I encounter is trying to use OpenVPN on Linux containers hosted using LXC. LXC containers are a great, low resource way to virtualise things but need some extra setup for OpenVPN. Typically you get an error that looks something like:

     ERROR: Cannot open TUN/TAP dev /dev/net/tun: No such file or directory

    (more…)

  • Apache as reverse proxy for letsencrypt free https certificates

    UPDATE (October 2026): the commands in step 3 and 4 below have been corrected. This guide was written in November 2016 against Ubuntu 16.04. The reverse-proxy configuration still works, but the certificate tooling was renamed shortly afterwards and the original commands will fail.

    • apt-get install python-letsencrypt-apache → the package is now python3-certbot-apache
    • letsencrypt --apache → the command is now certbot --apache
    • systemctl restart apache2 → prefer systemctl restart apache2

    Current certbot’s Apache plugin also installs the HTTPS vhost and the redirect itself, and renews via a systemd timer — so the manual ProxyPass /.well-known ! juggling below is no longer required, though it remains harmless. See certbot.eff.org for your platform.

    Scenario

    You have a single incoming IP address and want to run multiple web servers for multiple sites behind this IP address on your local network. The best way to do this is using a reverse proxy server For example:

    • Your External IP is: 8.8.8.8 with and internal LAN of 10.1.1.X
    • Ports 80 (http) and 443 (https) have been forwarded from your external ip to an internal server at 10.1.1.2 which will handle the reverse proxy and SSL/TLS work using letsencrypt
    • You have other application web servers listening on port 80 on your internal LAN at 10.1.1.11 and 10.1.1.12 but these are not accessible from outside your network.
    • You have subdomain11.yourdomain.com and subdomain12.yourdomain.com both pointed to 8.8.8.8 and you want visitors to them to see the application servers at 10.1.1.11 and 10.1.1.12 respectively.
    • You want to provide secure https access to both subdomains but don’t want to configure this on each of the three servers separately.

    This guide was written against Apache2 on Ubuntu 16.04 (see the update above — the certbot commands have been corrected), some commands may differ slightly between different flavours of Linux but the core configuration for Apache2 should work on any distribution.

    (more…)

  • Simple set up for Turnigy 9XR (or similar) 6 position mode switching with APM Autopilots – No Hardware/Flashing Required

    Simple set up for Turnigy 9XR (or similar) 6 position mode switching with APM Autopilots – No Hardware/Flashing Required

    This is a cross post for one of my original blog posts on DIY Drones. It is included here for permanent reference.

    T9X_1

    Until recently my main TX was a Turnigy 9X and I (like many others) was using a hacked in 6 position POT switch to select modes on my various aircraft. Unfortunately the 9X has died so has been replaced by a shiny new 9XR. Rather then hack apart the 9XR (too pretty) I wanted to experiment with using the existing controls to select modes on my aircraft. I have seen several tutorials on this but none met all of my requirements below.

    1. Not relying on guessing the position of the continuous POT switches
    2. Gives a easy to read visual indicator of current mode
    3. Uses only a single radio channel
    4. All performed with the default software and hardware – no flashing/hacking required

    The method I used was to combine the 3 position switch (ID0/1/2) with the Aileron Dir switch. Between these two switches there are six possible combinations which neatly matches the 6 modes of the APM ecosystem.

    (more…)