Overview
This how to article shows you how to automatically learn, store, and block email addresses that your spam defenses catch. The snippet we are sharing stores suspicious emails in a WordPress option, rejects future submissions that contain those values (exact or partial matches like @example.com), and removes them again if you later mark an entry “Not Spam”.
What We Will Build
- When a submission fails honeypot and the form aborts without saving, its email is remembered.
- When an entry is saved as spam, its email is remembered.
- When a spam entry is restored to Active, its email is un-remembered.
- The Email field’s validation uses gform_email_field_rejectable_values so future submissions containing those values are blocked.
Code Snippet
define( 'GF_EFRJ_FILTER_OPTION', 'gform_email_field_rejectable_values' );
/**
* Passes the values from the option to the gform_email_field_rejectable_values filter, so the email field will use them during field validation.
*
* @param array $rejectable_values An array of values or partial values to be rejected. Defaults to an empty array on the form preview page.
*
* @return array
*/
add_filter( 'gform_email_field_rejectable_values', function ( $rejectable_values ) {
$emails = get_option( GF_EFRJ_FILTER_OPTION );
if ( empty( $emails ) ) {
return $rejectable_values;
}
// The number of times an email address is added to the option before it is rejected.
$min_count = 2;
$emails = array_keys( array_filter( $emails, function ( $count ) use ( $min_count ) {
return $count >= $min_count;
} ) );
return array_unique( array_merge( $rejectable_values, $emails ) );
} );
/**
* Updates the option when the entry is marked as spam during form submission.
*
* @param array $entry The entry that was saved.
* @param array $form The form that was submitted.
*
* @return void
*/
add_action( 'gform_entry_created', function ( $entry, $form ) {
if ( rgar( $entry, 'status' ) !== 'spam' ) {
return;
}
update_gform_email_field_rejectable_values_option( $entry, $form );
}, 10, 2 );
/**
* Updates the option when the submission failed honeypot validation and the form is configured to abort without saving the entry.
*
* @param array $form The form that was submitted.
*
* @return void
*/
add_action( 'gform_post_process', function ( $form ) {
$form_id = absint( rgar( $form, 'id' ) );
if ( empty( $form_id ) ) {
return;
}
if ( ! rgars( GFFormDisplay::$submission, $form_id . '/abort_with_confirmation' ) ) {
return;
}
$found = false;
$honeypot_valid = GFCache::get( 'honeypot_' . $form_id, $found, false );
if ( $found && $honeypot_valid ) {
return;
}
$entry = GFFormsModel::get_current_lead( $form );
update_gform_email_field_rejectable_values_option( $entry, $form );
} );
/**
* Updates the option when the entry is marked as spam or not spam after submission (e.g. from the entries list or detail pages).
*
* @param int $entry_id The ID of the entry the status changed for.
* @param string $new_value The value value of the status property.
* @param string $previous_value The previous value of the status property.
*
* @return void
*/
add_action( 'gform_update_status', function ( $entry_id, $new_value, $previous_value ) {
$mark_as_spam = ( $new_value === 'spam' && $previous_value === 'active' );
$mark_as_ham = ( $new_value === 'active' && $previous_value === 'spam' );
if ( ! $mark_as_spam && ! $mark_as_ham ) {
return;
}
$entry = GFAPI::get_entry( $entry_id );
if ( is_wp_error( $entry ) ) {
return;
}
$form = GFAPI::get_form( rgar( $entry, 'form_id' ) );
if ( ! $form ) {
return;
}
update_gform_email_field_rejectable_values_option( $entry, $form, $mark_as_spam );
}, 1, 3 );
/**
* Helper to update the option.
*
* @param array $entry The entry to process.
* @param array $form The form that created the entry.
* @param bool $add Indicates if the email addresses are being added to or removed from the option.
*
* @return void
*/
function update_gform_email_field_rejectable_values_option( $entry, $form, $add = true ) {
$fields = GFAPI::get_fields_by_type( $form, array( 'email' ), true );
if ( empty( $fields ) ) {
return;
}
$emails = get_option( GF_EFRJ_FILTER_OPTION, array() );
if ( ! is_array( $emails ) ) {
if ( ! $add ) {
delete_option( GF_EFRJ_FILTER_OPTION );
return;
}
$emails = array();
}
foreach ( $fields as $field ) {
$email = $field->get_value_export( $entry );
if ( empty( $email ) ) {
continue;
}
if ( $add ) {
$emails[ $email ] = ( $emails[ $email ] ?? 0 ) + 1;
} elseif ( isset( $emails[ $email ] ) ) {
$emails[ $email ] -= 1;
if ( $emails[ $email ] <= 0 ) {
unset( $emails[ $email ] );
}
}
}
update_option( GF_EFRJ_FILTER_OPTION, $emails );
}
Placement
This code can be used in the functions.php file of the active theme, a custom functions plugin, a custom add-on, or with a code snippets plugin.
See also the PHP section in this article: Where Do I Put This Code?
How it works
Learning step
gform_post_process captures the email when the Honeypot fails and the form aborts without saving an entry.
gform_entry_created captures the email when an entry is saved with status spam.
Unlearning Step
gform_update_status removes or decrements the email when you restore a spam entry to Active.
Blocking step
gform_email_field_rejectable_values feeds the stored values back into the Email field’s validation. After a value appears at least two times ($min_count = 2), submissions containing that value will be rejected.
The filter supports exact values (e.g., [email protected]) or partial values (e.g., @tempmail.com) to catch entire domains.
Customize
Change the learning threshold
Increase or decrease $min_count in the filter callback to require more or fewer detections before blocking.
Block entire domains
If you prefer domain-level blocking, normalize and store just the domain (e.g., @domain.com) in update_gform_email_field_rejectable_values_option() instead of the full address.
Scope to specific forms
Wrap the logic with a form ID check if you only want learning/blocking on certain forms.
Notes and limitations
- The learning store is global, values apply across all forms unless you scope it in code.
- In the form Preview context, the underlying filter defaults to an empty array; depending on your implementation, embedded forms may also include defaults like @domain.com or @example.com.
- Storing email addresses is personally identifiable information. Ensure this aligns with your privacy policy and retention practices.
- Large WordPress options data can impact autoload or memory usage; if your site receives heavy spam, consider periodically pruning older entries or switching to a custom table.