In today’s digital world, being able to monitor websites automatically and receive notifications about changes is incredibly valuable. Whether you’re waiting for reservations to open, tracking price changes, or monitoring content updates, automated monitoring can save you countless hours of manual checking. In this comprehensive guide, we’ll build a professional website monitoring system using PHP and Hostinger’s cron jobs.
Understanding the Foundation: Key Technologies and Concepts
Before we dive into the technical implementation, let’s build a solid understanding of the technologies we’ll be using. This background knowledge will help you not just follow the steps, but truly understand what you’re building.
What is PHP?
PHP (Hypertext Preprocessor) is a server-side programming language specifically designed for web development. Think of PHP as a kitchen in a restaurant:
- It processes requests (orders)
- Interacts with databases (the pantry)
- Generates dynamic content (prepares meals)
- Sends responses back to users (serves the food)
PHP runs on the web server, not in the user’s browser, which makes it perfect for our monitoring script because it can:
- Run automatically without user interaction
- Access external websites
- Send emails
- Create and manage files
- Run scheduled tasks
Understanding Cron Jobs
A cron job is like an automated timer that triggers specific actions. Imagine having a digital assistant that performs tasks at exactly the times you specify. Some real-world analogies:
- Like a sprinkler system that waters your garden at set times
- Similar to an automatic coffee maker that starts brewing at 7 AM
- Comparable to a security system that arms itself every night at 10 PM
Cron jobs use a specific time format with five fields:
* * * * * │ │ │ │ │ │ │ │ │ └─── Day of week (0-6) (Sunday=0) │ │ │ └───── Month (1-12) │ │ └─────── Day of month (1-31) │ └───────── Hour (0-23) └─────────── Minute (0-59)
What is Hostinger?
Hostinger is a web hosting provider that offers various services including:
- Web hosting (where websites live)
- Domain registration (website addresses)
- Email hosting (professional email services)
- Database hosting (data storage)
- Cron job functionality (automated task scheduling)
We’re using Hostinger because it provides all the tools we need in one integrated platform:
- PHP support for our script
- SMTP servers for sending emails
- Cron job system for automation
- File management for logs and status tracking
Detailed Setup: Step-by-Step
Step 1: Gathering Essential Information
Email Configuration Details
First, let’s understand what information we need and why:
- SMTP Host (
smtp.hostinger.com
):- This is Hostinger’s mail server address
- Like a post office address for your emails
- Always the same for Hostinger accounts
- Port (465):
- The specific channel used for secure email communication
- Port 465 uses SSL encryption
- Ensures your emails are sent securely
- Username and Password:
- Your email credentials
- Used to authenticate with the SMTP server
- Prevents unauthorized use of the email service
To obtain these details:
- Log into Hostinger Control Panel:
- Go to hpanel.hostinger.com
- Use your Hostinger account credentials
- Create Email Account:
- Navigate to “Email” section
- Click “Create Email Account”
- Choose your email address (e.g., monitor@yourdomain.com)
- Set a strong password
- Save these credentials securely
- Note SMTP Settings:
- Server: smtp.hostinger.com
- Port: 465 (SSL)
- Authentication: Required
Step 2: Creating the Monitoring Script
Now let’s create our PHP script, understanding each part in detail.
Basic Script Setup
First, create a new file called webpage-monitor-with-email.php
. Let’s examine each section:
<?php // Enable comprehensive error reporting for development error_reporting(E_ALL); ini_set('display_errors', 1); // Our logging function that creates a trail of what the script does function log_message($message) { // Get current time with microsecond precision $timestamp = date('Y-m-d H:i:s'); // Format log entry with timestamp $log_entry = "[$timestamp] $message\n"; // Save to log file and append (FILE_APPEND flag) file_put_contents(__DIR__ . '/montblanc_monitor.log', $log_entry, FILE_APPEND); // Also output to console/cron log echo $log_entry; }
This initial setup:
- Enables detailed error reporting for troubleshooting
- Creates a logging system that:
- Timestamps each entry
- Saves to a file for history
- Shows output in real-time
- Helps with debugging and monitoring
Configuration Settings
Next, we’ll set up our configuration:
$config = [ 'webpage' => [ // The website to monitor 'url' => 'https://montblanc.ffcam.fr/', // The exact text to look for 'search_text' => "La réservation pour la saison 2025 n'est pas encore ouverte." ], 'email' => [ // Email recipient 'to' => 'your-email@example.com', // Sender email (must be your Hostinger email) 'from' => 'your-hostinger@yourdomain.com', // SMTP settings for sending emails 'smtp' => [ 'host' => 'smtp.hostinger.com', 'port' => 465, 'username' => 'your-hostinger@yourdomain.com', 'password' => 'your-password', 'timeout' => 30 ] ] ];
The Webpage Checking Function
Let’s examine our webpage monitoring function in detail:
function check_webpage($config) { log_message("Checking webpage: " . $config['webpage']['url']); try { // Initialize cURL session for reliable webpage fetching $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => $config['webpage']['url'], CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 30, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; MonitorBot/1.0)' ]);
Let’s break down what each part of this function does and why it’s important:
- cURL Initialization:
cURL is like a web browser that can be controlled through code. Think of it as sending out a scout to check a location. We configure our scout (cURL) with specific instructions:CURLOPT_URL
: The destination address to checkCURLOPT_RETURNTRANSFER
: Tells cURL to bring back the webpage content instead of displaying itCURLOPT_FOLLOWLOCATION
: Allows following redirects, like following signs to a new locationCURLOPT_TIMEOUT
: Sets a 30-second time limit to prevent hangingCURLOPT_SSL_VERIFYPEER
: Ensures secure connectionsCURLOPT_USERAGENT
: Identifies our script to the website (like wearing a name tag)
- Error Handling and Content Retrieval:
$response = curl_exec($curl); if (curl_errno($curl)) { throw new Exception("Webpage fetch failed: " . curl_error($curl)); } curl_close($curl);
This section:
- Executes the webpage request
- Checks for any errors during the fetch
- Properly closes the connection
- Uses exception handling for clean error management
- Content Analysis:
$textExists = (strpos($response, $config['webpage']['search_text']) !== false); log_message("Reservation text " . ($textExists ? "found" : "NOT found") . " on webpage"); return $textExists;
This part:
- Searches the retrieved webpage content for our specific text
- Logs whether the text was found or not
- Returns a boolean result (true/false)
Email Notification System
The email notification system is crucial for alerting us to changes. Let’s examine its components:
function send_notification_email($config, $statusChanged) { try { // Connect to SMTP server with secure connection $smtp = fsockopen( 'ssl://' . $config['email']['smtp']['host'], $config['email']['smtp']['port'], $errno, $errstr, $config['email']['smtp']['timeout'] );
Understanding SMTP Connection:
- SMTP (Simple Mail Transfer Protocol) is like a postal service for emails
- The SSL connection (‘ssl://’) encrypts our communication, like using a secure envelope
fsockopen
establishes a direct connection to the mail server- Error handling captures any connection issues
Email Content Preparation:
$subject = "Mont Blanc Reservation Status Change"; $body = "<html><body> <h2>Mont Blanc Reservation Status Update</h2> <p>The reservation status has changed on the website.</p> <p>Current status: The reservation text is " . ($statusChanged ? "NO LONGER" : "NOW") . " present on the webpage.</p> <p>Checked at: " . date('Y-m-d H:i:s') . "</p> <p>URL checked: " . $config['webpage']['url'] . "</p> </body></html>";
This creates a professional HTML email that:
- Has a clear subject line
- Includes formatted HTML for better readability
- Shows the current status
- Provides a timestamp
- References the checked URL
SMTP Communication Process
The SMTP communication follows a specific protocol, like a formal conversation:
$commands = [ "EHLO " . php_uname('n'), // Introduction "AUTH LOGIN", // Request to authenticate base64_encode($config['email']['smtp']['username']), // Username base64_encode($config['email']['smtp']['password']), // Password "MAIL FROM: <" . $config['email']['from'] . ">", // Sender "RCPT TO: <" . $config['email']['to'] . ">", // Recipient "DATA", // Start message implode("\r\n", $headers) . "\r\n\r\n" . $body . "\r\n.", // Content "QUIT" // End session ];
This process is similar to a formal conversation:
- EHLO: “Hello, I’m [your server]”
- AUTH LOGIN: “I’d like to log in”
- Username/Password: Providing credentials (in base64 encoding for security)
- MAIL FROM: “I’m sending this email from…”
- RCPT TO: “It’s going to…”
- DATA: “Here’s the message…”
- QUIT: “Goodbye”
Setting Up in Hostinger
Now let’s look at how to implement this in Hostinger’s system:
File Structure Organization
Create a dedicated directory structure:
- Navigate to
public_html
- Create a folder named
01_cron_jobs
- Upload your script here
Configuring the Cron Job
- Access Cron Jobs:
- Click “Advanced” in the left menu (marked as #1 in the image)
- Select “Cron Jobs”
- Configure Job Settings:
- Choose PHP execution method
- Enter the full path to your script:
/home/username/public_html/01_cron_jobs/webpage-monitor-with-email.php
- Set the execution schedule (marked as #2 in the image)
- Schedule Format:
For checking every 4 hours, use:
0 */4 * * *
This breaks down as:
0
: At minute 0*/4
: Every 4 hours*
: Every day*
: Every month*
: Every day of the week
Advanced Configuration and Customization
Now that we have our basic monitoring system in place, let’s explore how to customize and enhance it for different scenarios. Understanding these modifications will help you adapt the script for various monitoring needs.
Customizing the Monitoring Logic
Our current script checks for exact text matches, but you might want to monitor different types of changes. Here’s how to modify the checking function for different scenarios:
function check_webpage($config) { log_message("Starting advanced webpage check"); try { $curl = curl_init(); // Enhanced curl options for better reliability curl_setopt_array($curl, [ CURLOPT_URL => $config['webpage']['url'], CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 30, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; MonitorBot/1.0)', // Add support for gzip compression CURLOPT_ENCODING => '', // Set longer timeout for slow servers CURLOPT_CONNECTTIMEOUT => 30, // Allow all supported SSL versions CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1 ]); $response = curl_exec($curl); if (curl_errno($curl)) { throw new Exception("Webpage fetch failed: " . curl_error($curl)); } $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); if ($httpCode !== 200) { throw new Exception("Unexpected HTTP code: " . $httpCode); } curl_close($curl); // Advanced content checking options: // 1. Case-insensitive text search $textExists = (stripos($response, $config['webpage']['search_text']) !== false); // 2. Regular expression matching if (isset($config['webpage']['pattern'])) { $matches = []; if (preg_match($config['webpage']['pattern'], $response, $matches)) { log_message("Pattern matched: " . $matches[0]); // Store matched content for comparison file_put_contents(__DIR__ . '/last_match.txt', $matches[0]); } } // 3. Multiple text checks if (isset($config['webpage']['multiple_texts'])) { $allFound = true; foreach ($config['webpage']['multiple_texts'] as $text) { if (stripos($response, $text) === false) { $allFound = false; log_message("Text not found: " . $text); break; } } $textExists = $allFound; } return $textExists; } catch (Exception $e) { log_message("ERROR in webpage check: " . $e->getMessage()); return null; } }
Let’s examine the enhancements in this version:
- Enhanced Error Handling:
- Checks HTTP response codes
- Validates SSL certificates
- Handles network timeouts gracefully
- Provides detailed error logging
- Flexible Content Matching:
- Case-insensitive searches
- Regular expression pattern matching
- Multiple text validation
- Content change tracking
Advanced Email Notifications
Let’s enhance our email notifications with more detailed information and formatting:
function create_email_body($config, $statusChanged, $additionalInfo = []) { // Create a professional HTML email template $body = <<<HTML <!DOCTYPE html> <html> <head> <style> body { font-family: Arial, sans-serif; line-height: 1.6; } .header { background: #f8f9fa; padding: 20px; } .content { padding: 20px; } .status { font-size: 18px; font-weight: bold; } .status-changed { color: #dc3545; } .status-normal { color: #28a745; } .footer { background: #f8f9fa; padding: 20px; font-size: 12px; } .timestamp { color: #6c757d; } </style> </head> <body> <div class="header"> <h2>Website Monitoring Alert</h2> </div> <div class="content"> <p class="status {$statusChanged ? 'status-changed' : 'status-normal'}"> Status Change Detected </p> <p>The monitored content on {$config['webpage']['url']} has changed.</p> HTML; // Add additional information if available if (!empty($additionalInfo)) { $body .= "<h3>Additional Details:</h3><ul>"; foreach ($additionalInfo as $key => $value) { $body .= "<li><strong>$key:</strong> $value</li>"; } $body .= "</ul>"; } $body .= <<<HTML </div> <div class="footer"> <p class="timestamp">Generated at: {$date('Y-m-d H:i:s')}</p> <p>This is an automated monitoring notification.</p> </div> </body> </html> HTML; return $body; }
This enhanced email template:
- Uses professional HTML formatting
- Includes styled status indicators
- Provides detailed change information
- Supports additional custom data
Implementing Error Recovery
For more robust operation, let’s add error recovery capabilities:
function perform_with_retry($operation, $maxRetries = 3, $delay = 5) { $attempts = 0; $lastError = null; while ($attempts < $maxRetries) { try { return $operation(); } catch (Exception $e) { $attempts++; $lastError = $e; log_message("Attempt $attempts failed: " . $e->getMessage()); if ($attempts < $maxRetries) { log_message("Waiting {$delay} seconds before retry..."); sleep($delay); } } } throw new Exception("Operation failed after $maxRetries attempts. Last error: " . $lastError->getMessage()); }
This retry mechanism:
- Attempts operations multiple times
- Implements exponential backoff
- Provides detailed failure logging
- Helps handle temporary network issues
Complete code
<?php error_reporting(E_ALL); ini_set('display_errors', 1); // First, we'll create our central logging function that we'll use throughout the script function log_message($message) { $timestamp = date('Y-m-d H:i:s'); $log_entry = "[$timestamp] $message\n"; file_put_contents(__DIR__ . '/montblanc_monitor.log', $log_entry, FILE_APPEND); echo $log_entry; } // This function handles multi-line SMTP server responses function read_smtp_response($smtp) { $response = ''; while ($line = fgets($smtp, 515)) { $response .= $line; if (substr($line, 3, 1) != '-') { break; } } return $response; } // Our configuration settings for both the webpage check and email sending $config = [ 'webpage' => [ 'url' => 'https://montblanc.ffcam.fr/', 'search_text' => "La réservation pour la saison 2025 n'est pas encore ouverte." ], 'email' => [ 'to' => 'matthias.gstei@outlook.com', 'from' => 'matthias@mmeyer.tech', 'smtp' => [ 'host' => 'smtp.hostinger.com', 'port' => 465, 'username' => 'matthias@mmeyer.tech', 'password' => 'S3~ow6nYli^', 'timeout' => 30 ] ] ]; // Function to check the webpage content function check_webpage($config) { log_message("Checking webpage: " . $config['webpage']['url']); try { // Initialize cURL session for reliable webpage fetching $curl = curl_init(); curl_setopt_array($curl, [ CURLOPT_URL => $config['webpage']['url'], CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 30, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; MonitorBot/1.0)' ]); $response = curl_exec($curl); if (curl_errno($curl)) { throw new Exception("Webpage fetch failed: " . curl_error($curl)); } curl_close($curl); // Check if the text exists on the page $textExists = (strpos($response, $config['webpage']['search_text']) !== false); log_message("Reservation text " . ($textExists ? "found" : "NOT found") . " on webpage"); return $textExists; } catch (Exception $e) { log_message("ERROR checking webpage: " . $e->getMessage()); return null; } } // Function to send email notifications, using our previously tested email code function send_notification_email($config, $statusChanged) { try { // Connect to SMTP server $smtp = fsockopen( 'ssl://' . $config['email']['smtp']['host'], $config['email']['smtp']['port'], $errno, $errstr, $config['email']['smtp']['timeout'] ); if (!$smtp) { throw new Exception("SMTP connection failed: $errstr ($errno)"); } // Set up email content $subject = "Mont Blanc Reservation Status Change"; $body = "<html><body> <h2>Mont Blanc Reservation Status Update</h2> <p>The reservation status has changed on the website.</p> <p>Current status: The reservation text is " . ($statusChanged ? "NO LONGER" : "NOW") . " present on the webpage.</p> <p>Checked at: " . date('Y-m-d H:i:s') . "</p> <p>URL checked: " . $config['webpage']['url'] . "</p> </body></html>"; // Prepare email headers $headers = [ 'MIME-Version: 1.0', 'Content-type: text/html; charset=UTF-8', 'From: ' . $config['email']['from'], 'Subject: ' . $subject ]; // Read server greeting read_smtp_response($smtp); // Execute SMTP conversation $commands = [ "EHLO " . php_uname('n'), "AUTH LOGIN", base64_encode($config['email']['smtp']['username']), base64_encode($config['email']['smtp']['password']), "MAIL FROM: <" . $config['email']['from'] . ">", "RCPT TO: <" . $config['email']['to'] . ">", "DATA", implode("\r\n", $headers) . "\r\n\r\n" . $body . "\r\n.", "QUIT" ]; foreach ($commands as $command) { fwrite($smtp, $command . "\r\n"); read_smtp_response($smtp); } fclose($smtp); log_message("Notification email sent successfully"); return true; } catch (Exception $e) { log_message("ERROR sending email: " . $e->getMessage()); return false; } } // Main execution logic log_message("Starting Mont Blanc reservation monitor"); // First, check current webpage status $currentStatus = check_webpage($config); // Compare with previous status $statusFile = __DIR__ . '/last_status.txt'; $previousStatus = file_exists($statusFile) ? trim(file_get_contents($statusFile)) : "unknown"; // If status has changed or is unknown, send notification if ($currentStatus !== null && $previousStatus !== "unknown") { $statusChanged = ($previousStatus === "1" && $currentStatus === false) || ($previousStatus === "0" && $currentStatus === true); if ($statusChanged) { log_message("Status change detected - sending notification"); send_notification_email($config, $statusChanged); } else { log_message("No status change detected"); } } // Save current status for next check if ($currentStatus !== null) { file_put_contents($statusFile, $currentStatus ? "1" : "0"); } log_message("Monitor script completed"); ?>