Skip to content

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": "[email protected]",
    "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": "[email protected]",
    "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": "[email protected]", "event": "purchase", "data": {...}},
      {"subscriber_email": "[email protected]", "event": "purchase", "data": {...}},
      {"subscriber_email": "[email protected]", "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('[email protected]', '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('[email protected]', '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('[email protected]', '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

12 comments

7 comments

  1. femi.adeyemi
    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 us sending to people who already did the thing.
    1. admin
      Solid case study material here. If you're open to it, we'd love to write this up as a blog post — happy to credit you anonymously or otherwise
  2. m.schmidt78
    What's the max number of steps in a sequence before performance becomes a concern? Asking because we have a 14-step nurture and I'm wondering if it's overkill.
  3. phuong.mai.hn
    Solid walkthrough. The conditional-branching example especially — most automation guides skip that and you end up rebuilding from scratch.
    1. admin
      Thanks for the kind words. We try to keep these source-grounded so they age well...
  4. jmorrison.itop…
    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.
    1. admin
      Worth noting — your config diverges from the recommended one in one place that often bites people. Well send a separate note with the suggested change.
  5. d.cohen.tlv
    how do you handle subscribes who join mid-sequence (e.g. via API)? Do they start at step 1 or pick up at a current point?
    1. admin
      That config is exposed in 5.2+. For older versions you'll need to edit the config file directly. We'll add a version-matrix in the article.
  6. aditi.s.bom
    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.
  7. i.rossi.mil
    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.
    1. admin
      Appreciate it. If anything in this needs updating, ping us — we revisit articles every few months.

More in Automation