Keplars

Campaign Guide

Step-by-step guide to creating an audience, adding contacts, and sending your first email campaign.

This guide walks through the full campaign flow - from creating an audience to tracking results after sending.


Create an Audience

An audience is the contact list your campaign will be sent to. Create one in the dashboard under Audiences → New Audience, or via the API:

curl -X POST "https://api.keplars.com/api/v1/public/audiences/add-audience" \
  -H "Authorization: Bearer $KEPLARS_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Newsletter"}'
const res = await fetch(
  'https://api.keplars.com/api/v1/public/audiences/add-audience',
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.KEPLARS_ADMIN_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
    name: 'Newsletter',
  }),
  },
);
const { data } = await res.json();
import requests, os
res = requests.post(
    'https://api.keplars.com/api/v1/public/audiences/add-audience',
    json={
        'name': 'Newsletter',
    },
    headers={'Authorization': f'Bearer {os.environ["KEPLARS_ADMIN_KEY"]}'},
)
data = res.json()['data']
package main

import (
    "bytes"
    "fmt"
    "io"
    "net/http"
    "os"
)

func main() {
    body := bytes.NewBufferString(`{"name":"Newsletter"}`)
    req, _ := http.NewRequest("POST",
        "https://api.keplars.com/api/v1/public/audiences/add-audience", body)
    req.Header.Set("Authorization", "Bearer "+os.Getenv("KEPLARS_ADMIN_KEY"))
    req.Header.Set("Content-Type", "application/json")
    resp, _ := (&http.Client{}).Do(req)
    defer resp.Body.Close()
    b, _ := io.ReadAll(resp.Body)
    fmt.Println(string(b))
}
<?php
$ch = curl_init('https://api.keplars.com/api/v1/public/audiences/add-audience');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode([
        'name' => 'Newsletter',
    ]),
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('KEPLARS_ADMIN_KEY'),
        'Content-Type: application/json',
    ],
]);
$data = json_decode(curl_exec($ch), true)['data'];
curl_close($ch);
using System.Net.Http;
using System.Net.Http.Json;

var client = new HttpClient();
client.DefaultRequestHeaders.Add(
    "Authorization", $"Bearer {Environment.GetEnvironmentVariable("KEPLARS_ADMIN_KEY")}"
);
var response = await client.PostAsJsonAsync(
    "https://api.keplars.com/api/v1/public/audiences/add-audience",
    new
{
    name = "Newsletter"
}
);
var body = await response.Content.ReadAsStringAsync();
use reqwest::Client;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    let res = client
        .post("https://api.keplars.com/api/v1/public/audiences/add-audience")
        .header("Authorization", format!("Bearer {}", std::env::var("KEPLARS_ADMIN_KEY")?))
        .json(&json!({
    "name": "Newsletter"
}))
        .send()
        .await?;
    let data: serde_json::Value = res.json().await?;
    println!("{}", data);
    Ok(())
}
#include <curl/curl.h>
#include <cstdlib>
#include <string>

int main() {
    CURL* curl = curl_easy_init();
    std::string auth = std::string("Authorization: Bearer ") + std::getenv("KEPLARS_ADMIN_KEY");
    const char* payload = "{\"name\":\"Newsletter\"}";
    struct curl_slist* headers = nullptr;
    headers = curl_slist_append(headers, auth.c_str());
    headers = curl_slist_append(headers, "Content-Type: application/json");
    curl_easy_setopt(curl, CURLOPT_URL, "https://api.keplars.com/api/v1/public/audiences/add-audience");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
}
#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    CURL* curl = curl_easy_init();
    char auth[256];
    snprintf(auth, sizeof(auth), "Authorization: Bearer %s", getenv("KEPLARS_ADMIN_KEY"));
    const char* payload = "{\"name\":\"Newsletter\"}";
    struct curl_slist* headers = NULL;
    headers = curl_slist_append(headers, auth);
    headers = curl_slist_append(headers, "Content-Type: application/json");
    curl_easy_setopt(curl, CURLOPT_URL, "https://api.keplars.com/api/v1/public/audiences/add-audience");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    return 0;
}
{
  "data": {
    "id": "019dd568-1ac3-7320-ad48-ea6189b937d0",
    "name": "Newsletter",
    "contactCount": 0,
    "subscribeUrl": "https://keplars.com/s/a3f9bc4d..."
  }
}

The subscribeUrl is your public signup form - share it on your website so visitors can subscribe themselves.


Add Contacts

You have three ways to populate your audience:

Option 1 - Dashboard form

Go to the audience page, click Add Contact, and fill in the form.

Option 2 - CSV import

Click the ··· menu → Import CSV on the audience page. Your CSV should have an email column. Supported columns: email, first_name, last_name, company_name, job_title, phone_number, linkedin, timezone, tags (pipe-separated).

Option 3 - API

curl -X POST "https://api.keplars.com/api/v1/public/contacts/add-contact" \
  -H "Authorization: Bearer $KEPLARS_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","audience_id":"019dd568-1ac3-7320-ad48-ea6189b937d0","first_name":"John","last_name":"Doe","tags":"newsletter"}'
const res = await fetch(
  'https://api.keplars.com/api/v1/public/contacts/add-contact',
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.KEPLARS_ADMIN_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
    email: '[email protected]',
    audience_id: '019dd568-1ac3-7320-ad48-ea6189b937d0',
    first_name: 'John',
    last_name: 'Doe',
    tags: 'newsletter',
  }),
  },
);
const { data } = await res.json();
import requests, os
res = requests.post(
    'https://api.keplars.com/api/v1/public/contacts/add-contact',
    json={
        'email': '[email protected]',
        'audience_id': '019dd568-1ac3-7320-ad48-ea6189b937d0',
        'first_name': 'John',
        'last_name': 'Doe',
        'tags': 'newsletter',
    },
    headers={'Authorization': f'Bearer {os.environ["KEPLARS_ADMIN_KEY"]}'},
)
data = res.json()['data']
package main

import (
    "bytes"
    "fmt"
    "io"
    "net/http"
    "os"
)

func main() {
    body := bytes.NewBufferString(`{"email":"[email protected]","audience_id":"019dd568-1ac3-7320-ad48-ea6189b937d0","first_name":"John","last_name":"Doe","tags":"newsletter"}`)
    req, _ := http.NewRequest("POST",
        "https://api.keplars.com/api/v1/public/contacts/add-contact", body)
    req.Header.Set("Authorization", "Bearer "+os.Getenv("KEPLARS_ADMIN_KEY"))
    req.Header.Set("Content-Type", "application/json")
    resp, _ := (&http.Client{}).Do(req)
    defer resp.Body.Close()
    b, _ := io.ReadAll(resp.Body)
    fmt.Println(string(b))
}
<?php
$ch = curl_init('https://api.keplars.com/api/v1/public/contacts/add-contact');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode([
        'email' => '[email protected]',
        'audience_id' => '019dd568-1ac3-7320-ad48-ea6189b937d0',
        'first_name' => 'John',
        'last_name' => 'Doe',
        'tags' => 'newsletter',
    ]),
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('KEPLARS_ADMIN_KEY'),
        'Content-Type: application/json',
    ],
]);
$data = json_decode(curl_exec($ch), true)['data'];
curl_close($ch);
using System.Net.Http;
using System.Net.Http.Json;

var client = new HttpClient();
client.DefaultRequestHeaders.Add(
    "Authorization", $"Bearer {Environment.GetEnvironmentVariable("KEPLARS_ADMIN_KEY")}"
);
var response = await client.PostAsJsonAsync(
    "https://api.keplars.com/api/v1/public/contacts/add-contact",
    new
{
    email = "[email protected]",
    audience_id = "019dd568-1ac3-7320-ad48-ea6189b937d0",
    first_name = "John",
    last_name = "Doe",
    tags = "newsletter"
}
);
var body = await response.Content.ReadAsStringAsync();
use reqwest::Client;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    let res = client
        .post("https://api.keplars.com/api/v1/public/contacts/add-contact")
        .header("Authorization", format!("Bearer {}", std::env::var("KEPLARS_ADMIN_KEY")?))
        .json(&json!({
    "email": "[email protected]",
    "audience_id": "019dd568-1ac3-7320-ad48-ea6189b937d0",
    "first_name": "John",
    "last_name": "Doe",
    "tags": "newsletter"
}))
        .send()
        .await?;
    let data: serde_json::Value = res.json().await?;
    println!("{}", data);
    Ok(())
}
#include <curl/curl.h>
#include <cstdlib>
#include <string>

int main() {
    CURL* curl = curl_easy_init();
    std::string auth = std::string("Authorization: Bearer ") + std::getenv("KEPLARS_ADMIN_KEY");
    const char* payload = "{\"email\":\"[email protected]\",\"audience_id\":\"019dd568-1ac3-7320-ad48-ea6189b937d0\",\"first_name\":\"John\",\"last_name\":\"Doe\",\"tags\":\"newsletter\"}";
    struct curl_slist* headers = nullptr;
    headers = curl_slist_append(headers, auth.c_str());
    headers = curl_slist_append(headers, "Content-Type: application/json");
    curl_easy_setopt(curl, CURLOPT_URL, "https://api.keplars.com/api/v1/public/contacts/add-contact");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
}
#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    CURL* curl = curl_easy_init();
    char auth[256];
    snprintf(auth, sizeof(auth), "Authorization: Bearer %s", getenv("KEPLARS_ADMIN_KEY"));
    const char* payload = "{\"email\":\"[email protected]\",\"audience_id\":\"019dd568-1ac3-7320-ad48-ea6189b937d0\",\"first_name\":\"John\",\"last_name\":\"Doe\",\"tags\":\"newsletter\"}";
    struct curl_slist* headers = NULL;
    headers = curl_slist_append(headers, auth);
    headers = curl_slist_append(headers, "Content-Type: application/json");
    curl_easy_setopt(curl, CURLOPT_URL, "https://api.keplars.com/api/v1/public/contacts/add-contact");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
    curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    return 0;
}

Create a Template

Campaigns use email templates. Create one in the dashboard under Templates → New Template.

Required placeholder

Your template must include {"{{"}unsubscribeUrl{"}}"} somewhere in the body. Keplars injects the correct per-audience unsubscribe link at send time. Campaigns will be blocked from sending if this placeholder is missing.

You can use any Handlebars variables in your template:

<p>Hi {{firstName}},</p>
<p>Here's your weekly update...</p>
<p><a href="{{unsubscribeUrl}}">Unsubscribe</a></p>

Then pass values when setting up the campaign:

{
  "variables": {
    "firstName": "will be replaced per-contact automatically"
  }
}

For help building templates, see Email Templates and Template Libraries.


Create a Campaign

In the dashboard, go to Campaigns → New Campaign and fill in:

FieldDescription
NameInternal name for this campaign
AudienceThe audience to send to
TemplateThe email template to use
From EmailSender address (must be a verified email or custom domain)
From NameDisplay name shown in the inbox
SubjectEmail subject line (if not defined in the template)

Only contacts with subscribed status in the selected audience will receive the campaign. Unsubscribed and globally blocked contacts are automatically excluded.


Send or Schedule

Send now - click Send Campaign. Keplars fans out the emails immediately through the priority queue.

Schedule - click Schedule, pick a date/time and timezone. The campaign status changes to SCHEDULED and sends automatically at that time.

Scheduling requires LAUNCH plan

Scheduled campaigns are available on the LAUNCH plan and above. On the FREE plan, use Send now instead.

You can cancel a scheduled campaign any time before it starts sending.


Track Results

Once sending starts, the campaign dashboard shows live metrics:

MetricDescription
DispatchedEmails handed to the mail provider
DeliveredConfirmed delivery to inbox
OpenedRecipient opened the email
ClickedRecipient clicked a tracked link
BouncedEmail bounced (hard or soft)
FailedDelivery failed after retries

Click any campaign to drill into per-contact delivery status - see exactly who received, opened, clicked, or bounced.


Handle Unsubscribes

When a contact clicks the unsubscribe link in a campaign email, their status in that audience is automatically set to unsubscribed. They are:

  • Excluded from all future campaigns to that audience
  • Not removed from the audience - they still appear in the contacts list
  • Not affected in other audiences - their other subscriptions remain active

They can re-subscribe at any time by submitting the audience's subscribeUrl form.

You can also check and filter by subscription status via the API using subscribedOnly=true on the List Contacts endpoint.


Next Steps

On this page