Skip to main content

Common Patterns

Practical, copy-paste-ready patterns for the most frequent CallMeLater use cases. Each example shows the SDK code alongside the equivalent curl request.


Trial Expiration

Schedule a webhook to fire when a user's trial ends. If they subscribe before the timer runs out, cancel it by idempotency key.

Schedule the expiration webhook:

import { CallMeLater } from 'callmelater';

const client = new CallMeLater({ apiToken: 'sk_live_...' });

const action = await client.http('https://api.example.com/subscriptions/expire')
.post()
.payload({ user_id: 123, action: 'downgrade_to_free' })
.inDays(14)
.idempotencyKey('trial:user:123')
.send();

console.log(action.id); // act_...

Cancel if the user subscribes:

await client.cancelAction({ idempotency_key: 'trial:user:123' });

Tips: Use max_attempts: 5 -- this webhook matters. Make your endpoint idempotent so duplicate deliveries are harmless.


Deployment Approval

Get human sign-off before deploying to production. The approval is sent immediately and the callback URL receives the response.

await client.reminder('Production deploy v2.4.1')
.to('tech-lead@example.com')
.toMany(['devops@example.com'])
.message('Approve deployment of v2.4.1 to production?')
.buttons('Approve', 'Reject')
.noSnooze()
.expiresInDays(1)
.escalateTo(['cto@example.com'], 2)
.inMinutes(0)
.callback('https://ci.example.com/webhooks/approval')
.idempotencyKey('deploy:v2.4.1:prod')
.send();

Handle the callback to continue (or abort) your CI pipeline based on the response:

// In your callback handler
app.post('/webhooks/approval', (req, res) => {
const { event, payload } = req.body;
if (event === 'reminder.responded' && payload.response === 'confirmed') {
triggerDeploy('v2.4.1', 'production');
}
res.sendStatus(200);
});

Delayed Cleanup

Delete temporary resources (exports, uploads, sandbox data) after a retention period. Schedule the cleanup when you create the resource.

await client.http('https://api.example.com/exports/exp_abc123')
.delete()
.inDays(30)
.idempotencyKey('cleanup:export:exp_abc123')
.noRetry()
.send();

Tips: Use noRetry() / max_attempts: 1 -- if the resource was already deleted manually, one failed attempt is enough. Include the resource ID in the idempotency key for easy cancellation.


Scheduled Reports

Generate a weekly report every Monday morning using recurring actions. CallMeLater handles the re-scheduling automatically.

await client.http('https://api.example.com/reports/generate')
.post()
.payload({ type: 'weekly_summary' })
.at('next_monday')
.timezone('America/New_York')
.everyWeeks(1)
.repeatForever()
.idempotencyKey('report:weekly')
.send();

Stop the reports: Cancel the action when you no longer need reports.

await client.cancelAction({ idempotency_key: 'report:weekly' });

Tips: Set timezone so "Monday morning" stays consistent across DST changes. Use maxOccurrences(52) instead of repeatForever() to automatically stop after a year.


Follow-Up Sequence

Send an onboarding email series at day 1, day 3, and day 7 after signup. Each email gets its own action with a unique idempotency key so you can cancel the remaining ones if the user unsubscribes.

const userId = 123;
const steps = [
{ days: 1, template: 'welcome', key: 'day1' },
{ days: 3, template: 'getting_started', key: 'day3' },
{ days: 7, template: 'pro_tips', key: 'day7' },
];

for (const step of steps) {
await client.http('https://api.example.com/emails/send')
.post()
.payload({ user_id: userId, template: step.template })
.inDays(step.days)
.idempotencyKey(`onboard:user:${userId}:${step.key}`)
.send();
}

Cancel remaining emails if the user unsubscribes:

// Cancel all pending onboarding emails for a user
for (const key of ['day1', 'day3', 'day7']) {
await client.cancelAction({ idempotency_key: `onboard:user:${userId}:${key}` });
}

Invoice Reminder with Escalation

Remind a customer about an upcoming payment. If they do not respond within 48 hours, escalate to your accounts receivable team.

await client.reminder('Invoice INV-2026-042 payment due')
.to('billing@customer.com')
.message(
'Invoice INV-2026-042 ($3,200) is due in 5 days. ' +
'Click Confirm to mark as scheduled for payment.'
)
.buttons('Confirm Payment', 'Decline')
.allowSnooze(2)
.expiresInDays(7)
.escalateTo(['ar@yourcompany.com', '+15551234567'], 48)
.at('2026-02-20T09:00:00')
.timezone('America/Chicago')
.idempotencyKey('invoice:INV-2026-042:reminder')
.callback('https://api.example.com/webhooks/invoice-response')
.send();

Tips: Schedule the reminder a few days before the due date using scheduled_for. Set escalation.after_hours based on urgency -- 48 hours gives the customer time to respond before your team gets involved. Include a phone number in escalation contacts for SMS notifications.