Automatically Block Unwanted Emails Using Code

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.

Resources