Mobile Zammad notifications with Pushover

Both for my standalone work and for pretalx.com, I use the Zammad ticket system. Sadly, it includes neither a mobile app nor a usable mobile layout – but since I usually have my phone around, even when my laptop sits in my backpack/the next room/somewhere else, I wanted to see new tickets on my phone as they come in. Dismissing tickets I can look up later isn't particularly stressful for me, and well worth the opportunity to react to urgent tickets as fast as possible.

Note: At the time of this writing, Pushover comes with a one-time cost of USD 4.99 after a 7-day trial period. If paying for this service is not an option for you, the tool choices section has some alternatives that might help you.

Note: This solution assumes that you are alright with sending this information via a third-party service, and that you have a Linux shell with constant internet access somewhere.

Backstory

Backstory

Ticket systems are typically huge blobs of software, with vast capabilities (because feature creep is real when you re-implement email, and then put ALL the features on top: assignments, due dates, permissions, states, groups, other groups, tags, templates, …).

Since absolutely nobody enjoys migrating from one ticket system to the others, we still have the old giants like Request Tracker around, which you can't use or administrate without reading an in-depth manual, and where interface modernisation comes in the form of differently rounded corners.

One new-ish contender on the field is Zammad. It's an open-source tool financed via their hosted service and support, in a model closely resembling GitLab, pretix, and many other open-source tools. The project was started in 2012, and the Berlin-based company was founded two (and a half) years later in late 2014.

Zammad is prettier than its competitors, and requires much less learning to use the UI. It still has some contentious UI choices (at first, you will write a comment to a ticket instead of a reply), but those are pretty easy to live with, all alternatives considered. You can configure user interface actions, easily save searches/filters as permanent views, and see when somebody else works on the same ticket as you. Zammad also has neat features like putting a ticket on hold until a certain date, and once that date is reached, sending you a daily reminder. This is particularly good for "let's talk in four months" style communication.

Lately though, it seems that the Zammad team is a bit overwhelmed with feature requests and bug reports, and is looking for ways of dealing with those issues. GitHub issues are only to be used for bug reports as of November 2018, while feature requests have been moved to their own forum. The first, stickied, message in that forum is a notice from March 2019 with a rant regarding the rising number of tickets and problematic reports (missing required information, or messaging the devs directly).

Tool Choices

Tool Choices

Don't get me wrong: all of that is perfectly understandable, but it means that we shouldn't get our hopes up regarding new features, a mobile app, or even just a responsive style sheet. So, let's build our own solution instead!

Zammad

Zammad features an HTTP API, which even has an endpoint showing the current notifications for the logged-in user. I'm on Zammad 3.0, but the API has not changed noticeably since at least the 2.6 release, so presumably you should be able to use the code below with different versions, too.

Note: The Zammad API documentation is in a bad state. You can use it to find out which endpoints are available, but for everything beyond that, you'll have to query the endpoints directly, since the documentation includes fields that are not there, doesn't mention fields that are there, and is overall not very explanatory. Also note that the API has only very tentative support for things like searches or filters.

Pushover

I tried to find a good way of using this API with some script and pushing data to my phone, and asked around a bit. This is a list of things I considered, but didn't choose to go with:

  • 📨 Email. I don't have email notifications on my phone for a reason, and changing this is out of the question. I could have enabled notifications for a single receiver or sender, I suppose, but that would've made the setup very brittle when moving to a different mail app or a new phone.
  • Telegram bots, XMPP messages, etc: Same as with email, I don't typically receive XMPP notifications on my phone, especially since multi-device XMPP is its own special kind of hell, and receiving all highlights from my bouncer would result in pretty instantaneous information overload. 🤯
  • 📱 SMS. Zammad has built-in support for Twilio, so that would have been an option. But I already (happily) use Nexmo for all my phone-related needs, and I'm hesitant to pick up a second service. Also, SMS will include recurring costs, and I was hoping to do without.
  • Nock Nock is a free Android tool to monitor URLs, and includes the ability to execute custom JavaScript. It's not intended for this kind of work, but it would've worked! I'm just annoyed at the thought of copying API keys and JS code around on the phone. But I really like the idea!
  • Gotify is a server for sending and receiving all kinds of messages, but it wasn't quite clear how to get started at a glance. It's a great choice when using completely self-hosted infrastructure, but sadly does not support putting URLs directly into the notification.

There are infinite amounts of possibilities here, but the strongest recommendations from asking around was Pushover. Pushover comes with a one-time fee of USD 4.99 after a 7-day trial period at this time of writing. The fee needs to be paid per platform, which is perfectly reasonable to me. I'll have different Android phones, as far as I can currently tell, and should I ever switch to an iPhone, the cost of five dollars for this service will be the least of my expenses. Fixed costs are a good selling point to me in this scenario, and I'm very happy with the transparent and cheap pricing.

Pushover basically provides an Android application (as well as one for iOS on the iPhone, iPad, and iPod, plus desktop browsers and MacOS) and an HTTP API to send messages. You have to register an application to send messages, and specify the receiving user via a token (or create a group of users and target the group). It's very straightforward, and their API documentation is very clear, filled with examples, and generally pleasant to work with.

The Code

The Code

Python is my go-to language for stuff like this. Every other language will do, too, as will tools like Zapier and If This Then That. We'll need only the requests library, and will be ready to go. The code below is a simplified version for understanding the workflow. I removed mostly typecasts and error handling. You can find the complete version here.

import configparser
import json
from contextlib import suppress

import requests

config = configparser.ConfigParser()
config.read("notify.cfg")


def get_seen_ids():
    with open("./seen_ids") as seen_file:
        content = seen_file.read()
    return set(content.strip().split("\n"))


def write_seen_ids(ids):
    content = "\n".join(str(element) for element in ids)
    with open("./seen_ids", "w") as seen_file:
        seen_file.write(content)


def zammad_get(url):
    response = requests.get(
        url=config["zammad"]["url"] + url,
        headers={"Authorization": f'Bearer {config["zammad"]["token"]}'},
    )
    return response.json()


def get_unread_notifications():
    response = zammad_get("/api/v1/online_notifications")
    return [n for n in response if n["seen"] is False]


def send_notification(notification):
    ticket_id = notification["o_id"]
    ticket = zammad_get(f'/api/v1/ticket_articles/by_ticket/{ticket_id}')[-0]
    payload = {
        "token": config["pushover"]["app"],
        "user": config["pushover"]["user"],
        "title": ticket["from"],
        "message": ticket["body"],
        "url": f'/#ticket/zoom/{ticket_id}',
        "url_title": "Go to ticket",
    }
    response = requests.post(
        url="https://api.pushover.net/1/messages.json",
        data=json.dumps(payload),
        headers={"Content-Type": "application/json"},
    )


seen_ids = get_seen_ids()
notifications = get_unread_notifications()
new_seen_ids = []
for notification in notifications:
    if notification["id"] not in seen_ids:
        send_notification(notification)
    new_seen_ids.append(notification["id"])
write_seen_ids(new_seen_ids)

So we basically grab all unread notifications from Zammad, compare them against our list of notifications we already sent a message for, and use Pushover with each unhandled notification. We write a list of all notifications that are still unread on Zammad, but have been sent to Pushover, to a file for the next run – and we're done.

Setup instructions

Setup instructions

First off, register an account with Pushover, and do the email registration dance.

Then, register a Pushover application. You only need to provide a name and a description (and can add a URL and an icon, if you're feeling fancy).

Now, go to a Linux shell with constant internet access. Create a notify.cfg file like this:

[zammad]
token = secrettoken
url = https://my.domain.com

[pushover]
user = secretusertoken
app = secretapptoken

You'll find the pushover tokens prominently displayed on your pushover user page and the app page. For the Zammad token, go to the /#profile/token_access page on your Zammad server, and create a personal access token with the permissions ticket.agent and user_preferences.notifications.

Get the full notification script, and make it executable using chmod +x notify_zammad.py. If python3 -c "import requests" runs without issues for you, you don't really need a virtualenv – otherwise, install requests either globally or in a virtual environment of your choice, e.g. using python3 -m venv, or pip install --user requests.

*/5 * * * * cd /path/to/directory && ./notify_zammad.py

Now create a cronjob (or systemd timer or whatever you use to run commands) executing the script from its containing directory (so that it will find the configuration file), and you should be good to go!


If this post was helpful to you, consider clicking the Funding link.