Serving Native Ads in Brightspot Emails

This guide is for publishers who build newsletters in Brightspot CMS, send them through an email service provider such as SendGrid, and want a sponsored native ad — title, body, image and a tracked link — in each send, with impressions and clicks reported in AdButler.

A downloadable Brightspot integration kit (Java classes, email templates, and a command-line demo) accompanies this guide: . adbutler-brightspot-integration.zip.

How it works

Email is fixed HTML: once it is sent, it cannot call an ad server to pick a new ad on every open. So the ad is chosen once, when the newsletter is created, and the tracking is done with links that keep counting for as long as the email is opened.

When an editor saves a newsletter, Brightspot makes two server-side calls to AdButler:

  1. The JSON Ad API picks the winning ad for the zone and returns its native template variables (Title, Body, Image).
  2. The REST API returns permanent manual tracking links — an impression pixel and a click link — for that ad.

Both are written into the email HTML. Every later open loads the pixel and every click goes through the click link, so AdButler counts them normally.

Step Who What happens What AdButler records
1 Editor in Brightspot Creates the newsletter and saves
2 Brightspot → AdButler Ad request: GET …/adserve/;ID=…;setID=…;type=json;output_json_native_template_mode=2 Nothing with AccuPixel on; one “send” impression with it off
3 Brightspot → AdButler GET /v2/manual-tracking-links?placement=…&ad_item=…&type=impression,click_redirect
4 Brightspot Stores Title / Body / Image / pixel URL / click URL on the newsletter and renders them into the HTML
5 Brightspot → SendGrid → subscribers Email is sent as fixed HTML
6 Subscriber's email client Open loads the pixel; click follows the click link +1 impression per open, +1 click per click, indefinitely
What it is not: the ad does not rotate per open. One send = one ad, chosen by AdButler's normal rotation, schedule and keyword rules at creation time. All opens of that send count against that ad.

Part 1 — AdButler setup

Done by your AdButler administrator, in the AdButler UI or via the AdButler REST API.

1.1 Create the native template

Native templates define the variables an ad item carries. This integration uses three: Title (text), Body (text) and Image (image URL).

In the UI: Ad Items → Native Templates → Add Template → type Custom Rendering / API → add the three variables with the placeholders below → paste the HTML → Save.

Via the API: POST https://api.adbutler.com/v2/templates/native with header Authorization: Basic <API_KEY> (your raw API key, not base64-encoded):

{
  "name": "Email Native Ad (Title / Body / Image)",
  "raw_html": "<div class=\"sponsored\"><a href=\"[TRACKING_LINK]\"><img src=\"[%Image%]\" alt=\"[%Title%]\" /></a><h3><a href=\"[TRACKING_LINK]\">[%Title%]</a></h3><p>[%Body%]</p></div>",
  "variables": [
    { "name": "Title", "placeholder": "[%Title%]", "type": "text", "max_length": 90 },
    { "name": "Body",  "placeholder": "[%Body%]",  "type": "text", "max_length": 300 },
    { "name": "Image", "placeholder": "[%Image%]", "type": "image_url" }
  ]
}

The response returns the template id and an id for each variable; ad items are created against the variable IDs.

Naming matters. The JSON field names Brightspot receives are the placeholders with [% and %] removed: [%Title%] becomes "Title". The raw_html is only rendered when the template is served on a web page — for email, Brightspot uses the variables directly — but every placeholder must still appear in it.

1.2 Create native ad items from the template

In the UI: open the campaign → Add Ad ItemNative (Custom Rendering / API) → choose the template → fill in Title, Body, choose or upload the Image → set the Destination URL → Save.

Via the API: POST /v2/ad-items/native, with variables keyed by variable ID:

{
  "name": "Acme Spring Sale - Newsletter native",
  "location": "https://acme.example/spring?utm_source=newsletter",
  "template": 1234,
  "width": 600,
  "height": 200,
  "variables": {
    "5001": "Spring sale: 30% off everything",
    "5002": "Three days only. Free shipping on orders over $50.",
    "5003": "https://cdn.acme.example/spring-600x200.jpg"
  }
}

The Image variable accepts a public URL or a Media Library ID; AdButler returns a servable URL either way.

1.3 Zone, campaign and placement

  1. Zone: Publishers → your publisher → Add Zone → Standard, 600×200 (or your chosen size). One zone per email slot. Note the zone ID; Brightspot needs it. Only assign native-template ad items to this zone.
  2. Campaign under the advertiser, with the native ad item(s) assigned.
  3. Placement of the campaign on the zone with a schedule. Weights, dates, quotas and keyword targeting all apply at the moment the newsletter is created.

1.4 AccuPixel (optional)

AccuPixel is not required. It only changes what the ad request in step 2 records:

AccuPixel The JSON ad request (step 2) Each open (manual pixel)
On (recommended) records nothing +1 impression
Off (default) records one impression per request +1 impression

With AccuPixel off, each send shows opens + 1 impressions. Harmless at scale, but visible on small lists and while testing. Enable it under Settings → Ad Serving → AccuPixel.

1.5 The ad request Brightspot makes

GET https://servedbyadbutler.com/adserve/;ID={ACCOUNT_ID};size=600x200;setID={ZONE_ID};type=json;output_json_native_template_mode=2
Parameter Meaning
ID Your AdButler account ID
size Ad size; must match the zone / ad item
setID Zone ID
type=json Request a JSON response
output_json_native_template_mode=2 Return the native template variables as a template_params object and leave body empty. 1 = rendered markup only (default), 3 = both. output_json_template_params=1 is an older alias for 2.
kw=a,b (optional) Keyword targeting, so one zone can serve different sponsors per edition

Example response. Fields Brightspot uses are marked ★; fields it must not use are marked ✗:

{
  "status": "SUCCESS",                       ★  "NO_ADS" when nothing is eligible
  "placements": {
    "placement_1": {
      "banner_id": "523700001",              ★  ad item ID → manual-tracking-links ad_item
      "campaign_id": "1401950",
      "placement_id": "3612500",             ★  placement ID → manual-tracking-links placement
      "width": "600", "height": "200",
      "body": "",
      "template_params": {                   ★
        "Title": "Spring sale: 30% off everything",
        "Body":  "Three days only. Free shipping on orders over $50.",
        "Image": "https://servedbyadbutler.com/getad.img?libBID=98765"
      },
      "redirect_url": "...",                 ✗  single-use, expires — not for email
      "accupixel_url": "...",                ✗  single-use, expires — not for email
      "image_url": "...", "viewable_url": "...", "eligible_url": "...", "beacons": []
    }
  }
}

Notes on template_params: text variables are plain strings (HTML-escape them when rendering); image_url variables are URLs; number variables are numbers; file variables are returned as a complete <img> tag. If the winning ad is not a native-template ad, template_params is an empty array [] and Brightspot treats it as “no ad”.

GET https://api.adbutler.com/v2/manual-tracking-links?placement={placement_id}&ad_item={banner_id}&type=impression,click_redirect&protocol=https
Authorization: Basic {API_KEY}
{
  "object": "manual_tracking_links",
  "self": "/v2/manual-tracking-links",
  "data": {
    "impression":     "https://servedbyadbutler.com/adserve/;...type=tpixel...",
    "click_redirect": "https://servedbyadbutler.com/redirect.spark?..."
  }
}

These are the same links shown under Manual Tracking Links on a placement's page in the AdButler UI. They exist for exactly this situation — creatives that are cached somewhere and cannot make a new ad request:

accupixel_url / redirect_url from the ad response Manual tracking links
Lifetime Short validity window No expiry
Reuse Counted once; later hits are ignored as duplicates Counted every time
Intended for A live web page that just made the request Cached creatives, native ads, email

Manual-pixel requests count toward your monthly ad-request total — one per open.


Part 2 — Brightspot setup

Who does this part?

Brightspot is a Java-based CMS: every content type on a site (Article, Author, Newsletter…) is a Java class in a codebase the publisher owns, builds and deploys. Adding fields or behaviour means adding Java files and redeploying. This part is therefore for whoever deploys code changes to the publisher's Brightspot site — an in-house developer, or the agency/partner that built the site. Editors never touch code; once deployed, they simply see a new “AdButler Sponsorship” panel on the newsletter.

The integration kit contains five Java classes, two email templates and a stand-alone demo. Realistically it is a few hours of work for someone who already knows the site's codebase.

2.1 Install the classes

Copy src/main/java/com/example/adbutler/*.java from the kit into the Brightspot project and:

  1. Rename the package. Java files are organised in folders named after the company (for example com/acme/...). Move the files into your own package and change the first line of each from package com.example.adbutler; to your package name.
  2. Point the modification at your email content type. Every site names its newsletter class differently (Newsletter, EmailCampaign, Mailing…). In NewsletterAdButlerModification.java change one line:
@Modification.Classes(Newsletter.class)   // ← your email content type, e.g. EmailCampaign.class
Class Role
AdButlerSettings Adds an AdButler cluster to Site Settings: account ID, default zone, ad size, API key (stored as a secret), and the serve / API base URLs.
AdButlerNativeAd Embedded record holding the fetched Title, Body, Image, manual pixel URL, manual click URL and the AdButler IDs. Read-only in the editor.
AdButlerNativeAdClient Makes the two HTTP calls. fetch() is the ad request, attachManualTrackingLinks() is the tracking-links request, fetchWithTracking() runs both. Uses java.net.http and Jackson (bundled with Brightspot).
NewsletterAdButlerModification Adds the AdButler Sponsorship panel to the newsletter: zone override, keywords, Refetch ad on save, Disabled, and the stored ad. Its beforeSave() calls fetchWithTracking() when an ad is needed.
AdButlerNativeAdViewModel Exposes title, body, imageUrl, clickUrl and impressionPixelUrl to the email template.

2.2 Add the template block

Copy templates/newsletter-sponsored-block.hbs into the newsletter's email template where the sponsorship should appear, and bind sponsorship to AdButlerNativeAdViewModel. The block is table-based with inline styles for email clients. In essence it renders:

<a href="{{clickUrl}}"><img src="{{imageUrl}}" alt="{{title}}" width="600"></a>
<h3><a href="{{clickUrl}}">{{title}}</a></h3>
<p>{{body}}</p>
<img src="{{impressionPixelUrl}}" width="1" height="1" alt="">

If the email body is assembled in a SendGrid Dynamic Template instead, pass the same five values as dynamic_template_data.sponsor and use templates/sendgrid-dynamic-template-block.html.

2.3 Build, deploy and configure

Build and deploy the site as usual. Then, in the Brightspot admin, go to Sites & Settings → your site → AdButler and enter the account ID, default zone ID, ad size (600x200) and API key (created under Settings → API Settings in AdButler).

2.4 Editor workflow

  1. Create the newsletter as usual.
  2. Optionally set a zone or keywords in the AdButler Sponsorship panel.
  3. Save. The Title, Body, Image and tracking links fill in (read-only).
  4. Want a different sponsor? Tick Refetch ad on save and save again.
  5. No sponsor for this edition? Tick Disabled.

2.5 Failure handling

The client never throws. If AdButler returns NO_ADS, an HTTP error, a timeout, or a non-native ad, the newsletter saves normally with no sponsorship block. If the tracking-links call fails, the ad is not rendered either — an untracked sponsorship is never sent. Warnings are written to the application log.


Part 3 — Testing checklist

  1. From a terminal, run the ad request (examples/fetch.sh ACCOUNT_ID ZONE_ID in the kit, or java examples/AdButlerFetchDemo.java ACCOUNT_ID ZONE_ID with JDK 11+). Confirm template_params contains Title, Body and Image.
  2. Run the manual-tracking-links request with the returned placement_id and banner_id. Confirm impression and click_redirect are returned.
  3. In Brightspot, configure the site settings and save a newsletter. Confirm the ad fields and both tracking URLs populate.
  4. Send a test email to yourself. Open it twice and click once. AdButler reporting should show 2 impressions and 1 click (3 impressions if AccuPixel is off).
  5. Pause the campaign, tick Refetch ad on save, save. Confirm the block is omitted and no error is shown.

Things to know

  • Ad-request volume: the creation-time request is one ad request; every open is one more via the manual pixel. Budget on opens, not sends.
  • Apple Mail Privacy Protection pre-fetches images, so opens are inflated in the same way they are for every email provider.
  • Keep the email zone native-only so template_params is always populated.
  • SendGrid click tracking can wrap the AdButler click link; the redirect chain still ends at AdButler, then the advertiser.

Can't find what you're looking for?

Send us an email

hello@adbutler.com

Visit the blog

For more ad serving tips, industry news and AdButler insights.