How to create your custom Zammad Package

Zammad is a great tool for dealing with support, it supports multiple channels to receive support tickets and has a way to add features with packages. Unfortunately this package thing is documented in the forum only and can be hard to find.

How to create your custom Zammad Package

So you want to start with your own custom zammad package? Here's what you need to get started. Thanks to Marcel Herrguth from Zammad support in pointing me to the forum post describing how the szpm files are build. Basically you describe your package (more on that later) and encode the file you need as base64 and add it to the zpm file (more on that later as well).

To better understand this, let's create a custom SMS Gateway integration and set you up with a two way SMS Gateway, since we already have our own numbers it would not make sense for us to sign up with an external vendor offering the same services we already have and sell to our clients.

New SMS channel driver for Zammad

If you look at the Zammad files you will notice the existing channel drivers for twilio and massenversand are in app/models/channel/driver/sms/ which means you need to add a new file called cloudvox.rb to this directory. You can copy the existing twilio driver and make your modification where, this is what I did. The file needs to create the class class Channel::Driver::Sms::Cloudvox contain a send process and fetchable? method as well as the configuration parameters which will be presented in the web interface. Let's start with the parameters.

  def self.definition
    {
      name:         'cloudvox',
      adapter:      'sms/cloudvox',
      account:      [
        { name: 'options::gateway', display: 'Gateway', tag: 'input', type: 'text', limit: 200, null: false, placeholder: 'https://sms.cloudvox.eu/messages/send_api', default: 'https://sms.cloudvox.eu/messages/send_api' },
        { name: 'options::webhook_token', display: 'Webhook Token', tag: 'input', type: 'text', limit: 200, null: false, default: Digest::MD5.hexdigest(rand(999_999_999_999).to_s), disabled: true, readonly: true },
        { name: 'options::token', display: 'Cloudvox SMS API Key', tag: 'input', type: 'text', limit: 200, null: false },
        { name: 'options::sender', display: 'Sender', tag: 'input', type: 'text', limit: 200, null: false, placeholder: '+491710000000' },
        { name: 'group_id', display: 'Destination Group', tag: 'select', null: false, relation: 'Group', nulloption: true, filter: { active: true } },
      ],
      notification: [
        { name: 'options::gateway', display: 'Gateway', tag: 'input', type: 'text', limit: 200, null: false, placeholder: 'https://sms.cloudvox.eu/messages/send_api', default: 'https://sms.cloudvox.eu/messages/send_api' },
        { name: 'options::token', display: 'Cloudvox SMS API Key', tag: 'input', type: 'text', limit: 200, null: false },
        { name: 'options::sender', display: 'Sender', tag: 'input', type: 'text', limit: 200, null: false, placeholder: '+491710000000' },
      ],
    }
  end

Here you have to define the name and adapter this is needed to load the appropriate class once the package is installed. You can define what ever parameters you need in the web interface to configure your gateway. I need an URL, a sender number as well as an API key. This will look like this when you add the new sms channel.

New SMS Gateway for Zammad

I won't dig to much into the code as it's not that hard to understand and not the scope of this post. One thing to notice however, if you intend in returning something as response to the API call, it has to be an array containing the content type and the body of the data you want to return. I for example wanted to know if a  ticket was created or updated and which ticket it was so I can send an SMS back to the sender.

['application/json; charset=UTF-8;', { status: ticket_action, ticket_id: ticket.id }.to_json]

Now that we have this, how do we get our new ruby file to the server without having to manually copy it there (consider docker for example) and also to keep it safe after updates.

This is where the zpm files come in handy to install a new packaged.

Create a zammad package

The forum post tells us that the file should look something like this and then also contain the file as base64 encoded string.

{
  "name": "Addon",
  "version": "1.0.0",
  "vendor": "Zammad GmbH",
  "license": "GNU AGPL v2",
  "url": "http://zammad.org/",
  "change_log": [
    {
      "version": "1.0.0",
      "date": "2019-08-05 13:37:42 UTC",
      "log": "Initial version."
    }
  ],
  "description": [
    {
      "language": "en",
      "text": "Adding custom functionality XY."
    }
  ],
  "files": [
    {
      "location": "app/model/custom.rb",
      "permission": 644
    }
  ]
}

Here is my version of the file, note that I left the content part empty.

{
  "name": "CloudVox SMS",
  "version": "1.0.0",
  "vendor": "LcX IT Solutions",
  "license": "MIT",
  "url": "https://lcx.wien/",
  "buildhost": "git.lcx.at",
  "builddate": "2019-10-10 10:37:42 UTC",
  "change_log": [
    {
      "version": "1.0.0",
      "date": "2019-10-10 10:37:42 UTC",
      "log": "Initial version."
    }
  ],
  "description": [
    {
      "language": "en",
      "text": "Adding two way SMS for CloudVox"
    }
  ],
  "files": [
    {
      "location": "app/models/channel/driver/sms/cloudvox.rb",
      "permission": 644,
      "encode": "base64",
      "content": "**YOUR STRING HERE**"
    }
  ]
}
cloudvox-sms.szpm

This will tell Zammad to take the string out of content decode it and save it to app/models/channel/driver/sms/cloudvox.rb to generate the base64 string you could pipe the file contents through base64 on linux or mac like this cat cloudvox.rb | base64 copy the result and paste it in place of your string here in the example file above.

You can find both files on github.

All that you have to do now is to install the package. Just go to the Zammad settings -> Packages and upload the file. That's it, it really is that simple.

Install Zammad Package

I hope this helps you create your custom zammad package, it really was very simple in this case as there where no UI changes needed and I had to only add one new file. Things can probably get tricky for more complex modifications but I hope this post does give you a head start.