AAuditPro Suite· Finance manual
Finance manual Emails

The 9 templates

CodeTriggerRecipient
client.welcomeLead converted to clientClient primary contact
client.info_requestManual — request data from clientClient primary contact
client.fieldwork_startJob status → in_progressClient primary contact
client.report_deliveredAudit report sentClient primary contact
quote.sentQuote status → sentLead/client primary contact
invoice.sentInvoice status → sentClient primary contact
invoice.payment_reminderCron auto-flip overdue + dunning chainClient primary contact + accountant cc
payment.receivedPayment recorded with allocationsClient primary contact
compliance.expiry_reminderDocument/visa expiring (M13/M16)Employee work email + manager cc

Branded HTML layout

Every email follows a single 600px-wide table layout with:

Token engine

Each template carries an available_tokens documentation field. Common tokens:

TokenResolves to
{firm.name}companies.company_name
{firm.address}Full firm address one-line
{client.contact_name}Primary contact full name
{client.legal_name}clients.legal_name
{invoice.number}e.g. INV/2026/0042
{invoice.amount}Formatted OMR 5,565.000
{invoice.due_date}Formatted DD MMM YYYY
{quote.number}e.g. QU/2026/0017
{payment.amount}Formatted OMR
{payment.method}Bank Transfer · Cash · etc.
{job.number}e.g. JOB/2026/0042
{partner.name}Engagement partner name

Caller passes a flat dict to MailerService::sendTemplate(); unknown tokens stay literal (so a typo doesn't blow up the send).

Sample — invoice.sent template

Al Musaaid Auditing Bureau
Tax Invoice INV/2026/0042

Dear Mr. Ahmed Al-Bahja,

Please find attached our tax invoice INV/2026/0042 for OMR 5,565.000 covering the Annual Audit FY 2025 engagement.

Payment terms: 30 days from issue date. Due by 12 May 2026.

Please remit to our bank account (details on the invoice). For any questions, reply to this email or call your engagement manager directly.

Thank you for choosing Al Musaaid Auditing Bureau.

Best regards,
Praveendas S · Engagement Partner

Al Musaaid Auditing Bureau · Muscat, Oman · VAT OM1100012345 · +968 9XXX XXXX

Lifecycle hooks → email triggers

EventDispatcher fires on these state transitions; each hook may queue an email:

All hooks are post-commit + try/catch — SMTP failures never break the upstream state machine.

Email log

email_log table captures every send:

Forensic — never deleted. Available at Settings → Communication → Email Log with filters.

Test mode

For sandbox testing without spamming clients:

Critical for staging environments and partner training. Never go to production with test_mode=1.

Step-by-step — customise a template

  1. Open Settings → Communication → Templates

    List of 9 system-seeded templates.

  2. Click into one

    Subject line · HTML body · plain-text fallback · available tokens.

  3. Edit

    Change wording, tone, signature. Preview renders in the right panel with sample data.

  4. Save

    Audit-logged. Future sends use the new wording. Old sends preserved with their original wording in email_log.body_html.

  5. Test send

    From settings → "Send test email". Use test_mode + your own email. Verify rendering.

Try this

Send a test invoice. Check Settings → Communication → Email Log — your test send appears with sent_at, SMTP host, Message-ID. Filter by template=invoice.sent to see all outbound invoices over time. This is your audit trail for "did the client get the invoice?"

Watch out

Don't include hard-coded firm details in template HTML. Use {firm.name}, {firm.address} tokens — when you onboard a second firm, only the data changes, the template stays. Hard-coded names = manual rewrite per firm.

Tip — open-tracking

If your SMTP provider supports tracking pixels, enable them. email_log.opened_at stamps when the client opens the email — useful for invoice + dunning campaigns. "Client opened the reminder 3 times but didn't pay" is a different escalation conversation than "client never opened it" (probably an email-deliverability issue).