1. Introduction

GR8 CRM is a set of Grails Web Application Framework plugins that makes it easy to develop web applications with CRM functionality.

You can find more information about GR8 CRM on the main documentation site http://gr8crm.github.io.

1.1. Customer Relationship Management

Customer relationship management (CRM) is a system for managing a company’s interactions with current and future customers. It involves using technology to organize, automate and synchronize sales, marketing, customer service, and technical support. Wikipedia

The GR8 CRM "Ecosystem" currently contains over 40 Grails plugins. For a complete list of plugins see http://gr8crm.github.io.

Each GR8 CRM plugin defines a Bounded Context that focus on one specific domain, for example contact, project or document.

2. Campaign Management Plugin

This plugin provide storage and services for managing many types of campaigns in GR8 CRM applications.

Note that this plugin does not contain any user interface components. This plugin contains domain classes and services only. The plugin crm-campaign-ui provides a Twitter Bootstrap based user interface for managing campaigns. crm-campaign-ui depends on crm-campaign so you only need to include crm-campaign-ui in your BuildConfig.groovy if you want end-user campaign management features.

3. Domain Model

Campaign Domain Model

3.1. CrmCampaign

4. CrmCampaignService

Like most other GR8 CRM plugins this plugin have a main service with methods for creating, searching and updating campaigns.

4.1. Create a new campaign

CrmCampaign createCampaign(Map params, boolean save = false)

To create a new campaign you call the createCampaign(Map, boolean) method with a map of property values and a boolean to indicate if the campaign should be persisted inside the method or if you want to persist it later yourself.

def c = crmCampaignService.createCampaign(...);

println "Campaign $c created"

4.2. Banner Campaign

A banner campaign is used when you want to display a banner image on your web page with a link that leads to a page with more information. A banner campaign uses crm-content plugin to store banner images.

def grailsApplication
def bannerCampaign (1)

final ApplicationContext ctx = grailsApplication.mainContext

final CrmCampaign parentCampaign = crmCampaignService.createCampaign(number: "banner",
        name: "Banners on home page", description: "Parent campaign for all banners on home page",
        true) (2)

def createCampaign = { name, desc, img, link -> (3)
    def c = crmCampaignService.createCampaign(parent: parentCampaign,
            name: name, description: desc, code: "ads", true)
    if(c.hasErrors()) {
        throw new RuntimeException("Cannot create campaign")
    } else {
        def image = ctx.getResource(img).file (4)
        image.withInputStream { is ->
            crmCampaignService.addCampaignResource(c, is, "banner.jpg", image.length(), "image/jpeg")
        bannerCampaign.configure(c) {
          url link (5)
// Create three campaigns, one for each of our new amazing products.
createCampaign "Product X", "Our amazing new Product X...",
        "images/banners/product-x.jpg", "http://mywebshop.com/view/product-x"
createCampaign "Product Y", "Our amazing new Product Y...",
        "images/banners/product-y.jpg", "http://mywebshop.com/view/product-y"
createCampaign "Product Z", "Our amazing new Product Z...",
        "images/banners/product-z.jpg", "http://mywebshop.com/view/product-z"
1 Inject the BannerCampaign bean so we can use it to configure our campaigns.
2 Create a parent campaign that group all banner campaigns.
3 Create a closure to make it easy to create sub-campaigns.
4 Banner images are located in web-app/images/banners so we can get them via ApplicationContext.
5 Each sub-campaign is configured with the link to more information.
Rendering banners

To display the three banners we created above we can call the images action in CrmCampaignResourceController to get a list of images. In the following example we use Javascript and jQuery to add all images to a banner area on our web page. When the visitor clicks on an image it will redirect the browser to the configured link.

$.getJSON("/crmCampaignResource/images?t=1&code=ads", function(data) { (1)
    var container = $("#banners");
    for(var i = 0; i < data.length; i++) {
        var banner = data[i];
        var $img = $("<img/>").attr("src", banner.uri).attr("alt", banner.title);
        var $link = $("<a/>").attr("href", banner.url).attr("target", '_blank')
                .attr("title", banner.title);
1 Because GR8 CRM is always multi-tenant we must specify in which tenant our campaigns are stored (t=1).

4.3. Information Campaign

If you develop a public facing site with GR8 CRM you can display different content depending on where your visitors come from. For example if they come via a link on Twitter you can display one type of content, if they come via Facebook you can display another type of content.

def twitter = crmCampaignService.createCampaign(number: "twitter-app-2016", code: "twitter",
    name: "Tweets", description: "Visitors that came to us via a tweet", true)
def text = """
<h3>Download our Twitter App</h3>
<div class="pull-right">
   <@link controller="download" action="index" query="id=twitter-app" class="btn btn-success">Download</@link>
<p>With our new Twitter app you can do amazing things...</p>
crmCampaignService.addCampaignResource(twitter, text, "twitter-app.ftl", [contentType: "text/html")

With one or more campaigns (created above) we can use the <crm:campaign> GSP tag to render the text.

crm {
    campaign {
        landingParam = ['utm_source', 'campaign', '_c] (1)
        landingPages = [home: 'index'] (2)
        sessionParam = 'campaign' (3)
1 A list of known campaign source query parameters. This list is used to lookup the right campaign.
2 The landing pages that will trigger campaign lookup, in this example it’s HomeController.index.
3 If a campaign is found a reference to it is stored in a HTTP session attribute called 'campaign'.
<div class="well">
    <crm:campaign campaign="${request.session.campaign}" template="default-campaign.ftl"/> (1)
1 If the session attribute 'campaign' is non-null the text for that campaign is rendered. Otherwise a default text template is rendered using the 'crm-content' plugin.

4.4. Product Discount Campaign

If you develop a web shop with the crm-product plugin you can configure product campaigns to offer discounts or value-add products for your customers. The ProductDiscountCampaign handler is very flexible with lots of configuration options.

The following example applies 10 % discount on "Product X" products if the customer lands on our home page or web shop and a campaign source query parameter has the value "twitter".

crm {
    campaign {
        landingParam = ['utm_source', 'campaign', '_c]
        landingPages = ['home|blog|webshop': 'index']
        sessionParam = 'campaign'
        productDiscountCampaign.round = true
def twitter = crmCampaignService.createCampaign(number: "twitter2016", code: "twitter",
    name: "Promotion for twitter users", description: "Follow us on Twitter to receive a 10 % discount on Product X.")
productDiscountCampaign.configure(twitter) {
    products = ['productXbasic', 'productXpro', 'productXultimate'] (1)
    discount = 0.10 (2)
    condition = 'any' (3)
campaign.save(failOnError: true)
1 Only apply discount on a limited list of products.
2 10 % discount
3 The discount is applied without any amount threshold. If condition is a number the discount will only be applied if the total shopping cart value is higher than 'condition'.

See ProductDiscountCampaignSpec.groovy for more examples.

4.5. Email Campaign

def emailCampaign (1)

def campaign = crmCampaignService.createCampaign(name: "Test campaign", true)
emailCampaign.configure(campaign) {
    subject = "Test"
    sender = "info@mycompany.com"
    parts = ['body']
    body = """<h1>Hello World!</h1>"""
} (2)
crmCampaignService.createRecipients(campaign, [[email: 'me@mycompany.com'], [email: 'joe.average@customer.com']]) (3)
crmEmailCampaignService.send() (4)
1 Inject the EmailCampaign bean so we can use it to configure our campaign.
2 Configure the email layout and other email parameters.
3 Add two recipients.
4 Start the campaign. This will send the email to the recipients.

4.5.1. Open tracking

If you want to know if a recipient has opened the email you can include an invisible "beacon" image that when requested from the server notifies the campaign that the recipient has viewed the email. However this is not 100% accurate because most people today don’t show images automatically in their email clients. They have to manually force a download of remote content (images). But it can give you a hint about how successfull your campaign is.

To activate the tracking image add the following configuration parameter:

crm.campaign.email.track = true

This adds a tracking image to all outbound emails and you can see the result in the target group tab on the campaign.

4.5.2. Bounce Tracking (return-to-sender)

When you send out large amount of emails you must have a process to handle invalid email addresses and other type of return-to-sender emails. The quartz job CrmEmailBounceJob helps you with that burden. It periodically scans the IMAP mailbox that you used as sender or reply-to address. If it finds an error report from an external mail server it looks up the recipient in your GR8 CRM database and marks it with error status. Optionally it can tag the CrmContact instance with a tag that tells you that this contact’s email address it not valid. Future email campaign can ignore contacts tagged as invalid. Or you can query and delete them from your address database.

crm {
    campaign {
        job.bounce.enabled = true
        email {
            bounce {
                imap.host = 'imap.googlemail.com'
                imap.port = 993
                imap.username = 'info@yourcompany.com' (1)
                imap.password = 'password' (2)
                imap.store = 'imaps'
                to = 'newsletter@yourcompany.com' (3)
                tag = 'wrong-email' (4)
                clearEmail = true (5)
                tenant = 1 (6)
1 Your sender or reply-to address
2 GMail users, you can generate an app specific password in Google Admin Console
3 If the sender or reply-to address is an alias, specify the address here to filter messages on To:
4 Contacts will be tagged with this value, exclude or set to null if you don’t want to use tagging
5 If true the email property of the recipient will be set to null (to make it impossible to send email to this recipient again)
6 The tenant to process in a multi-teant environment

5. Changes


New method CrmCampaignService#addRecipient(Map) that accepts a single recipient


When a campaign is copied, all attached resources (html, images, etc.) are now copied.


Better support for supplying custom model to email templates.


Fixed bug that caused the link between recipient and contact to be missing when sending emails.


Better configurability for the email campaign bounce tracker.


First public release (2.4.0 got corrupted when published to grails.org).

6. License

This plugin is licensed with Apache License version 2.0

7. Source Code

The source code for this plugin is available at https://github.com/technipelago/grails-crm-campaign

8. Contributing

Please report issues or suggestions.

Want to improve the plugin: Fork the repository and send a pull request.