Wednesday, March 2, 2011

Using Hudson's email-ext..

One of the issues in using the Hudson email extended notification is that the html.jelly file contains inline <style> that gets ignored by Gmail and other webmail clients. The way to get around this restriction is to use inline styles within the HTML tags. An example is shown here:

So the extended email notification html.jelly file, which shows up as this, doesn't appear quite right.

One way to get around this issue is to use a converter to take your HTML styles and apply them to all your HTML elements. One such web site that can do something is the following:

...but the problem I encountered was copying and pasting could easily introduce bugs in the final HTML code.

Another approach is to use Pynliner. However, while using Pynliner with the Hudson html.jelly file to do inline CSS styles, I found a bunch of issues using it with this file, most notably the BODY and TD.bg1 tags (the HTML itself had lowercase tags). Some styles were not added as a result of these case sensitivity mismatches. The html.jelly for Hudson is located here:

There are a few issues with using Pynliner:
1. It currently is case sensitive with respect to tags and classes used in the <style>. If you have upper-case style tags (i.e. <BODY>, then lower case <body> tags will not match and inline styles will not be generated for those tags.

2. It will introduce a newline for every multiple CSS styles you use (i.e. <h1 style="font-weight (newline); color: red">)

3. It doesn't have any tabs output (prettify=True option in BeautifulSoup) to help verify your HTML output is correct.

4. Finally, more related to the Hudson html.jelly file, but it does not auto-escape ampersand's, which means that Hudson will throw an exception on line 3 that contains two nbsp characters with a preceding ampersand. You have to swap the ampersand (\&) for the \& character after generating the HTML with inline styles.

I was able to correct it with the following GitHub fork:

Assuming you pull these GitHub changes (or they eventually get merged into Pynliner), you can then do:
import pynliner
p = pynliner.Pynliner().from_string(open("html.jelly", "r").read())
open("/tmp/html_gmail.jelly", "w").write(

Once you're done (and you've fixed the ampersand issues), you can copy/paste the html_gmail.jelly file and copy it in your HUDSON_HOME (specified in /etc/default/hudson) into ~/email-templates/dir, and then configure the Hudson email-extended notification to do:


FYI - Hudson will first try to look within its own WEB-INF directory for html_gmail.jelly. If it can't find it, Hudson will then try to look inside your HUDSON_HOME/email-templates dir. The html.jelly file, which is used to generate this file, should already be in your your HUDSON_HOME/plugins/email-ext/WEB-INF/classes/hudson/plugins/emailext/templates dir.

Finally, one last change that can be done is to configure Hudson to output ${FAILED_TESTS} so that you can quickly review the results instead of having to go to the web console. The following is what you can set inside you default Hudson Default Content inside the "Manage Hudson" link:
<H3>Failed Tests</H3>>
<pre style="font-size: 12px">
Note the instructions for using Jelly tokens (i.e. ${JELLY_SCRIPT, template="xx"}):
"Arguments may be given for each token in the form name="value" for strings and in the form name=value for booleans and numbers. The {'s and }'s may be omitted if there are no arguments.

Addendum (03/08): within Pynliner, you may also need to patch the pyliner/ code to handle Jelly tags. We use the BeautifulSoup XML parser instead of the HTML one since the latter introduces a bunch of restrictions on how nested tags can be interprted.
def _get_soup(self):
         """Convert source string to BeautifulSoup object. Sets it to self.soup.
-        self.soup = BeautifulSoup(self.source_string)
+        from BeautifulSoup import BeautifulStoneSoup
+        BeautifulStoneSoup.NESTABLE_TAGS['j:if'] = []
+        BeautifulStoneSoup.NESTABLE_TAGS['j:foreach'] = []
+        BeautifulStoneSoup.NESTABLE_TAGS['table'] = []
+        self.soup = BeautifulStoneSoup(self.source_string, selfClosingTags=['j:set', 'j:getstatic', 'br'])

Since BeautifulSoup lowercases many of the tags/classes, you may also find that some entries from the generated file also need to be adjusted too (i.e. varstatus to varStatus, classname to className). You must make sure that the tags are replaced to forEach and getStatic -- otherwise, the Jelly script will not run correctly.
<   <j:set var="spc" value="&nbsp; " />
>   <j:set var="spc" value="&nbsp;&nbsp;" />
<     <j:forEach var="cs" items="${changeSet.logs}" varstatus="loop">
>     <j:forEach var="cs" items="${changeSet.logs}" varStatus="loop">
<   <j:getStatic var="resultFailure" field="FAILURE" classname="hudson.model.Result" />
>   <j:getStatic var="resultFailure" field="FAILURE" className="hudson.model.Result" />
If you really don't want to go through these steps, you can download the html_gmail.jelly file from here:
The README.TXT file is here:


  1. Thank you very much for this blog.
    I am a very beginner and I want to configure the ext-maio of my hudson to send URL of report to developers.
    I have difficults to follow the steps described above. Can you please, explain where define each part.
    That is what I have done:
    1. create the html_gmail.jetty (copy/paste your)
    2. Place it under $HUDSON_HOME/ext_mai/~/templates
    3. Copy this part at the default content of hudson manager (extended e-mail notification part)
    H3 Failed Tests H3
    pre style="font-size: 12px"
    thank you very much for your help

  2. I have as a result in my mail:
    No tests ran.

    JellyException: Could not parse Jelly script : null

    How can I resolve this problem.

  3. Can you please how to use the Pynliner to modify the html file

  4. You can download the html_gmail.jelly from

    You may have forgotten to escape the ampersand:

    j:set var="spc" value="&nbsp;&nbsp;" />

  5. thank you very much for your help. Now the result is displayed but not the cobertura report.
    My project is grails project. I wonder if that has an impact. If I should specify the path of the report since the structure is different from a java project.

  6. Have you please any idea how we can configure the ext-mail to send alse the findbug, codenarc reports.
    Thank you very much

  7. I used Python's coverage to generate Cobertura reports. You need to find something the equivalent in Grails. Sorry, can't help you on the latter two items. If you find out, you can generate your own blog posting!

  8. I am sending arguments to Jelly script like this
    However inside the script it has no value, I have this:

    And when i print this just there is:
    Where '5' is the build number.
    Could you help me please?