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:
- The JSON Ad API picks the winning ad for the zone and returns its native template variables (Title, Body, Image).
- 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 |
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". Theraw_htmlis 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 Item → Native (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
- 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.
- Campaign under the advertiser, with the native ad item(s) assigned.
- 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”.
1.6 The tracking-links request Brightspot makes
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:
- 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 frompackage com.example.adbutler;to your package name. - Point the modification at your email content type. Every site names its newsletter class differently (
Newsletter,EmailCampaign,Mailing…). InNewsletterAdButlerModification.javachange 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
- Create the newsletter as usual.
- Optionally set a zone or keywords in the AdButler Sponsorship panel.
- Save. The Title, Body, Image and tracking links fill in (read-only).
- Want a different sponsor? Tick Refetch ad on save and save again.
- 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
- From a terminal, run the ad request (
examples/fetch.sh ACCOUNT_ID ZONE_IDin the kit, orjava examples/AdButlerFetchDemo.java ACCOUNT_ID ZONE_IDwith JDK 11+). Confirmtemplate_paramscontains Title, Body and Image. - Run the manual-tracking-links request with the returned
placement_idandbanner_id. Confirmimpressionandclick_redirectare returned. - In Brightspot, configure the site settings and save a newsletter. Confirm the ad fields and both tracking URLs populate.
- 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).
- 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_paramsis always populated. - SendGrid click tracking can wrap the AdButler click link; the redirect chain still ends at AdButler, then the advertiser.
Related articles
- Server-side ad requests via the JSON Ad API
- Where to get manual tracking links
- Tracking statistics with AccuPixel · AccuPixel and the JSON Ad API
- Native ad template overview
- Example native template API
- How to create a native ad item
- AdButler REST API reference
- Brightspot developer guide · Brightspot Newsletter content type