Skip to content
Pro · Professional planRequires Easy Invoice Pro with a Professional (or Agency) license.

Smart Reminders & Late Fees

A multi-step dunning workflow on top of the basic "X days before due" reminder that already ships in Pro. Add steps, customize the email template per step, auto-apply a late fee after N days overdue, and optionally offer an early-payment discount.

When to use it

  • You're losing cashflow to overdue invoices
  • You don't have time to manually chase clients
  • You want fees / discounts handled consistently across every invoice

The single highest-ROI feature in the Pro plan. Typical recovery on previously-written-off invoices is 15–25% in the first month.

How it differs from the built-in reminder

Easy Invoice Pro already ships a basic "remind me X days before due date" feature. Smart Reminders adds three things that don't exist in core:

FeatureCore reminderSmart Reminders addon
Steps1 (pre-due only)Unlimited (pre + post due)
Per-step email templateNoYes
Auto-apply late feeNoYes (% or flat)
Early-payment discountNoYes
"Run now" buttonNoYes
Per-invoice progress trackingNoYes (last step + late-fee flags)

You can run both simultaneously — they don't conflict on cron hook names or meta keys.

Enabling

  1. Easy Invoice → Addons
  2. Find Smart Reminders & Late Fees
  3. Click Activate
  4. Open via the Settings → link or navigate to Easy Invoice → (sidebar) → Smart Reminders (slug: easy-invoice-addon-dunning)

The addon registers one daily WP-Cron event: easy_invoice_dunning_daily_tick.

Configuring the workflow

Reminder Schedule

Each row in the schedule is one step. A step is "send this email to the client when the invoice is X days from its due date." Offset days are relative to the invoice's due date — negative = before, positive = after.

Default schedule shipped with the addon:

StepOffsetTone
1-3 days"Friendly reminder — your invoice is due in 3 days"
2+1 day"Your invoice is overdue"
3+7 days"Second notice"
4+14 days"Final notice"

Each step has a Subject and a Body with smart-tag interpolation. Available tags:

{{invoice_number}}   {{client_name}}   {{total_amount}}
{{due_date}}         {{invoice_url}}   {{company_name}}

Add as many steps as you want with Add step, reorder by changing offset values (rows are sorted on save). Each step fires exactly once per invoice — the addon records the last-sent step in _easy_invoice_dunning_last_step so a step is never re-sent.

Late Fee

Auto-apply a fee after N days overdue:

OptionDefaultWhat it does
Enable late feesoffMaster switch
Fee typepercentpercent of invoice total, or flat currency amount
Amount5Percent (if percent) or flat currency (if flat)
Apply after (days)7Days overdue before the fee is calculated and stored

The calculated fee is stored in invoice meta _easy_invoice_late_fee_amount and _easy_invoice_late_fee_applied_at. To surface it on the rendered invoice, the addon hooks two filters automatically:

easy_invoice_calculate_total     ⟶ adds fee to total
easy_invoice_extra_line_items    ⟶ appends fee as a line item

That means the late fee shows up automatically on the invoice — no extra wiring needed.

Early-Payment Discount

Encourage clients to pay before due date:

OptionDefaultWhat it does
Enable discountoffMaster switch
Discount percent (%)2Percentage off
Within (days of issue)7Pay within N days of the issue date to qualify

The discount is offered in your reminder emails (use a smart-tag in the body) and recorded as discount-related meta when honoured — full automation of discount enforcement requires payment-gateway integration and is on the roadmap.

Running it

Daily cron

The hook easy_invoice_dunning_daily_tick runs once per day. WP-Cron is "pseudo-cron" — it runs on real page loads, so a quiet site may run a day late. If you have real cron set up (wp-cron.php from system cron), the run is exactly on schedule.

"Run now"

The Run now button (top-right of the addon page) calls the same Service::runDailyTick() synchronously. Useful when you've just changed the schedule and want to see immediate results. The redirect shows a banner like:

Dunning run complete. Scanned 247 invoice(s) — sent 12 reminder(s), applied 3 late fee(s).

Behaviour at scale

The tick is designed for thousands of invoices:

  • Batched query: invoices are loaded 50 at a time (posts_per_page=50, paged=N). Never posts_per_page=-1.
  • SQL-level filter: only invoices whose _easy_invoice_due_date <= today + max_pre_offset_days are loaded — the runner never touches paid invoices, cancelled invoices, or invoices whose first reminder is still weeks away.
  • Wall-clock budget: by default 25 seconds. If the runner can't finish the queue in that time, it stops at a batch boundary and the next cron picks up where it left off (per-invoice progress is durable via _easy_invoice_dunning_last_step).
  • Per-invoice progress: re-runs are idempotent — a step that already fired for an invoice is skipped.

Filters for tuning:

php
// How many invoices to load per query (default 50)
add_filter('easy_invoice_dunning_batch_size', fn() => 100);

// How long each tick may run (default 25s)
add_filter('easy_invoice_dunning_tick_budget_seconds', fn() => 45);

Permissions

Anyone with manage_options can use the addon. With Team Members & Audit Log enabled, every reminder send and fee application is logged.

Hooks for developers

HookTypeWhen
easy_invoice_dunning_email_sent (invoice_id, step, recipient_email)actionJust after a reminder email is wp_mail()'d
easy_invoice_dunning_late_fee_applied (invoice_id, fee, settings)actionJust after a late fee is stored on an invoice
easy_invoice_dunning_after_tick (reminders_sent, fees_applied, scanned)actionEnd of every daily tick
easy_invoice_dunning_batch_sizefilterOverride the per-batch invoice count
easy_invoice_dunning_tick_budget_secondsfilterOverride the tick wall-clock budget

Example — Slack notification on every late fee:

php
add_action('easy_invoice_dunning_late_fee_applied', function ($invoice_id, $fee, $settings) {
    $client = get_post_meta($invoice_id, '_easy_invoice_customer_name', true);
    $msg    = sprintf('Late fee of %.2f applied to invoice #%d (%s)', $fee, $invoice_id, $client);
    wp_remote_post('https://hooks.slack.com/services/…', [
        'body' => wp_json_encode(['text' => $msg]),
    ]);
}, 10, 3);

Smart tag reference

The body and subject of each reminder step support these tags. They're interpolated server-side just before wp_mail():

TagSource
_easy_invoice_number meta, or #{ID} if missing
_easy_invoice_customer_name meta
_easy_invoice_total, formatted with 2 decimals
_easy_invoice_due_date, formatted via the site's date_format
get_permalink($invoice_id)
easy_invoice_company_name option

The body is run through nl2br() so plain-text newlines become <br>. HTML in the body is allowed (kept by wp_kses_post on save).

Coexistence with the built-in Pro reminder

The basic Pro reminder uses cron hook easy_invoice_send_payment_reminders and option easy_invoice_payment_reminder_settings. Smart Reminders uses cron hook easy_invoice_dunning_daily_tick and option easy_invoice_dunning_settings. No conflict — but you might double-send if both are configured to email at the same offset. If you've enabled the addon, consider disabling the built-in reminder under Settings → Email → Payment Reminder.

Troubleshooting

"Run now" reports 0 reminders sent

Most common causes:

  1. The schedule's offset has no step matching today's today − due_date for any invoice
  2. Invoices are missing a _easy_invoice_due_date meta value
  3. Invoices are already paid / cancelled / refunded (excluded by design)
  4. Each matching step has already been sent for the matching invoice (per _easy_invoice_dunning_last_step meta)

The "Scanned N invoice(s)" count in the banner tells you how many invoices the SQL pre-filter let through — if Scanned is 0, your filter cut everything out (often a missing due date).

Cron isn't firing

WP-Cron only runs when someone visits the site. Add real cron:

cron
*/5 * * * * curl -fsS https://your-site.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1

Then disable WP's built-in cron:

php
// wp-config.php
define('DISABLE_WP_CRON', true);

See Troubleshooting → WP-Cron.

Late fee doesn't show on invoice

The fee writes to _easy_invoice_late_fee_amount meta and the addon's two filters surface it. If you've replaced the invoice render pipeline with a custom template, you may need to call those filters yourself:

php
$total = apply_filters('easy_invoice_calculate_total', $base_total, $invoice_id);
$items = apply_filters('easy_invoice_extra_line_items', $items, $invoice_id);

See also