GAP School Module 04 — Email Engine Lesson 4.4

Automated email sequences raise an obvious problem: what happens when someone replies? If the reply goes to a no-reply address and disappears, or arrives in a generic inbox that nobody monitors closely, the system that was supposed to improve response time has created a new gap. The inbound listener closes that gap by routing replies back into the CRM automatically.


The situation

After the nurture sequences went live, reply rate to sequence emails was approximately 12%. That’s significant volume — roughly 1 in 8 leads who received an automated email responded. Before the inbound listener was built, those replies went to the sales team’s general inbox with no connection to the lead record in the CRM. The salesperson responding had no context about which sequence the lead was in, what emails had been sent, or what the lead’s current status was.


What I did

Brevo inbound parsing

Brevo’s inbound email parsing routes incoming emails to a webhook endpoint. When a lead replies to a sequence email, Brevo receives the reply, parses the email, and POSTs the structured data to a WordPress REST endpoint. Configure the inbound email address in Brevo: inbound@yourdomain.com or a dedicated catching address — all emails sent there are forwarded to the webhook.

The inbound webhook handler

Inbound webhook — match reply to lead record, update status, notify sales
add_action( 'rest_api_init', function() { register_rest_route( '[client]/v1', '/email-inbound', [ 'methods' => 'POST', 'callback' => '[client]_handle_inbound_email', 'permission_callback' => '__return_true', ] ); } ); function [client]_handle_inbound_email( WP_REST_Request $request ): WP_REST_Response { $data = $request->get_json_params(); $from_email = sanitize_email( $data['From'] ?? '' ); $subject = sanitize_text_field( $data['Subject'] ?? '' ); $body_text = wp_kses_post( $data['TextBody'] ?? $data['HtmlBody'] ?? '' ); if ( ! $from_email ) { return new WP_REST_Response( [ 'ok' => false, 'error' => 'No sender' ], 400 ); } // Find the lead by sender email $lead = [client]_get_lead_by_email( $from_email ); if ( $lead ) { // Log the inbound reply to the conversation table [client]_add_conversation( $lead->id, 'email_in', $body_text, $subject, $from_email ); // Update lead status if still 'new' — they replied, so they're 'contacted' if ( $lead->status === 'new' ) { [client]_update_lead_status( $lead->id, 'contacted', 'inbound_email' ); } // Notify the sales team [client]_notify_sales_of_reply( $lead->id, $subject, $body_text ); } else { // New contact — create a lead from the inbound email $lead_id = [client]_create_lead( [ 'email' => $from_email, 'source_form' => 'email_reply', 'notes' => $body_text, ] ); [client]_add_conversation( $lead_id, 'email_in', $body_text, $subject, $from_email ); } return new WP_REST_Response( [ 'ok' => true ], 200 ); }

The reply notification

When an inbound reply is received, the sales team needs to know immediately — with context about the lead’s full history, not just the reply text:

Reply notification — full timeline context in the notification email
function [client]_notify_sales_of_reply( int $lead_id, string $subject, string $body ): void { $timeline = [client]_get_lead_timeline( $lead_id ); $lead = $timeline['lead']; $history = $timeline['conversations']; $context = [ 'lead' => $lead, 'reply_subject' => $subject, 'reply_body' => $body, 'reply_url' => admin_url( 'admin.php?page=[client]-leads&lead=' . $lead_id ), 'prior_emails' => array_filter( $history, fn( $c ) => $c->type === 'email_out' ), ]; $notification_body = [client]_render_email_template( 'reply-notification', $context ); wp_mail( SALES_EMAIL, '[Reply] ' . $lead->first_name . ' ' . $lead->last_name . ' responded', $notification_body, [ 'Content-Type: text/html; charset=UTF-8' ] ); }

The notification email includes the full reply text, the lead’s status, the emails sent previously, and a direct link to the lead record. The salesperson has complete context in a single email.

Threading by In-Reply-To header

For proper email threading, sequence emails should include a Message-ID header so the inbound parser can match replies to the specific step they’re responding to:

Message-ID header — enables In-Reply-To matching on inbound
$message_id = '<lead-' . $lead_id . '-step-' . $step_index . '@yourdomain.com>'; add_filter( 'wp_mail', function( $args ) use ( $message_id ) { $args['headers'][] = 'Message-ID: ' . $message_id; return $args; } );

Why it matters

A nurture sequence that has no reply handling is a black hole — replies disappear, leads get frustrated, and the sequence that was supposed to build trust instead damages it. The inbound listener turns a one-way broadcast into a two-way channel.

The automatic status update (new → contacted when a reply arrives) keeps the CRM accurate without manual intervention. The lead’s status reflects reality, and the next sequence step that checks status_not: contacted correctly skips on the lead’s behalf.


The Anchor build

The inbound listener was added in the second week after sequences went live, after the first unmatched replies came in. In the first month, 47 replies to sequence emails were automatically logged to their respective lead records. Of those, 12 resulted in sales — all of them caught because the inbound listener surfaced them with context, rather than burying them in a generic inbox.

The “new lead from inbound” path — where someone emails the inbound address directly without having submitted a form — created 8 new leads in the first two months. Cold contacts who sent an email directly got a CRM record and entered the nurture sequence automatically.


Do this, not that

  • Return HTTP 200 to Brevo immediately, even if processing fails. A non-200 response triggers retry logic, which can result in duplicate inbound records. Log errors internally; return 200 to the webhook provider.
  • Match inbound by email address first, then by In-Reply-To. Email matching handles the 90% case. Message-ID threading handles the edge case where the same email address submitted two separate lead records.
  • Update lead status on reply. A lead that replies to your email has demonstrated intent. Update the CRM to reflect that — it prevents the next sequence step from sending an inappropriate “just following up” message.
  • Include the full reply context in the sales notification. A bare “John replied to your email” notification is only marginally better than the generic inbox it replaced. Full context is the value.
  • Create a new lead for unmatched senders. Someone who emails your inbound address without having submitted a form is still a prospect. Create a lead record, log the conversation, and treat them as an organic contact.
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 →