GAP School Module 04 — Email Engine Lesson 4.2

A single “lead notification” email that fires for every form type is the email equivalent of one form capturing every intent. It tells the recipient nothing useful and trains them to ignore it. The Anchor build has seven email modules — one per meaningful lead context — each with a different subject line pattern, different body structure, and different routing.


The situation

Before Era 3, the outbound email system was: WordPress sends a generic “New lead” notification to a single inbox. The email contained name, phone, email, and message. No context about what form was submitted. No formatting that helped the recipient act quickly. No record in the CRM that the email had been sent.

The sales team was receiving a flat feed of submissions and manually triaging them. A credit application — high intent, time-sensitive — arrived in the same format as a general contact with a question about hours.


What I did

The module pattern

Each email module is a PHP function with a consistent signature: it receives a lead ID, builds a context array from the lead record, renders a template, and sends via wp_mail() through Brevo SMTP.

Email module — unit inquiry (canonical pattern)
function [client]_email_module_unit_inquiry( int $lead_id ): bool { $lead = [client]_get_lead_by_id( $lead_id ); if ( ! $lead ) return false; // Check suppression list before sending if ( [client]_email_is_suppressed( $lead->email ) ) return false; $unit = $lead->source_unit_id ? get_post( $lead->source_unit_id ) : null; $context = [ 'lead' => $lead, 'unit_title' => $unit ? get_the_title( $unit ) : 'Unit inquiry', 'unit_url' => $unit ? get_permalink( $unit ) : '', 'unit_price' => $unit ? get_post_meta( $unit->ID, '[client]_price', true ) : '', 'reply_url' => admin_url( 'admin.php?page=[client]-leads&lead=' . $lead_id ), ]; $subject = '[Inquiry] ' . $context['unit_title'] . ' — ' . $lead->first_name . ' ' . $lead->last_name; $body = [client]_render_email_template( 'unit-inquiry', $context ); $sent = wp_mail( SALES_EMAIL, $subject, $body, [ 'Content-Type: text/html; charset=UTF-8' ] ); if ( $sent ) { [client]_add_conversation( $lead_id, 'system', 'Internal notification sent: unit inquiry', $subject ); } return $sent; }

The 7 modules

Each module fires from the routing array in Lesson 3.1. Each has a distinct subject prefix, body structure, and recipient:

ModuleSubject prefixRecipientPriority
unit_inquiry[Inquiry]Sales teamStandard
financing[Financing]Finance managerStandard
credit_application[HOT — Credit App]Finance managerHigh
trade_in[Trade-In]Sales teamStandard
consignment[Consignment]Sales managerStandard
general[General]Front deskLow
post_sale[Thank You]Customer (outbound)Confirmation

Template rendering

Templates are PHP files in the plugin’s templates/email/ directory. They receive the $context array and return an HTML string. The template pattern uses output buffering:

Template renderer — output buffering pattern
function [client]_render_email_template( string $template_name, array $context ): string { $template_path = plugin_dir_path( __FILE__ ) . 'templates/email/' . $template_name . '.php'; if ( ! file_exists( $template_path ) ) { return ''; } extract( $context, EXTR_SKIP ); ob_start(); include $template_path; return ob_get_clean(); }

The EXTR_SKIP flag on extract() prevents the context array from overwriting existing variables in the template scope. Template variables are then available directly: $lead->first_name, $unit_title, $reply_url.

The credit application module

The credit application is the hottest lead the system generates. The module reflects that — it fires two simultaneous sends instead of one:

Credit app module — double-notify pattern
function [client]_email_module_credit_app( int $lead_id ): bool { $lead = [client]_get_lead_by_id( $lead_id ); $context = [ 'lead' => $lead, 'reply_url' => admin_url( 'admin.php?page=[client]-leads&lead=' . $lead_id ), 'app_fields' => [client]_get_credit_app_fields( $lead_id ), ]; $subject = '🔴 HOT — Credit Application: ' . $lead->first_name . ' ' . $lead->last_name; // Send to finance manager AND sales manager simultaneously wp_mail( FINANCE_MANAGER_EMAIL, $subject, [client]_render_email_template( 'credit-app', $context ), [ 'Content-Type: text/html; charset=UTF-8' ] ); wp_mail( SALES_MANAGER_EMAIL, $subject, [client]_render_email_template( 'credit-app', $context ), [ 'Content-Type: text/html; charset=UTF-8' ] ); [client]_add_conversation( $lead_id, 'system', 'Hot notification sent to finance + sales manager', $subject ); return true; }

Why it matters

Seven modules means the right person sees the right information immediately. The finance manager’s inbox contains only financing and credit application leads — not general inquiries mixed in. The subject line tells them the priority before they open it.

The reply_url in every internal notification links directly to the lead record in the admin. One click from the email to the full lead context — no searching, no navigating. Response time drops because the friction of finding the lead is eliminated.

Logging every send to the CRM via [client]_add_conversation() means the lead timeline is complete. When a salesperson looks at a lead, they can see that an internal notification was sent, when, and to whom. No guessing whether the finance manager was notified.


The Anchor build

All seven modules have been running since Era 3. The credit application module fires two simultaneous sends — finance manager and sales manager both receive it within seconds of submission. In the first month, average time-to-first-contact on credit applications dropped from 4.2 hours to 18 minutes.

The reply_url admin link in notifications turned out to be the single most-used feature. Staff went from “I need to find this lead in WordPress” to “click the link in the email, I’m there.” The workflow change was behavioral, not technical.


Do this, not that

  • One module per intent, not one generic notification. The subject line and routing should change based on what the person actually submitted. “New lead” subject lines train recipients to ignore notifications.
  • Check the suppression list before every send. A bounced address that you keep sending to damages deliverability. The check is one function call — always make it.
  • Log every send to the CRM. If you sent an email and didn’t log it, the lead timeline is incomplete. Every outbound email should produce a conversation record.
  • Use extract( $context, EXTR_SKIP ) in templates. Without EXTR_SKIP, a context key named $template_path or $context would silently overwrite your local variables and cause hard-to-debug rendering bugs.
  • Send high-priority modules to multiple recipients simultaneously. A credit application that only one person sees is a bottleneck. Send to finance manager AND sales manager — the duplication is worth it.
When you’re ready to build

The lessons are yours. When you want it built, we’re here.

Every lesson stays free — no account, no paywall, no email gate, ever. But if you’d rather have this system standing on your business than wire all 48 lessons yourself, leave your email. We’ll send you a direct line to a build — and you’ll be first to hear when we add new tools to the curriculum.

None of this gates a single lesson. The curriculum was free before you got here and it stays that way.

We’ll use your email to send you a fast-track to a GAP build and occasional notes on how GAP builds digital sales departments. Lessons stay 100% free — no email required to read any of them. We never share or sell your information. Unsubscribe any time. Privacy policy at gapindustriesllc.com/privacy.html.

Done learning how it’s built? We’ll build it.

You came here to understand the system, and now you do. If you’d rather have it standing on your business than spend the next three months wiring it yourself, GAP Concierge is the same architecture from these lessons — a white-label AI agent that knows your catalog and captures your leads — set up for you, from $97/mo.

See GAP Concierge →