06.22.07

Ticketish Email Integration

by Ben Myles

One of the central features of Ticketish is email integration. Users can create new tickets by email and add comments to tickets by replying to any email that has the ticket id in the subject. I’ll show how this integration is achieved, from setting up Postfix to writing the Ticket and Comment handlers.

Each project in Ticketish receives its own email address. The email address is in the format “[project.permalink]@[account.subdomain].ticketi.sh”. Sending an email to the project’s email address creates a new ticket. Once a new ticket is created, a notification email is sent. Replying to the notification email (or any further emails regarding the ticket) adds a comment to the ticket. Further, the user can add attachments to any emails and have them show up as attachments to a comment.

So, how does it work? Let’s start with the big picture. The mail transfer agent Postfix runs on the same server as the Ticketish mongrels. Postfix receives mail for all ticketi.sh subdomains. When Postfix receives a new message, it fires up script/runner for Ticketish and passes the message in via STDIN . At this point we don’t care if the project exists or not, that’s for Rails to figure out. Postfix just needs to send any emails addressed to .ticketi.sh to script/runner.

Let’s dig a little deeper, and see how to accomplish this behavior with Postfix.

The first Postfix configuration file we’ll look at is main.cf. I won’t paste the whole file, just the interesting parts that differ from the default main.cf file Postfix comes with.

/etc/postfix/main.cf
myhostname = ticketi.sh
mydomain   = ticketi.sh
mydestination = $myhostname, localhost.$mydomain, localhost, regexp:/etc/postfix/mydestination

This is mostly straight-forward. We set the hostname and domain to “ticketi.sh”. The interesting part is that we’re using a regular expression lookup table with mydestination. The mydestination setting means “this is the final destination for the following domains”. Obviously, we want to be the final destination for all Ticketi.sh domains, so we use a regular expression.

/etc/postfix/mydestination
/.*\.ticketi\.sh/       OK

Pretty simple. You need to make sure you generate the database file from that lookup table by running postalias:

# postalias /etc/postfix/mydestination
# ls /etc/postfix/mydestination.db
/etc/postfix/mydestination.db

Now that Postfix knows to receive mail for .ticketi.sh, we need to tell it where to send that mail. First, we’ll create a wrapper around script/runner that can receive incoming emails from Postfix.

/etc/postfix/ticketish_agent.sh
#!/bin/bash
HOME=/home/lsws /usr/bin/ruby /home/lsws/apps/ticketish/current/script/runner 'data = STDIN.read; CommentHandler.receive(data) || TicketHandler.receive(data)' 2>> /home/lsws/ticketish_agent.log

The actual Rails code in runner is pretty simple. We assign the incoming email to the data variable, and then do ‘CommentHandler.receive(data) || TicketHandler.receive(data)’. This code tries to add the message as a comment first, but if the comment handler returns nil (as it will if the message isn’t a comment) it’ll create it as a new ticket instead.

Now we need to add a service to Postfix by adding a few lines at the bottom of master.cf.

/etc/postfix/master.cf
mailman   unix  -       n       n       -       -       pipe
flags= user=lsws argv=/etc/postfix/ticketish_agent.sh

We’ve just called the service “mailman” and it pipes the message through to the wrapper we just created. Note the user= field. As you might guess, this is the user the process runs as. Be sure to use the user your Rails application runs under.

Finally, we need to tell Postfix to deliver all local mail to this new service (all local mail, since Postfix is entirely dedicated to Ticketish). We do this by defining the local_transport in main.cf.

/etc/postfix/main.cf
local_transport = mailman

Creating the CommentHandler and TicketHandler is pretty simple. We just use the following structure:

ticketish/app/models/comment_handler.rb
class CommentHandler < ActionMailer::Base 
def receive(email)
# ...
end
end

You can do whatever you like once you have the email. For example, to extract the project name and domain name from the recipient address of the email:

project_name, domain_name = email.to.first.split("@")

So there you have it. Easy email integration with Postfix and Rails. If you haven’t already heard of Ticketish, take a look. It’s our new simple ticketing application, currently in beta. We’re still sending out beta invites, so feel free to add your name.

Comments

Paolo Dona 6 days later

Great article guys. I wish you the best for your product line!

Leave a Comment