TrigRun
Use Cases

Abandoned Cart Reminder Automation

Schedule automated abandoned cart email triggers with TrigRun. Recover lost revenue by reminding shoppers to complete checkout.

Trigger your abandoned cart recovery process every 30 minutes. TrigRun calls your endpoint, your backend finds carts abandoned for 1+ hours, and sends reminder emails — recovering 5-15% of otherwise lost revenue.

The problem

A customer adds items to their cart and leaves. Industry average cart abandonment rate is 70%. Email reminders sent within 1-3 hours recover the most revenue. You need a reliable process that checks for abandoned carts frequently, sends the first reminder within an hour, and follows up appropriately.

How it works with TrigRun

┌─────────────┐  every 30m    ┌──────────────────┐              ┌──────────────┐
│   TrigRun   │ ─────────────▶│ Your Cart API    │ ────────────▶│ Email        │
│  Scheduler  │  POST /check  │ /api/carts/      │  send emails │ Provider     │
└─────────────┘               │   abandoned      │              │ (SendGrid)   │
       │                      └──────────────────┘              └──────────────┘
       ▼                             │
  Execution log                      ▼
  "12 reminders sent,          Query carts where
   3 already recovered"        updated_at < 1 hour ago
                               AND reminder_sent = false

Create via API

curl -X POST https://api.trigrun.com/v1/jobs \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Abandoned cart checker",
    "kind": "cron",
    "schedule": {
      "cron": "*/30 * * * *"
    },
    "request": {
      "url": "https://api.yourstore.com/api/carts/process-abandoned",
      "method": "POST",
      "headers": {
        "Authorization": "secret://store-admin-key",
        "Content-Type": "application/json"
      },
      "body": {
        "abandon_threshold_minutes": 60,
        "max_reminders": 3,
        "reminder_intervals_hours": [1, 24, 72]
      },
      "timeout_seconds": 60
    },
    "retry_policy": {
      "max_attempts": 2,
      "retry_on_statuses": [500, 502, 503]
    }
  }'

Expected results

Typical run:

FieldExample value
Status200 OK
Duration3,180 ms
Response{"checked": 47, "reminder_1_sent": 8, "reminder_2_sent": 3, "reminder_3_sent": 1, "already_recovered": 2, "total_cart_value": "$1,247.50"}

No abandoned carts found (quiet period):

FieldExample value
Status200 OK
Response{"checked": 0, "reminder_1_sent": 0}

Your endpoint

// Express.js example
app.post('/api/carts/process-abandoned', async (req, res) => {
  const { abandon_threshold_minutes, reminder_intervals_hours } = req.body;
  const cutoff = new Date(Date.now() - abandon_threshold_minutes * 60000);

  // Find abandoned carts
  const carts = await db.carts.findMany({
    where: {
      updatedAt: { lt: cutoff },
      status: 'active',
      reminderCount: { lt: 3 },
    },
    include: { items: true, user: true },
  });

  let sent = { reminder_1: 0, reminder_2: 0, reminder_3: 0, recovered: 0 };

  for (const cart of carts) {
    const reminderNum = cart.reminderCount + 1;
    const hoursThreshold = reminder_intervals_hours[cart.reminderCount];
    const hoursSinceUpdate = (Date.now() - cart.updatedAt) / 3600000;

    if (hoursSinceUpdate < hoursThreshold) continue;

    await sendReminderEmail(cart.user.email, cart.items, reminderNum);
    await db.carts.update({
      where: { id: cart.id },
      data: { reminderCount: reminderNum, lastReminderAt: new Date() },
    });
    sent[`reminder_${reminderNum}`]++;
  }

  res.json({
    checked: carts.length,
    ...sent,
    total_cart_value: carts.reduce((sum, c) => sum + c.total, 0),
  });
});

Reminder sequence

ReminderTimingSubject lineConversion rate
#11 hour after abandon"You left something behind"8-12%
#224 hours"Still thinking about it?"3-5%
#372 hours"Last chance — items selling fast"1-2%

On this page