# Daily Email Summary of Failed Logins

> Counts failed login attempts during the day (per username and IP address) and emails the administrator a short summary each morning, so you notice brute-force attacks without a flood of alerts.

- Type: PHP
- Runs: Run everywhere
- Category: [Security](https://wpide.com/library/category/security/)
- Tags: login, alerts, brute-force
- Author: WPIDE Team
- Installs: 0
- Updated: 2026-10-28
- Page: https://wpide.com/library/sec-failed-login-digest/

## Code

```php
// Send the summary only when at least this many attempts failed.
$wpide_digest_minimum = 10;

add_action( 'wp_login_failed', function ( $username ) {
    $day  = wp_date( 'Y-m-d' );
    $data = get_option( 'wpide_failed_logins' );
    $data = is_array( $data ) && isset( $data['day'] ) && $data['day'] === $day ? $data : array( 'day' => $day, 'total' => 0, 'users' => array(), 'ips' => array() );
    $ip   = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : 'unknown';
    $user = substr( sanitize_user( (string) $username ), 0, 60 );

    $data['total']++;
    if ( count( $data['users'] ) < 200 || isset( $data['users'][ $user ] ) ) {
        $data['users'][ $user ] = ( isset( $data['users'][ $user ] ) ? $data['users'][ $user ] : 0 ) + 1;
    }
    if ( count( $data['ips'] ) < 200 || isset( $data['ips'][ $ip ] ) ) {
        $data['ips'][ $ip ] = ( isset( $data['ips'][ $ip ] ) ? $data['ips'][ $ip ] : 0 ) + 1;
    }

    update_option( 'wpide_failed_logins', $data, false );
} );

add_action( 'init', function () {
    if ( ! wp_next_scheduled( 'wpide_failed_login_digest' ) ) {
        wp_schedule_event( strtotime( 'tomorrow 07:00' ) - (int) ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ), 'daily', 'wpide_failed_login_digest' );
    }
} );

add_action( 'wpide_failed_login_digest', function () use ( $wpide_digest_minimum ) {
    $data = get_option( 'wpide_failed_logins' );

    if ( ! is_array( $data ) || empty( $data['day'] ) || $data['day'] === wp_date( 'Y-m-d' ) ) {
        return;
    }

    delete_option( 'wpide_failed_logins' );

    if ( $data['total'] < $wpide_digest_minimum ) {
        return;
    }

    arsort( $data['users'] );
    arsort( $data['ips'] );
    $lines = array( sprintf( '%d failed login attempts on %s.', $data['total'], $data['day'] ), '', 'Most tried usernames:' );
    foreach ( array_slice( $data['users'], 0, 10, true ) as $name => $count ) {
        $lines[] = sprintf( '  %s: %d%s', $name, $count, username_exists( $name ) ? ' (existing account)' : '' );
    }
    $lines[] = '';
    $lines[] = 'Busiest IP addresses:';
    foreach ( array_slice( $data['ips'], 0, 10, true ) as $ip => $count ) {
        $lines[] = sprintf( '  %s: %d', $ip, $count );
    }

    wp_mail( get_option( 'admin_email' ), sprintf( '[%s] Failed login summary', wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ) ), implode( "\n", $lines ) );
} );
```

## Notes

No email is sent on days with fewer failed attempts than $wpide_digest_minimum.

## How to use it

Install the free WPIDE plugin (https://wordpress.org/plugins/wpide/), find "Daily Email Summary of Failed Logins" in its snippet library and add it in one click; or create a new PHP snippet in WPIDE (or any code snippets plugin) and paste the code above. More snippets: https://wpide.com/library/
