API Triggers for Custom Automations — Wiring AcelleMail to Anything

Your CRM, app, or backend can fire AcelleMail automations directly — no Zapier, no manual tagging. This guide walks the UI setup (custom-event triggers + API token generation) plus a complete payload + signing reference in the collapsible developer section.

When you need an API-triggered automation

The standard AcelleMail triggers (subscribed-to-list, email opened, tag added) cover most marketing-side events. The Custom Event trigger is for everything else:

  • "Subscriber finished onboarding step 3 in our app" — fires from your backend
  • "Customer renewed their subscription in Stripe" — fires from a Stripe webhook bridge
  • "Order shipped from warehouse" — fires from your shipping integration
  • "Support ticket closed" — fires from Zendesk / Intercom

The pattern is always: your-system → HTTPS POST to AcelleMail's events API → AcelleMail's automation triggered by that event name → email sent (or tag added, or webhook out, or whatever the flow does).

This article walks the AcelleMail-side setup. The developer-side code is in the collapsible.

UI walkthrough — get your AcelleMail side ready

Step 1: generate an API token

In AcelleMail's top-right user menu, click API & WebhooksAPI tokensNew token.

Give it a descriptive name (crm-bridge-2026 or shopify-webhook-prod). Copy the token immediately — AcelleMail shows it once.

If you scope tokens per integration, you can revoke just that one when the bridge moves or breaks, without affecting other integrations.

Step 2: build the automation that listens

Open the visual automation builder

In AcelleMail's left sidebar, click AutomationAutomations. The index lists every automation in this account with its current state:

Automations index

Click New automation in the top-right toolbar. The trigger picker opens:

Trigger picker — pick what starts the automation

For the trigger picker, choose Custom event:

  1. Event name — pick a stable identifier you'll send from your system. Examples: onboarding_step_3, subscription_renewed, support_ticket_closed. Lowercase + underscores, no spaces.
  2. Save the automation

The automation is now listening. Add downstream steps (send email, add tag, send webhook):

Listening automation — custom event trigger

Step 3: test from the AcelleMail UI

Inside the automation, click TestSimulate event → fill in a test subscriber email + payload. AcelleMail runs the flow once with this test data:

Automation flow — test mode

The automation log records the test run; verify your downstream email or webhook actions worked correctly before you wire up the real source system.

Step 4: configure your external system to POST

Now fire the event from outside AcelleMail. The HTTP call:

POST https://acellemail.com/api/v1/events
Headers:
  Authorization: Bearer <your_api_token>
  Content-Type: application/json
Body:
  {
    "subscriber_email": "user@example.com",
    "event": "onboarding_step_3",
    "data": {
      "step_name": "added_first_team_member",
      "duration_seconds": 45
    }
  }

The event value must match the event name configured in step 2 above. The subscriber_email must already exist on the list the automation is scoped to (or use upsert: true to create-if-not-exists).

The data object is freeform — payload-shaped JSON your automation steps can read via {{ event.data.step_name }} etc.

Step 5: monitor

The Automations index shows total runs per automation:

Automations index — run counts

Open the automation detail → run log → see every event received, with timestamp + subscriber + payload, plus the path each subscriber took through the flow:

Run log + per-subscriber path

Failed runs (e.g. subscriber not found, payload invalid) show in red — click to see the error message.

Common UI signals + fixes

Symptom Likely cause UI fix
API returns 401 Unauthorized Token typo'd or revoked Regenerate token from API & Webhooks page; rotate in source system
API returns 404 Not Found Wrong API path Should be https://your-acellemail-domain.com/api/v1/events (not /api/v2)
API returns 200 but automation doesn't run Event name in payload doesn't match automation's configured event name Open automation → confirm the exact event name string
Automation runs but the email step skipped Subscriber doesn't exist on the automation's scoped list Either pre-create the subscriber, or use upsert: true in the API call
Same event fires multiple times Bridge service retries on timeout, AcelleMail isn't idempotent on event-id Include a unique event_id in payload; configure automation step to skip if event_id already processed
Payload data missing in email body Merge tag uses wrong path {{ event.data.step_name }} not {{ step_name }} — payload is nested under event.data

What event names work well

Naming events stably is one of the small things that pays back forever:

  • Use past tensepurchase_completed not purchase (clear it already happened)
  • One concept per eventonboarding_step_3_completed is fine; onboarding_or_signup_or_invite is not
  • Don't put data in the namepurchased_yoga_mat is wrong; the product is in data.product, the event is purchase_completed
  • Match your internal product vocabulary — if engineering calls it "session_started", call the event "session_started" too. Don't introduce a new vocabulary just for AcelleMail.

Common automation patterns

Use case Event from your system AcelleMail flow does
Onboarding nudge after 3 days idle onboarding_paused Sends "stuck on something?" email
Renewal confirmation subscription_renewed Tags as renewing-customer + thank-you email
Cart abandonment cart_abandoned Wait 1 hour → reminder email; wait 24 hours → discount email
Subscription cancel subscription_cancelled Add tag, suppress marketing sends, send win-back at 30/60/90 days
Customer success milestone nps_response_recorded If score >= 9, send referral CTA; if score <= 6, webhook to CRM ticket
Login activity user_logged_in Update last-active timestamp; trigger re-engagement if 30+ days gap
Developer: full API reference, signing, retries, batch endpoints, SDK examples

The custom-event API and adjacent endpoints in detail.

Authentication:

Authorization: Bearer <token>

Tokens are issued per-account; rotate them when team members leave. Scope-per-integration is recommended — separate tokens for crm-bridge, shopify-webhook, support-tool-sync — so a leak from one source rotates without cascading.

Fire a custom event:

curl -X POST "https://acellemail.com/api/v1/events" \
  -H "Authorization: Bearer $ACELLE_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Event-ID: $(uuidgen)" \
  -d '{
    "subscriber_email": "user@example.com",
    "event": "onboarding_step_3",
    "data": {
      "step_name": "added_first_team_member",
      "duration_seconds": 45,
      "completed_at": "2026-05-19T14:30:00Z"
    },
    "upsert": true
  }'

Response (200 OK):

{
  "event_id": "evt_a1b2c3...",
  "subscriber_uid": "sub_x9y8z7...",
  "automation_runs_triggered": ["auto_onboarding_3_nudge"],
  "processed_at": "2026-05-19T14:30:01Z"
}

The X-Event-ID header is for idempotency — if your bridge retries on timeout, repeated calls with the same event-id are deduplicated server-side.

Batch events (for high-volume backfill):

curl -X POST "https://acellemail.com/api/v1/events/batch" \
  -H "Authorization: Bearer $ACELLE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {"subscriber_email": "a@example.com", "event": "purchase", "data": {...}},
      {"subscriber_email": "b@example.com", "event": "purchase", "data": {...}},
      {"subscriber_email": "c@example.com", "event": "purchase", "data": {...}}
    ]
  }'

Max 1000 events per batch. Useful for backfilling historical events when first integrating a CRM into AcelleMail.

Webhook signing (when AcelleMail webhooks out to your system):

If you configure AcelleMail to Send webhook as an automation step, AcelleMail signs every outbound request with an HMAC-SHA256 header:

X-AcelleMail-Signature: t=1684320000,v1=abc123def456...

Verify on your side:

import hmac, hashlib, time

def verify_webhook(body, signature_header, secret):
    parts = dict(p.split('=') for p in signature_header.split(','))
    timestamp = parts['t']
    sig = parts['v1']
    payload = f"{timestamp}.{body}".encode()
    expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)

Reject any request older than 5 minutes (replay protection) and verify the signature matches — this prevents anyone-with-the-URL from forging requests.

Retries:

AcelleMail's outbound webhooks retry with exponential backoff on 5xx responses: 1m, 5m, 30m, 2h, 12h, 24h. After 6 failed attempts the webhook is marked failed-permanent and the automation step's run shows red.

For inbound events FROM your system, you control retries — if AcelleMail returns 5xx, retry with exponential backoff. Don't retry on 4xx (bad payload — retrying won't fix it).

SDK examples:

// Node.js
const fetch = require('node-fetch');

async function fireEvent(email, event, data) {
  const res = await fetch('https://acellemail.com/api/v1/events', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.ACELLE_TOKEN}`,
      'Content-Type': 'application/json',
      'X-Event-ID': crypto.randomUUID(),
    },
    body: JSON.stringify({
      subscriber_email: email,
      event,
      data,
      upsert: true,
    }),
  });
  if (!res.ok) throw new Error(`AcelleMail event failed: ${res.status}`);
  return res.json();
}

// Usage:
await fireEvent('user@example.com', 'onboarding_step_3', {
  step_name: 'added_first_team_member',
});
# Python
import requests, uuid, os

def fire_event(email, event, data):
    res = requests.post(
        'https://acellemail.com/api/v1/events',
        headers={
            'Authorization': f"Bearer {os.environ['ACELLE_TOKEN']}",
            'Content-Type': 'application/json',
            'X-Event-ID': str(uuid.uuid4()),
        },
        json={
            'subscriber_email': email,
            'event': event,
            'data': data,
            'upsert': True,
        },
        timeout=10,
    )
    res.raise_for_status()
    return res.json()

# Usage:
fire_event('user@example.com', 'onboarding_step_3', {
    'step_name': 'added_first_team_member'
})
// PHP / Laravel
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

function fireAcelleEvent(string $email, string $event, array $data): array
{
    $response = Http::withToken(config('services.acelle.token'))
        ->withHeaders(['X-Event-ID' => (string) Str::uuid()])
        ->timeout(10)
        ->post('https://acellemail.com/api/v1/events', [
            'subscriber_email' => $email,
            'event' => $event,
            'data' => $data,
            'upsert' => true,
        ]);

    $response->throw();
    return $response->json();
}

// Usage:
fireAcelleEvent('user@example.com', 'onboarding_step_3', [
    'step_name' => 'added_first_team_member',
]);

Local development testing — use webhook.site or ngrok to expose your local dev server to AcelleMail's webhook deliveries while developing:

ngrok http 3000
# AcelleMail webhook URL: https://<ngrok-id>.ngrok.io/your-endpoint

Update the automation's webhook destination during dev; flip back to your production URL when shipping.

Rate limits:

The events API accepts 1000 requests/minute per token. Burst higher than that returns 429 with Retry-After header — back off and retry. For high-volume systems, use the batch endpoint instead.

Schema versioning:

If your event payload schema changes (e.g. you rename data.product to data.sku), bump the event name explicitly: purchase_completedpurchase_completed_v2. AcelleMail automations target one event name; switching is opt-in per-automation, so old data and new data don't crash each other.

Audit trail:

Every API call to /api/v1/events is logged in AcelleMail's audit log (admin → Audit log). Useful for forensics when a bug fires unexpected events — search by token, by event name, by subscriber email.

Related articles

8 Kommentare

6 Kommentare

  1. phuong.mai.hn
    Always test the END of the sequence first, not the start. Most testing focuses on email 1 but the longest-tenure subscribers are at the end and that's where bugs surface.
  2. akira.tnk88
    How do you handle subscribers who join mid-sequence (e.g. via API)? Do they start at step 1 or pick up at a current point?
    1. admin
      Currently a manual step. Theres a feature request tracking it on the repo if you want to +1
    2. admin (bearbeitet)
      We're aware of the silent-bail-out on deleted customers — there's an open issue for it. Workaround for now: monitor the campaign:rerun log for absence of expected log lines, alert when silent for > 20 min.
  3. danrey.dev
    We do almost exactly this but with one tweak — we use the 'goal' node to exit subscribers from the sequence early when they complete a target action. Saves u sending to people who already did the thing
  4. lucas.bernard.…
    The visual flow diagram is exactly what I needed. Our welcome series has been a mess of forgotten branches — going to redo it tonight using this as the template.
  5. cw.dev.sh
    Built a 9-email welcome series last quarter using this pattern. Took 4 days end-to-end. Open rate on email 1 is 62%, drops to 28% by email 9 — which is actually higher engagement than our broadcast list. Highly recommend the format
  6. cmendoza.mx
    Solid walkthrough. The conditional-branching example especially — most automation guides skip that and you end up rebuilding from scratch.

More in Automation