Email marketing
The email command mirrors the entire email marketing surface across the
app — /email, /settings/email-domain, /email/templates, /email/audience,
and /email/campaigns. From the terminal you can manage authenticated
sender domains (DKIM/SPF/DMARC), template content, audience contacts and
segments, and end-to-end campaigns including scheduling, sending, and
cancellation.
Backed by /api/v1/app/email/*. Every call uses your stored oc_live_
token.
Quick start
# 1. Verify a sender domain
splashify email domain add example.com
splashify email domain verify example.com # checks DNS records
splashify email domains # see all + status
# 2. Build a template
splashify email template create \
--name "Spring promo" \
--subject "20% off this week" \
--html-file ./spring-promo.html
# 3. Stage an audience
splashify email audience contact add --email alice@example.com --name "Alice"
splashify email audience segment create --name "Active subscribers" \
--filters '{"conditions":[{"field":"is_subscribed","operator":"equals","value":true}],"logic":"and"}'
# 4. Send a campaign
splashify email campaign create \
--name "Spring launch" \
--template <template_id> \
--from "promos@example.com" \
--segment <segment_id>
splashify email campaign send <campaign_id>Command tree
email
├── stats / dashboard global metrics
├── domains list authenticated sender domains
├── domain
│ ├── add <domain> create + return DNS records to publish
│ ├── verify <domain> re-check DNS (DKIM/SPF/DMARC/Return-Path)
│ └── delete <domain> remove a domain
├── templates list templates
├── template
│ ├── <template_id> show one
│ ├── create POST a new template
│ ├── update <template_id> PUT changes (RMW)
│ ├── delete <template_id> remove
│ ├── preview <template_id> render preview (HTML + plain text)
│ └── stats <template_id> usage counts
├── audience
│ ├── contacts list audience contacts
│ ├── contact
│ │ ├── add add a contact
│ │ ├── update <contact_id> edit
│ │ └── delete <contact_id> remove
│ ├── segments list audience segments
│ └── segment
│ ├── create new segment
│ ├── update <id> edit
│ ├── delete <id> remove
│ ├── add-contacts <id> bulk-add contacts to a segment
│ └── remove-contacts <id> bulk-remove
└── campaigns list campaigns
└── campaign
├── <campaign_id> show one
├── create draft a new campaign
├── send <id> send immediately
└── cancel <id> cancel a scheduled / running campaignDomain authentication
Email deliverability hinges on SPF/DKIM/DMARC alignment. Both providers (Meta and the partner backend) refuse to send from unverified domains.
# Add a domain — backend returns DKIM CNAMEs + SPF/DMARC TXT records to publish
splashify email domain add example.com
# After publishing the DNS records, verify
splashify email domain verify example.com
# Re-list with verification status
splashify email domains | jq '.domains[] | {domain, dkim_verified, spf_verified, dmarc_verified}'| Backed by | GET /app/email/domains |
|---|---|
POST /app/email/domains {domain} | |
POST /app/email/domains/:domain/verify | |
DELETE /app/email/domains/:domain |
Verification is idempotent — re-run after each DNS edit; the backend re-resolves each record and updates the row.
Templates
# Inline HTML
splashify email template create \
--name "Welcome" --subject "Welcome to our store" \
--html "<h1>Hi {{first_name}}</h1><p>…</p>"
# From a file
splashify email template create \
--name "Spring promo" --subject "20% off" --html-file ./promo.html
# Update (RMW — backend's PUT replaces every column)
splashify email template update <template_id> --subject "New subject only"
# Render preview (handy for QA before sending)
splashify email template preview <template_id>| Backed by | GET /app/email/templates |
|---|---|
POST /app/email/templates | |
PUT /app/email/templates/:template_id | |
DELETE /app/email/templates/:template_id | |
GET /app/email/templates/:template_id/preview | |
GET /app/email/templates/:template_id/stats |
Variables. Use {{first_name}}, {{email}}, or any contact attribute as
a placeholder. The backend renders them per recipient at send time.
Audience
The audience surface is distinct from the messaging surface — these are email-only contacts and segments, scoped to your domain.
# Contacts
splashify email audience contacts # list
splashify email audience contact add --email alice@example.com --name "Alice"
splashify email audience contact update <contact_id> --name "Alice Smith"
splashify email audience contact delete <contact_id>
# Segments (filter DSL same shape as messaging segments)
splashify email audience segments
splashify email audience segment create \
--name "Active subscribers" \
--filters '{"conditions":[{"field":"is_subscribed","operator":"equals","value":true}],"logic":"and"}'
splashify email audience segment update <id> --name "Refreshed"
splashify email audience segment delete <id>
# Bulk membership
splashify email audience segment add-contacts <id> \
--contact-ids c1,c2,c3
splashify email audience segment remove-contacts <id> \
--contact-ids c1| Backed by | GET/POST/PUT/DELETE /app/email/audience/contacts[/:id] |
|---|---|
GET/POST/PUT/DELETE /app/email/audience/segments[/:id] | |
POST /app/email/audience/segments/:id/add-contacts | |
POST /app/email/audience/segments/:id/remove-contacts |
Campaigns
# Draft
splashify email campaign create \
--name "Spring launch" \
--template <template_id> \
--from "promos@example.com" \
--segment <segment_id> \
[--schedule 2026-06-01T10:00:00Z]
# Lifecycle
splashify email campaigns # list
splashify email campaign <campaign_id> # detail
splashify email campaign send <campaign_id> # send now (skips/overrides schedule)
splashify email campaign cancel <campaign_id> # cancel scheduled or in-flight| Backed by | GET /app/email/campaigns |
|---|---|
POST /app/email/campaigns | |
GET /app/email/campaigns/:id | |
POST /app/email/campaigns/:id/send | |
POST /app/email/campaigns/:id/cancel |
| Flag | Required | Notes |
|---|---|---|
--name | yes | Display name |
--template | yes | template_id |
--from | yes | A verified domain address — must match one of splashify email domains with *_verified: true |
--segment | yes | Audience segment_id |
--schedule | no | ISO 8601 UTC; omit to send via send |
Same preflight as broadcasts — campaigns refuse if no active plan / trial or wallet balance is insufficient for the recipient count × per-email rate.
Stats and reporting
splashify email stats # global dashboard
splashify email template stats <template_id> # opens / clicks / bounces per template
splashify email campaign <campaign_id> | jq '.stats' # per-campaign delivery| Backed by | GET /app/email/stats |
|---|---|
GET /app/email/templates/:id/stats |
Common workflows
Verify every domain in one go
splashify email domains | jq -r '.domains[].domain' | \
xargs -I{} splashify email domain verify {}Bulk-import an audience from CSV
# csv: email,name
while IFS=, read -r email name; do
splashify email audience contact add --email "$email" --name "$name"
done < audience.csvWait for a scheduled campaign to finish
CID=<campaign_id>
while : ; do
STATUS=$(splashify email campaign "$CID" | jq -r '.campaign.status // .status')
echo "$(date -u +%H:%M:%S) $STATUS"
[ "$STATUS" = "completed" ] || [ "$STATUS" = "cancelled" ] && break
sleep 30
doneFind templates with low open rates
splashify email templates | jq -r '.templates[].template_id' | \
while read tid; do
splashify email template stats "$tid" | \
jq --arg t "$tid" '{template_id: $t, opens: .stats.opens, sends: .stats.sends, open_rate: (.stats.open_rate // 0)}'
done | jq -s 'sort_by(.open_rate)'Re-send a campaign to a fresh segment
# Clone a campaign by copying its config to a new one
OLD=$(splashify email campaign <old_id>)
TPL=$(echo "$OLD" | jq -r '.campaign.template_id')
FROM=$(echo "$OLD" | jq -r '.campaign.from_email')
splashify email campaign create --name "Re-run May" \
--template "$TPL" --from "$FROM" --segment <new_segment_id>Troubleshooting
“Domain not verified” on campaign create — --from references a
domain that hasn’t passed all four DNS checks (DKIM, SPF, DMARC,
Return-Path). Run splashify email domain verify <domain> after DNS
propagates. dig CNAME splashifypro._domainkey.<domain> confirms DKIM
locally.
“insufficient_balance” on campaign send — wallet won’t cover the
campaign cost (recipients × per-email rate). The error includes the
shortfall.
Preview shows raw {{first_name}} — variables only resolve at send
time against a real contact. The preview endpoint uses sample values
when available, but missing fields print as-is.
Audience contact import is slow — the CLI sends one POST per row.
For thousands of contacts, batch via the segment add-contacts endpoint:
upload contacts in parallel, collect their IDs, then call
add-contacts once with the full list.
See also
splashify broadcasts— the WhatsApp equivalent.splashify segments— messaging segments (a different audience pool from email segments).splashify walletandsplashify expenses— billing for sent emails.