--- title: Smart Reminders & Late Fees (Pro addon) description: Multi-step automated payment chase. Configure Day 0 → +3 → +7 → +14 reminders, auto-apply late fees after N days, and offer early-payment discounts. Single highest cash-flow ROI feature. ---
Pro · Professional plan Requires 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:** | Feature | Core reminder | Smart Reminders addon | |---|---|---| | Steps | 1 (pre-due only) | Unlimited (pre + post due) | | Per-step email template | No | Yes | | Auto-apply late fee | No | Yes (% or flat) | | Early-payment discount | No | Yes | | "Run now" button | No | Yes | | Per-invoice progress tracking | No | Yes (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: | Step | Offset | Tone | |---|---|---| | 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: | Option | Default | What it does | |---|---|---| | **Enable late fees** | off | Master switch | | **Fee type** | `percent` | `percent` of invoice total, or `flat` currency amount | | **Amount** | 5 | Percent (if `percent`) or flat currency (if `flat`) | | **Apply after (days)** | 7 | Days 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: | Option | Default | What it does | |---|---|---| | **Enable discount** | off | Master switch | | **Discount percent (%)** | 2 | Percentage off | | **Within (days of issue)** | 7 | Pay 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](./team-roles)** enabled, every reminder send and fee application is logged. ## Hooks for developers | Hook | Type | When | |---|---|---| | `easy_invoice_dunning_email_sent` `(invoice_id, step, recipient_email)` | action | Just after a reminder email is `wp_mail()`'d | | `easy_invoice_dunning_late_fee_applied` `(invoice_id, fee, settings)` | action | Just after a late fee is stored on an invoice | | `easy_invoice_dunning_after_tick` `(reminders_sent, fees_applied, scanned)` | action | End of every daily tick | | `easy_invoice_dunning_batch_size` | filter | Override the per-batch invoice count | | `easy_invoice_dunning_tick_budget_seconds` | filter | Override 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()`: | Tag | Source | |---|---| | `{{invoice_number}}` | `_easy_invoice_number` meta, or `#{ID}` if missing | | `{{client_name}}` | `_easy_invoice_customer_name` meta | | `{{total_amount}}` | `_easy_invoice_total`, formatted with 2 decimals | | `{{due_date}}` | `_easy_invoice_due_date`, formatted via the site's `date_format` | | `{{invoice_url}}` | `get_permalink($invoice_id)` | | `{{company_name}}` | `easy_invoice_company_name` option | The body is run through `nl2br()` so plain-text newlines become `
`. 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](../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 - [Email & notifications](../email-settings) — base email config the reminders ride on - [Payments](../payments) — how status flips to `paid` (which stops dunning) - [Webhooks & Zapier Bridge](./webhooks) — fire a webhook when a reminder is sent - [Addons overview](./)