Filtering Sales Pitches Out of Slack Notifications from Contact Form 7
When every Contact Form 7 submission pings Slack, sales outreach drowns out real customer inquiries. Three implementation paths to filter sales pitches before they reach Slack: keyword blocklists, custom wpcf7 hooks with an LLM, and the YomuForm plugin. Code, trade-offs, and when to choose which.
You wired up Contact Form 7 to ping Slack on every submission. For the first few weeks it felt great — real-time visibility on customer inquiries. Then the cold outreach hit.
Now the channel is so noisy that real customer inquiries get buried, and the on-call sales rep has muted the channel. You want to keep Slack for real inquiries and route sales pitches somewhere quieter.
This is the article that walks through three implementation paths to do exactly that, with code, trade-offs, and when each makes sense.
Why Slack's own filters can't solve this
The obvious-first instinct: "Can't I just filter on the Slack side?" Slack notification rules work on channel, DM, and mention scope — not on per-message content classification. Worse, even if you suppress the notification, the message still lands in the channel's history, so when someone scrolls back later they're still buried in outreach noise.
The right place to filter is before the message reaches Slack at all, on the WordPress side, at the moment Contact Form 7 is about to dispatch.
Level 1: Keyword blocklist in wpcf7_before_send_mail
The simplest possible implementation. Drop this into functions.php or a tiny custom plugin:
add_action( 'wpcf7_before_send_mail', function ( $contact_form, &$abort, $submission ) {
if ( ! $submission ) return;
$body = (string) ( $submission->get_posted_data()['your-message'] ?? '' );
// A few likely-sales-pitch phrases
$sales_phrases = [ 'SEO services', 'I noticed your site', 'partnership opportunity', 'staffing solution' ];
foreach ( $sales_phrases as $phrase ) {
if ( stripos( $body, $phrase ) !== false ) {
// Flag this submission as sales — your Slack-notification plugin
// (e.g. a custom wpcf7-to-slack integration) should check this
// flag and skip the Slack post for matching submissions.
update_post_meta( $contact_form->id(), '_yomuform_last_verdict', 'sales' );
return;
}
}
}, 10, 3 );
Pros:
- 15 minutes of work, zero dependencies
- Easy to reason about
Cons:
- False positives: "I'd like to engage your SEO services, please send a quote" — a real inquiry — gets flagged as sales because it contains "SEO services"
- Maintenance treadmill: senders A/B test their boilerplate. The phrases you block today get rephrased tomorrow.
- No visibility: when a submission is silently re-routed, neither the sender nor the recipient knows why
This works for very small sites with very predictable sales patterns. Most teams outgrow it within a month.
Level 2: Custom LLM classification in wpcf7_before_send_mail
Step up: replace the keyword list with an actual LLM that reads the message and classifies it.
add_action( 'wpcf7_before_send_mail', function ( $contact_form, &$abort, $submission ) {
if ( ! $submission ) return;
$body = (string) ( $submission->get_posted_data()['your-message'] ?? '' );
$response = wp_remote_post( 'https://api.anthropic.com/v1/messages', [
'headers' => [
'x-api-key' => get_option( 'my_anthropic_key' ),
'anthropic-version' => '2023-06-01',
'Content-Type' => 'application/json',
],
'body' => wp_json_encode( [
'model' => 'claude-haiku-4-5',
'max_tokens' => 200,
'messages' => [[
'role' => 'user',
'content' => "Classify the following contact form submission as 'sales' (cold outreach trying to sell to the site owner) or 'inquiry' (real interest in the site's product/service). Respond with JSON: {\"category\": \"sales\" | \"inquiry\", \"confidence\": 0-1}\n\nSubmission:\n" . $body,
]],
] ),
'timeout' => 8,
] );
if ( is_wp_error( $response ) ) return; // fail-open if API down
$verdict = json_decode( $data['content'][0]['text'] ?? '{}', true );
if ( ( $verdict['category'] ?? '' ) === 'sales' && ( $verdict['confidence'] ?? 0 ) >= 0.7 ) {
update_post_meta( $contact_form->id(), '_yomuform_last_verdict', 'sales' );
}
}, 10, 3 );
Accuracy is dramatically better than the keyword approach. Implementation gotchas:
- API key storage: use
wp_optionswithautoload=falseso the key isn't loaded into memory on every request - Timeout: keep at 5-8 seconds maximum. CF7 will fail the form submission if the hook takes too long
- Fail-open, not fail-closed: when the API is down, let the submission through. Losing real inquiries because the AI provider had a 5-minute outage is worse than letting one extra sales pitch into Slack
Things you'll find yourself wanting after a few weeks of running this:
- A log of past classifications (to verify it's working / debug bad calls)
- A prompt-tuning UI (to fix the few cases that get classified wrong)
- A way to support more than just Slack — Discord, Webhooks, multiple recipient routing
- Multiple AI providers (in case one has an outage)
That's where it stops being a small hook and starts being its own plugin.
Level 3: Install YomuForm
This level is the plugin we built for exactly this. Three minutes of configuration:
- Install YomuForm from WordPress.org (Free tier)
- Settings → YomuForm → paste your Anthropic, OpenAI, or Gemini API key
- Notifications:
- Slack notification, webhook URL
https://hooks.slack.com/..., trigger condition "inquiries only" - Email notification, address
archive@yourcompany.com, trigger condition "all classifications" (for record-keeping)
- Slack notification, webhook URL
That's it. Slack only pings on real inquiries; sales pitches are recorded in the archive email but stay out of Slack.
Why this is different from generic "CF7 to Slack" plugins
Plugins like CF7 to Slack mechanically translate every submission into a Slack post. They have no classification step, so they're a thin pipe from form to channel. YomuForm has the classification engine built in — that's the part that makes per-category routing possible at all.
Comparison table
| Approach | Accuracy | Effort | Maintenance |
|---|---|---|---|
| Level 1: keyword list | Low (many false positives) | 15 min | High (phrase rotation, never-ending) |
| Level 2: custom LLM hook | High | 1-3 days + ongoing | Medium (you own the pipeline) |
| Level 3: YomuForm | High | 5 minutes | Low (plugin-side) |
The non-obvious benefit: cleaner GA4 conversion data
Once you have classification working, a side-effect emerges. If you've been using Contact Form 7 submissions as a GA4 conversion event, your conversion rate has been inflated by cold outreach the whole time. With a classification signal available, you can rewire the conversion event to fire only on genuine_inquiry — and suddenly your GA4 numbers tell the truth.
This is covered in detail in Your GA4 Conversion Rate Just Dropped — And It Might Be the Best News of the Quarter. Worth reading after you finish setting up the Slack filter.
TL;DR
- Slack's own notification filters can't classify form content. Filter on the WordPress side.
- Keyword blocklists are tempting but produce false positives and require constant maintenance.
- A custom LLM-based hook gives high accuracy but you end up rebuilding plugin infrastructure.
- YomuForm packages level 3 in a 5-minute install — Free tier covers the basic Slack-on-inquiry-only setup.
Start with Free, watch the classification log for a week, and you'll have a much better sense of what's hitting your form than you do today.