# Gaspar API Skill Operational runbook for working with [Gaspar](https://gaspar.hidagama.com), an email-campaign platform with a REST API at `https://api.hidagama.com/api/gaspar/`. **Read this before creating any campaign.** Every section maps to a failure mode hit in production — the rules exist because someone skipped them. > If you only have 60 seconds: read the [Pre-flight checklist](#3-pre-flight-checklist-before-any-campaign-fires) and follow it religiously. --- ## Table of contents 1. [API quickstart — auth, base URL, key concepts](#1-api-quickstart) 2. [Time conversion — the #1 mistake](#2-time-conversion--the-1-mistake) 3. [Pre-flight checklist (before any campaign fires)](#3-pre-flight-checklist-before-any-campaign-fires) 4. [Full campaign lifecycle](#4-full-campaign-lifecycle) 5. [Campaign body shape (POST /campaigns)](#5-campaign-body-shape) 6. [HTML email design — table-based, mobile-safe, brand-perfect](#6-html-email-design) 7. [Subject lines — craft + limits](#7-subject-lines) 8. [Recipient management — manual, CSV, Sheets, audiences](#8-recipient-management) 9. [Template syntax (merge fields, fallbacks, special variables)](#9-template-syntax) 10. [The watchdog — and how to unpause without re-pause](#10-the-watchdog) 11. [Click-tracking gotcha + UTM workaround](#11-click-tracking) 12. [Frequency rules + sequence staggering](#12-frequency-rules) 13. [Quick-send vs full campaign vs test-send](#13-quick-send-vs-full-campaign-vs-test-send) 14. [Sequences (multi-step follow-ups)](#14-sequences) 15. [Forms (lead capture)](#15-forms) 16. [Suppressions + replies + inbox](#16-suppressions-replies-inbox) 17. [Analytics — opens, clicks, GA4, deliverability](#17-analytics) 18. [Complete API endpoint reference](#18-complete-endpoint-reference) 19. [MCP tools mapping](#19-mcp-tools-mapping) 20. [Common campaign patterns (cold outreach, T-1 reminder, recovery)](#20-common-patterns) 21. [Five mistakes worth memorizing](#21-five-mistakes) 22. [When to invoke this skill](#22-when-to-invoke) --- ## 1. API quickstart **Base URL:** `https://api.hidagama.com/api/gaspar/` **App URL (UI only — do not call):** `https://gaspar.hidagama.com/app` **Auth:** Bearer token in `Authorization` header. ```bash export GASPAR_API_KEY=gsk_... # mint from gaspar.hidagama.com → Settings → API curl -sS -H "Authorization: Bearer $GASPAR_API_KEY" \ https://api.hidagama.com/api/gaspar/auth/check ``` **Key shape:** Gaspar API keys start with `gsk_`. They're scoped — recommended scopes for AI-assistant integrations: - `campaigns:read` — list campaigns, recipients, stats - `campaigns:write` — create + edit drafts (no send) - `campaigns:launch` — actually fire campaigns (leave OFF when an AI controls the key) **Never** commit the key. **Never** print it in chat output. Cache in `GASPAR_API_KEY` env var, macOS Keychain, 1Password CLI, GitHub Actions secret — never inline. **First call to confirm everything works:** ```bash curl -sS -H "Authorization: Bearer $GASPAR_API_KEY" \ https://api.hidagama.com/api/gaspar/auth/check | jq ``` Returns `{ ok: true, user_id, via, scopes, plan, active, ... }`. If `ok: false` or 401, the key is wrong or expired. **Wrong URLs that return "Not found":** - `gaspar.hidagama.com/api/...` — that's the app frontend - `api.hidagama.com/api/gaspar/v1/...` — no version prefix - `api.hidagama.com/gaspar/...` — missing `/api/` segment --- ## 2. Time conversion — the #1 mistake Gaspar stores `scheduled_start_at` as **UTC milliseconds since epoch**. Operators always think in local time. Confusing the two ships campaigns 3 hours late. | Operator says | What you compute | Sanity check | |---|---|---| | "Send Friday at 6pm IL" | IL is UTC+3 in summer → UTC 15:00 Fri → ms epoch | `date -u -r $((TS_MS/1000))` should print Fri 15:00 UTC | | "Send Monday morning Chicago time" | Chicago is UTC-5 in summer → UTC ~13:00 Mon → ms | Don't confuse operator-locale vs recipient-locale | | "Send tomorrow at 9am" | **ALWAYS ASK: 9am whose time?** | Never guess | **Always echo both timezones before save:** > "Friday 18:00 IL = Friday 15:00 UTC = epoch ms 1780521060000. Confirm?" Helpers: ```bash TZ=Asia/Jerusalem date -d "2026-06-08 09:00" # local → readable TZ=UTC date -d "@1780521060" # epoch → UTC readable TZ=America/Chicago date -d "@1780521060" # epoch → recipient-local # Compute ms epoch from local time: TS_MS=$(TZ=UTC date -d "2026-06-08 13:30" +%s%3N) echo "$TS_MS" ``` --- ## 3. Pre-flight checklist (before any campaign fires) Run every box. Skipping any has cost real campaigns. - [ ] **1. Audience scope confirmed in writing** — "all ~N non-suppressed contacts from list X" not "all contacts" - [ ] **2. Throttle rate sane for sender** — Gmail OAuth caps at ~30/hr safely. Verified custom domains allow more. Verify the connected account at `GET /accounts` - [ ] **3. `scheduled_start_at` echoed in BOTH operator-local AND UTC** before save - [ ] **4. Subject ≤60 chars** — Gmail mobile truncates at ~60. Run `echo -n "subject" | wc -c` - [ ] **5. Pre-header text set** — the hidden text after the subject in inbox preview. Use a `
` block as the first body element. Without it, inbox shows the first line of code or a leftover word. - [ ] **6. Body uses `{{firstName | fallback}}` syntax** — see section 9 - [ ] **7. Unsubscribe link present in footer**: `https://api.hidagama.com/api/gaspar/u?e={{email}}&c={{campaign_id}}`. Required by CAN-SPAM + GDPR. - [ ] **8. CTA link includes UTM**: `?utm_source=gaspar&utm_medium=email&utm_campaign=` — critical, see section 11. - [ ] **9. Test-send to yourself** via `POST /test-send`. Inspect rendered output. Note: test-send skips merge-field substitution — verify the production template, not the test render. - [ ] **10. Audience size ≥ 25** if you want the auto-generated PDF completion report. - [ ] **11. Frequency check** — has this list received an email in the past 48h? Section 12. - [ ] **12. Render check at mobile width** — open HTML in a browser at 375px. If it horizontally scrolls or text overflows, the email is broken on mobile (the majority of opens). If you cannot tick every box, do not press Schedule. --- ## 4. Full campaign lifecycle ``` 1. Mint API key → POST /keys (or Settings UI) 2. Connect a sender account → POST /oauth/start (or /outlook/start, /custom-domains) 3. Verify auth → GET /auth/check 4. (Optional) Add recipients to list → POST /audiences, /sheets-import, etc. 5. Design HTML body → Local — see section 6 6. Create draft campaign → POST /campaigns (status: 'draft') 7. Test-send to yourself → POST /test-send 8. Schedule the real send → PATCH /campaigns/{id}/schedule 9. Monitor in-flight → GET /campaigns/{id} (poll stats) 10. Pause if needed → POST /campaigns/{id}/pause 11. Investigate bounces / replies → GET /campaigns/{id}/recipients, /replies 12. Resume after addressing issues → POST /campaigns/{id}/resume (see section 10) 13. Completion → auto PDF report → arrives at reply_to address (≥25 recipients) 14. Pull analytics → GET /campaigns/{id}/stats, /ga/metrics/{id} ``` Every step is an API call. Every step has gotchas. Sections 5-17 cover each in depth. --- ## 5. Campaign body shape ```json POST /api/gaspar/campaigns Content-Type: application/json Authorization: Bearer $GASPAR_API_KEY { "name": "Q4 Trade Show — T-1 reminder", "subject_template": "One last note before the show opens", "body_html_template": "...", "body_text_template": "Plain-text fallback for non-HTML clients", "from_name": "Your Brand", "reply_to": "you@yourdomain.com", "gaspar_account_id": "", "throttle_per_hour": 30, "jitter_seconds": 0, "scheduled_start_at": 1780713600000, "track_opens": 1, "track_clicks": 1, "campaign_type": "broadcast", "recipients": [ { "email": "recipient@example.com", "company": "Example Co", "firstName": "First", "lastName": "Last", "country": "US", "website": "https://example.com" } ] } ``` **Field reference:** | Field | Type | Notes | |---|---|---| | `name` | string | Internal label, shown in dashboard. Not seen by recipients. | | `subject_template` | string | Goes in the email subject. ≤60 chars. Supports `{{merge_fields}}`. | | `body_html_template` | string | Full HTML email. See section 6 for design rules. | | `body_text_template` | string | Plain-text fallback. Optional but recommended. Some spam filters penalize HTML-only emails. | | `from_name` | string | The display name shown in recipient's inbox ("Your Brand "). | | `reply_to` | string | Where replies go. Often differs from sender. | | `gaspar_account_id` | string | Pre-connected sender account. Get from `GET /accounts`. | | `throttle_per_hour` | number | Sends per hour. Default 30. Gmail OAuth: keep ≤30 unless you've verified higher. Custom domains: higher allowed. | | `jitter_seconds` | number | Random delay (0-N seconds) added to each send to avoid looking robotic. Default 0. | | `scheduled_start_at` | number (ms) | UTC milliseconds. See section 2. | | `track_opens` | 0\|1 | Inserts 1px tracking pixel. | | `track_clicks` | 0\|1 | Should wrap CTA links — see section 11 for the gotcha. | | `campaign_type` | string | `broadcast` (one-shot to list), `sequence` (multi-step). | | `recipients` | array | Flat objects. See section 8 for the schema. | | `attachment_name` | string | Optional. PDF/image attached to every send. | | `attachment_mime` | string | E.g. `application/pdf`. | | `attachment_b64` | string | Base64-encoded attachment body. | | `ab_enabled` | 0\|1 | Enable A/B subject testing. | | `ab_subject_b` | string | Variant B subject when A/B is on. | | `ab_body_html_b` | string | Variant B HTML body. | | `ab_pick_after_mins` | number | After N minutes, auto-pick the winner and send the rest of the list with it. | **Returns:** ```json { "ok": true, "campaign": { "id": "uuid", "status": "draft", "total_recipients": 280, ... } } ``` The campaign starts in `status: 'draft'`. Set `scheduled_start_at` to launch. Status flow: `draft → scheduled → running → completed` (or `paused` mid-flight). --- ## 6. HTML email design Email rendering is a 1998-era nightmare. Gmail strips `
Your inbox-preview line — make it count.
YOUR BRAND
 

Eyebrow / context

Your headline — 8 words max.

Hi {{firstName | there}},

Body copy. Two or three short paragraphs. One idea per paragraph. Verb-first sentences. Active voice.

Your CTA  →

Trust micro-line under the button (optional).

— Your Brand

Your Company
Your address

Unsubscribe  ·  yourdomain.com

``` ### 6.3 Common bugs and how to avoid them | Bug | Cause | Fix | |---|---|---| | Outlook ignores rounded corners on the CTA | Outlook uses MSO HTML, doesn't support `border-radius` | Either accept square buttons in Outlook, OR use MSO conditional VML — see Email Geeks resources | | CTA disappears in Outlook dark mode | Outlook dark mode inverts colors | Set explicit `background-color` on the button table cell, not just the `` | | Mobile clients shrink text below 16px | iOS Safari auto-shrinks | Set explicit font-size on every text element. Don't rely on inheritance. | | Hidden pre-header text appears on iOS | iOS auto-respects `display: none` but preview text shows | Use the trick `
...
` | | Gmail dark mode inverts background | Gmail auto-applies CSS filter | Use `background-color` (not `background-image`) on every wrapper; test in Gmail dark mode | | Yahoo strips `