Contact Form with Email Integration IN HTML CSS AND JAVASCRIPT WITH PHP AND MY SQL

📧 Contact Form with Email Integration

Project Introduction

A professional contact form system that allows website visitors to send messages directly to the site administrator. This component integrates with the existing blog website, providing a seamless way for users to reach out with inquiries, feedback, or support requests. The system stores all submissions in a MySQL database and sends email notifications using PHPMailer with SMTP authentication.


✨ Features

User-Facing Features

  • Responsive Contact Form: Clean, modern design that works on all devices
  • Real-Time Validation: JavaScript validation with user-friendly error messages
  • CSRF Protection: Security tokens prevent cross-site request forgery
  • CAPTCHA Integration: Optional Google reCAPTCHA v2 to prevent spam
  • Success/Error Messages: Clear feedback after form submission
  • Auto-Responder: Optional confirmation email sent to users

Admin Features

  • Database Storage: All submissions saved in MySQL for record-keeping
  • Email Notifications: Instant alerts when new messages arrive
  • Spam Protection: Honeypot fields and CAPTCHA to filter automated submissions
  • Submission History: View all contact requests in admin panel
  • Export Data: Download submissions as CSV for analysis

Technical Features

  • PHPMailer Integration: Reliable SMTP email delivery
  • Environment Variables: Secure credential management
  • Input Sanitization: Protection against XSS and injection attacks
  • AJAX Submission: Optional asynchronous form processing
  • Rate Limiting: Prevents spam by limiting submissions per IP

📁 File Structure

blog-website/ │ ├── contact.php # Contact form page ├── contact-success.php # Thank you page after submission │ ├── includes/ │ ├── contact-config.php # Contact form configuration │ ├── contact-process.php # Form processing logic │ ├── contact-functions.php # Helper functions for contact form │ └── contact-ajax-handler.php # AJAX endpoint for form submission │ ├── admin/ │ ├── contact-submissions.php # View all contact submissions │ ├── view-submission.php # View single submission details │ ├── export-submissions.php # Export as CSV │ └── delete-submission.php # Delete submission │ ├── css/ │ └── contact.css # Contact form specific styling │ ├── js/ │ └── contact.js # Contact form JavaScript │ ├── vendor/ # Composer dependencies (PHPMailer) │ ├── .env # Environment variables (credentials) ├── composer.json # Composer dependencies file │ └── database/ └── contact.sql # Database schema for contact form

🗄️ Database Schema (database/contact.sql)

-- Create contact submissions table CREATE TABLE IF NOT EXISTS contact_submissions ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(100) NOT NULL, subject VARCHAR(200) NOT NULL, message TEXT NOT NULL, ip_address VARCHAR(45), user_agent TEXT, status ENUM('unread', 'read', 'replied') DEFAULT 'unread', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_email (email), INDEX idx_status (status), INDEX idx_created (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- Create table for blocked IPs (rate limiting) CREATE TABLE IF NOT EXISTS contact_rate_limit ( id INT AUTO_INCREMENT PRIMARY KEY, ip_address VARCHAR(45) NOT NULL, attempts INT DEFAULT 1, last_attempt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, blocked_until TIMESTAMP NULL, INDEX idx_ip (ip_address), INDEX idx_blocked (blocked_until) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

🔧 File Contents

1. composer.json

{ "name": "blog-website/contact-form", "description": "Contact form with email integration using PHPMailer", "type": "project", "require": { "php": ">=7.4", "phpmailer/phpmailer": "^6.8", "vlucas/phpdotenv": "^5.5" }, "require-dev": {}, "license": "MIT", "authors": [ { "name": "Your Name", "email": "[email protected]" } ] }

2. .env (Environment Variables)

# Database Configuration DB_HOST=localhost DB_NAME=blog_db DB_USER=root DB_PASS= # SMTP Configuration SMTP_HOST=smtp.gmail.com SMTP_PORT=587 [email protected] SMTP_PASSWORD=your-app-password SMTP_ENCRYPTION=tls # Email Settings [email protected] [email protected] CONTACT_FROM_NAME="My Blog Contact" CONTACT_SUBJECT_PREFIX="[Contact Form]" # reCAPTCHA (optional) RECAPTCHA_SITE_KEY=your_site_key RECAPTCHA_SECRET_KEY=your_secret_key RECAPTCHA_ENABLED=false # Security RATE_LIMIT_ENABLED=true RATE_LIMIT_ATTEMPTS=5 RATE_LIMIT_MINUTES=60

3. includes/contact-config.php

<?php /** * Contact Form Configuration * Loads environment variables and sets up configuration */ require_once __DIR__ . '/../vendor/autoload.php'; use Dotenv\Dotenv; // Load environment variables $dotenv = Dotenv::createImmutable(__DIR__ . '/../'); $dotenv->load(); // Database configuration define('DB_HOST', $_ENV['DB_HOST']); define('DB_USER', $_ENV['DB_USER']); define('DB_PASS', $_ENV['DB_PASS']); define('DB_NAME', $_ENV['DB_NAME']); // SMTP Configuration define('SMTP_HOST', $_ENV['SMTP_HOST']); define('SMTP_PORT', $_ENV['SMTP_PORT']); define('SMTP_USERNAME', $_ENV['SMTP_USERNAME']); define('SMTP_PASSWORD', $_ENV['SMTP_PASSWORD']); define('SMTP_ENCRYPTION', $_ENV['SMTP_ENCRYPTION']); // Email Settings define('CONTACT_TO_EMAIL', $_ENV['CONTACT_TO_EMAIL']); define('CONTACT_FROM_EMAIL', $_ENV['CONTACT_FROM_EMAIL']); define('CONTACT_FROM_NAME', $_ENV['CONTACT_FROM_NAME']); define('CONTACT_SUBJECT_PREFIX', $_ENV['CONTACT_SUBJECT_PREFIX']); // reCAPTCHA Settings define('RECAPTCHA_SITE_KEY', $_ENV['RECAPTCHA_SITE_KEY']); define('RECAPTCHA_SECRET_KEY', $_ENV['RECAPTCHA_SECRET_KEY']); define('RECAPTCHA_ENABLED', filter_var($_ENV['RECAPTCHA_ENABLED'], FILTER_VALIDATE_BOOLEAN)); // Rate Limiting define('RATE_LIMIT_ENABLED', filter_var($_ENV['RATE_LIMIT_ENABLED'], FILTER_VALIDATE_BOOLEAN)); define('RATE_LIMIT_ATTEMPTS', $_ENV['RATE_LIMIT_ATTEMPTS']); define('RATE_LIMIT_MINUTES', $_ENV['RATE_LIMIT_MINUTES']); // Database connection function getDBConnection() { $conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME); if ($conn->connect_error) { error_log("Database connection failed: " . $conn->connect_error); return null; } $conn->set_charset("utf8mb4"); return $conn; } // Start session for CSRF token if (session_status() === PHP_SESSION_NONE) { session_start(); }

4. includes/contact-functions.php

<?php /** * Contact Form Helper Functions */ require_once __DIR__ . '/contact-config.php'; /** * Generate CSRF token */ function generateCSRFToken() { if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } return $_SESSION['csrf_token']; } /** * Verify CSRF token */ function verifyCSRFToken($token) { if (empty($_SESSION['csrf_token']) || $token !== $_SESSION['csrf_token']) { return false; } return true; } /** * Validate and sanitize input */ function sanitizeInput($data) { $data = trim($data); $data = stripslashes($data); $data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8'); return $data; } /** * Validate email */ function validateEmail($email) { return filter_var($email, FILTER_VALIDATE_EMAIL); } /** * Check rate limit */ function checkRateLimit($ip) { if (!RATE_LIMIT_ENABLED) { return true; } $conn = getDBConnection(); if (!$conn) { return true; // Allow if DB fails } // Clean old attempts $cleanup = $conn->prepare("DELETE FROM contact_rate_limit WHERE last_attempt < DATE_SUB(NOW(), INTERVAL ? MINUTE)"); $minutes = RATE_LIMIT_MINUTES * 2; // Clean after double the limit time $cleanup->bind_param("i", $minutes); $cleanup->execute(); // Check if IP is blocked $stmt = $conn->prepare("SELECT attempts, blocked_until FROM contact_rate_limit WHERE ip_address = ?"); $stmt->bind_param("s", $ip); $stmt->execute(); $result = $stmt->get_result(); if ($row = $result->fetch_assoc()) { if ($row['blocked_until'] && strtotime($row['blocked_until']) > time()) { return false; // IP is blocked } if ($row['attempts'] >= RATE_LIMIT_ATTEMPTS) { // Block IP $block_until = date('Y-m-d H:i:s', strtotime('+' . RATE_LIMIT_MINUTES . ' minutes')); $update = $conn->prepare("UPDATE contact_rate_limit SET blocked_until = ? WHERE ip_address = ?"); $update->bind_param("ss", $block_until, $ip); $update->execute(); return false; } } return true; } /** * Increment rate limit counter */ function incrementRateLimit($ip) { if (!RATE_LIMIT_ENABLED) { return; } $conn = getDBConnection(); if (!$conn) { return; } $stmt = $conn->prepare("INSERT INTO contact_rate_limit (ip_address, attempts) VALUES (?, 1) ON DUPLICATE KEY UPDATE attempts = attempts + 1, last_attempt = CURRENT_TIMESTAMP"); $stmt->bind_param("s", $ip); $stmt->execute(); } /** * Save submission to database */ function saveSubmission($data) { $conn = getDBConnection(); if (!$conn) { return false; } $stmt = $conn->prepare("INSERT INTO contact_submissions (name, email, subject, message, ip_address, user_agent) VALUES (?, ?, ?, ?, ?, ?)"); $ip = $_SERVER['REMOTE_ADDR'] ?? ''; $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? ''; $stmt->bind_param("ssssss", $data['name'], $data['email'], $data['subject'], $data['message'], $ip, $user_agent ); $result = $stmt->execute(); $stmt->close(); $conn->close(); return $result ? $conn->insert_id : false; } /** * Send email using PHPMailer */ function sendContactEmail($data) { require_once __DIR__ . '/../vendor/autoload.php'; use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\SMTP; use PHPMailer\PHPMailer\Exception; $mail = new PHPMailer(true); try { // Server settings $mail->SMTPDebug = SMTP::DEBUG_OFF; $mail->isSMTP(); $mail->Host = SMTP_HOST; $mail->SMTPAuth = true; $mail->Username = SMTP_USERNAME; $mail->Password = SMTP_PASSWORD; $mail->SMTPSecure = SMTP_ENCRYPTION; $mail->Port = SMTP_PORT; // Recipients $mail->setFrom(CONTACT_FROM_EMAIL, CONTACT_FROM_NAME); $mail->addAddress(CONTACT_TO_EMAIL); $mail->addReplyTo($data['email'], $data['name']); // Content $mail->isHTML(true); $mail->Subject = CONTACT_SUBJECT_PREFIX . ' ' . $data['subject']; // HTML Email Body $mail->Body = " <!DOCTYPE html> <html> <head> <style> body { font-family: Arial, sans-serif; line-height: 1.6; } .container { max-width: 600px; margin: 0 auto; padding: 20px; } .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; text-align: center; } .content { padding: 20px; background: #f9f9f9; } .field { margin-bottom: 15px; } .label { font-weight: bold; color: #555; } .value { margin-top: 5px; padding: 10px; background: white; border-radius: 5px; } .footer { text-align: center; padding: 20px; color: #888; font-size: 12px; } </style> </head> <body> <div class='container'> <div class='header'> <h2>New Contact Form Submission</h2> </div> <div class='content'> <div class='field'> <div class='label'>Name:</div> <div class='value'>" . htmlspecialchars($data['name']) . "</div> </div> <div class='field'> <div class='label'>Email:</div> <div class='value'>" . htmlspecialchars($data['email']) . "</div> </div> <div class='field'> <div class='label'>Subject:</div> <div class='value'>" . htmlspecialchars($data['subject']) . "</div> </div> <div class='field'> <div class='label'>Message:</div> <div class='value'>" . nl2br(htmlspecialchars($data['message'])) . "</div> </div> <div class='field'> <div class='label'>Submitted:</div> <div class='value'>" . date('F j, Y g:i A') . "</div> </div> <div class='field'> <div class='label'>IP Address:</div> <div class='value'>" . ($_SERVER['REMOTE_ADDR'] ?? 'Unknown') . "</div> </div> </div> <div class='footer'> This message was sent from the contact form on your website. </div> </div> </body> </html> "; // Plain text alternative $mail->AltBody = "Name: {$data['name']}\n" . "Email: {$data['email']}\n" . "Subject: {$data['subject']}\n" . "Message:\n{$data['message']}\n\n" . "Submitted: " . date('F j, Y g:i A') . "\n" . "IP: " . ($_SERVER['REMOTE_ADDR'] ?? 'Unknown'); $mail->send(); // Send auto-reply to user (optional) sendAutoReply($data); return true; } catch (Exception $e) { error_log("Email sending failed: " . $mail->ErrorInfo); return false; } } /** * Send auto-reply confirmation to user */ function sendAutoReply($data) { require_once __DIR__ . '/../vendor/autoload.php'; use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception; $mail = new PHPMailer(true); try { $mail->SMTPDebug = SMTP::DEBUG_OFF; $mail->isSMTP(); $mail->Host = SMTP_HOST; $mail->SMTPAuth = true; $mail->Username = SMTP_USERNAME; $mail->Password = SMTP_PASSWORD; $mail->SMTPSecure = SMTP_ENCRYPTION; $mail->Port = SMTP_PORT; $mail->setFrom(CONTACT_FROM_EMAIL, CONTACT_FROM_NAME); $mail->addAddress($data['email'], $data['name']); $mail->isHTML(true); $mail->Subject = "Thank you for contacting us"; $mail->Body = " <!DOCTYPE html> <html> <head> <style> body { font-family: Arial, sans-serif; line-height: 1.6; } .container { max-width: 600px; margin: 0 auto; padding: 20px; } .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; text-align: center; } .content { padding: 20px; background: #f9f9f9; } .message { padding: 15px; background: white; border-left: 4px solid #667eea; margin: 20px 0; } </style> </head> <body> <div class='container'> <div class='header'> <h2>Thank You for Contacting Us</h2> </div> <div class='content'> <p>Dear " . htmlspecialchars($data['name']) . ",</p> <p>Thank you for reaching out to us. We have received your message and will get back to you as soon as possible.</p> <p>Here's a copy of your message for your reference:</p> <div class='message'> <strong>Subject:</strong> " . htmlspecialchars($data['subject']) . "<br><br> <strong>Message:</strong><br> " . nl2br(htmlspecialchars($data['message'])) . " </div> <p>Best regards,<br> " . CONTACT_FROM_NAME . " Team</p> </div> </div> </body> </html> "; $mail->AltBody = "Thank you for contacting us. We have received your message and will respond shortly.\n\n" . "Your message:\nSubject: {$data['subject']}\n\n{$data['message']}"; $mail->send(); return true; } catch (Exception $e) { error_log("Auto-reply failed: " . $e->getMessage()); return false; } } /** * Verify reCAPTCHA */ function verifyRecaptcha($response) { if (!RECAPTCHA_ENABLED) { return true; } $url = 'https://www.google.com/recaptcha/api/siteverify'; $data = [ 'secret' => RECAPTCHA_SECRET_KEY, 'response' => $response, 'remoteip' => $_SERVER['REMOTE_ADDR'] ]; $options = [ 'http' => [ 'header' => "Content-type: application/x-www-form-urlencoded\r\n", 'method' => 'POST', 'content' => http_build_query($data) ] ]; $context = stream_context_create($options); $result = file_get_contents($url, false, $context); $resultJson = json_decode($result, true); return isset($resultJson['success']) && $resultJson['success'] === true; }

5. includes/contact-process.php

<?php /** * Contact Form Processing Script */ require_once __DIR__ . '/contact-functions.php'; // Initialize response array $response = [ 'success' => false, 'message' => '', 'errors' => [] ]; // Check if form was submitted if ($_SERVER['REQUEST_METHOD'] !== 'POST') { $response['message'] = 'Invalid request method.'; echo json_encode($response); exit; } // Check CSRF token if (!verifyCSRFToken($_POST['csrf_token'] ?? '')) { $response['message'] = 'Invalid security token. Please refresh the page and try again.'; echo json_encode($response); exit; } // Check honeypot (should be empty) if (!empty($_POST['website'])) { // Bot detected - silently fail $response['success'] = true; // Pretend it worked $response['message'] = 'Thank you for your message. We\'ll get back to you soon.'; echo json_encode($response); exit; } // Get and validate input $name = sanitizeInput($_POST['name'] ?? ''); $email = sanitizeInput($_POST['email'] ?? ''); $subject = sanitizeInput($_POST['subject'] ?? ''); $message = sanitizeInput($_POST['message'] ?? ''); $recaptcha_response = $_POST['g-recaptcha-response'] ?? ''; // Validate required fields $errors = []; if (empty($name)) { $errors['name'] = 'Please enter your name.'; } elseif (strlen($name) < 2) { $errors['name'] = 'Name must be at least 2 characters long.'; } if (empty($email)) { $errors['email'] = 'Please enter your email address.'; } elseif (!validateEmail($email)) { $errors['email'] = 'Please enter a valid email address.'; } if (empty($subject)) { $errors['subject'] = 'Please enter a subject.'; } elseif (strlen($subject) < 3) { $errors['subject'] = 'Subject must be at least 3 characters long.'; } if (empty($message)) { $errors['message'] = 'Please enter your message.'; } elseif (strlen($message) < 10) { $errors['message'] = 'Message must be at least 10 characters long.'; } // Check rate limit $ip = $_SERVER['REMOTE_ADDR'] ?? ''; if (!checkRateLimit($ip)) { $errors['rate_limit'] = 'Too many attempts. Please try again later.'; } // Verify reCAPTCHA if (RECAPTCHA_ENABLED && !verifyRecaptcha($recaptcha_response)) { $errors['recaptcha'] = 'Please complete the CAPTCHA verification.'; } // If there are errors, return them if (!empty($errors)) { $response['errors'] = $errors; $response['message'] = 'Please correct the errors below.'; echo json_encode($response); exit; } // Prepare data for processing $form_data = [ 'name' => $name, 'email' => $email, 'subject' => $subject, 'message' => $message ]; // Save to database $submission_id = saveSubmission($form_data); if (!$submission_id) { error_log("Failed to save contact submission to database"); // Continue anyway - don't tell user about DB error } // Increment rate limit counter incrementRateLimit($ip); // Send email $email_sent = sendContactEmail($form_data); if ($email_sent) { $response['success'] = true; $response['message'] = 'Thank you for your message! We\'ll get back to you soon.'; // Clear CSRF token to prevent resubmission unset($_SESSION['csrf_token']); } else { $response['message'] = 'There was an error sending your message. Please try again later.'; error_log("Failed to send contact email from {$email}"); } echo json_encode($response); exit;

6. contact.php (Main Contact Page)

<?php require_once 'includes/config.php'; require_once 'includes/contact-functions.php'; $page_title = 'Contact Us - My Blog'; $csrf_token = generateCSRFToken(); include 'includes/header.php'; ?> <link rel="stylesheet" href="css/contact.css"> <?php if (RECAPTCHA_ENABLED): ?> <script src="https://www.google.com/recaptcha/api.js" async defer></script> <?php endif; ?> <div class="contact-page"> <div class="contact-header"> <h1>Get in Touch</h1> <p>Have questions or feedback? We'd love to hear from you. Send us a message and we'll respond as soon as possible.</p> </div> <div class="contact-container"> <div class="contact-info"> <div class="info-card"> <div class="info-icon">📧</div> <h3>Email Us</h3> <p><a href="mailto:<?php echo CONTACT_TO_EMAIL; ?>"><?php echo CONTACT_TO_EMAIL; ?></a></p> <p class="info-note">We typically respond within 24 hours</p> </div> <div class="info-card"> <div class="info-icon">📍</div> <h3>Visit Us</h3> <p>123 Blog Street<br>Digital City, DC 12345</p> </div> <div class="info-card"> <div class="info-icon">📱</div> <h3>Follow Us</h3> <div class="social-links"> <a href="#" class="social-link">Twitter</a> <a href="#" class="social-link">Facebook</a> <a href="#" class="social-link">Instagram</a> </div> </div> </div> <div class="contact-form-wrapper"> <form id="contact-form" class="contact-form" method="POST" novalidate> <input type="hidden" name="csrf_token" value="<?php echo $csrf_token; ?>"> <!-- Honeypot field (hidden from real users) --> <div class="form-group honeypot"> <label for="website">Website (leave empty)</label> <input type="text" id="website" name="website" tabindex="-1" autocomplete="off"> </div> <div class="form-row"> <div class="form-group"> <label for="name">Your Name *</label> <input type="text" id="name" name="name" class="form-control" placeholder="John Doe" required> <div class="error-message" id="name-error"></div> </div> <div class="form-group"> <label for="email">Email Address *</label> <input type="email" id="email" name="email" class="form-control" placeholder="[email protected]" required> <div class="error-message" id="email-error"></div> </div> </div> <div class="form-group"> <label for="subject">Subject *</label> <input type="text" id="subject" name="subject" class="form-control" placeholder="What's this about?" required> <div class="error-message" id="subject-error"></div> </div> <div class="form-group"> <label for="message">Message *</label> <textarea id="message" name="message" class="form-control" rows="6" placeholder="Your message..." required></textarea> <div class="error-message" id="message-error"></div> </div> <?php if (RECAPTCHA_ENABLED): ?> <div class="form-group"> <div class="g-recaptcha" data-sitekey="<?php echo RECAPTCHA_SITE_KEY; ?>"></div> <div class="error-message" id="recaptcha-error"></div> </div> <?php endif; ?> <div class="form-group"> <button type="submit" class="btn-submit" id="submit-btn"> <span class="btn-text">Send Message</span> <span class="btn-loader" style="display: none;">Sending...</span> </button> </div> <div class="form-note"> * Required fields. Your information will only be used to respond to your inquiry. </div> </form> <div id="form-message" class="form-message" style="display: none;"></div> </div> </div> </div> <script src="js/contact.js"></script> <?php include 'includes/footer.php'; ?>

7. contact-success.php

<?php require_once 'includes/config.php'; $page_title = 'Message Sent - My Blog'; include 'includes/header.php'; ?> <div class="success-page"> <div class="success-card"> <div class="success-icon">✓</div> <h1>Thank You!</h1> <p class="success-message">Your message has been sent successfully. We'll get back to you as soon as possible.</p> <div class="success-details"> <p>What happens next?</p> <ul> <li>You'll receive a confirmation email shortly</li> <li>Our team will review your message</li> <li>We'll respond within 24-48 hours</li> </ul> </div> <div class="success-actions"> <a href="index.php" class="btn-primary">Return to Homepage</a> <a href="blog.php" class="btn-secondary">Browse Blog Posts</a> </div> </div> </div> <style> .success-page { min-height: 60vh; display: flex; align-items: center; justify-content: center; padding: 2rem; } .success-card { background: white; border-radius: 10px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); padding: 3rem; max-width: 600px; text-align: center; } .success-icon { width: 80px; height: 80px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; font-size: 40px; line-height: 80px; border-radius: 50%; margin: 0 auto 1.5rem; } .success-card h1 { color: #333; margin-bottom: 1rem; } .success-message { color: #666; font-size: 1.1rem; margin-bottom: 2rem; line-height: 1.6; } .success-details { background: #f8f9fa; border-radius: 8px; padding: 1.5rem; margin: 2rem 0; text-align: left; } .success-details p { font-weight: bold; color: #333; margin-bottom: 1rem; } .success-details ul { list-style: none; padding: 0; } .success-details li { padding: 0.5rem 0; padding-left: 1.5rem; position: relative; color: #666; } .success-details li:before { content: "•"; color: #667eea; font-weight: bold; position: absolute; left: 0; } .success-actions { display: flex; gap: 1rem; justify-content: center; margin-top: 2rem; } .btn-primary, .btn-secondary { display: inline-block; padding: 0.8rem 1.5rem; border-radius: 5px; text-decoration: none; transition: opacity 0.3s; } .btn-primary { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; } .btn-secondary { background: #6c757d; color: white; } .btn-primary:hover, .btn-secondary:hover { opacity: 0.8; } @media (max-width: 768px) { .success-card { padding: 2rem; } .success-actions { flex-direction: column; } } </style> <?php include 'includes/footer.php'; ?>

8. css/contact.css

/* Contact Page Styles */ .contact-page { max-width: 1200px; margin: 0 auto; padding: 2rem 1rem; } .contact-header { text-align: center; margin-bottom: 3rem; } .contact-header h1 { font-size: 2.5rem; color: #333; margin-bottom: 1rem; } .contact-header p { font-size: 1.1rem; color: #666; max-width: 600px; margin: 0 auto; line-height: 1.6; } .contact-container { display: grid; grid-template-columns: 1fr 2fr; gap: 2rem; background: white; border-radius: 10px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); overflow: hidden; } /* Contact Info Sidebar */ .contact-info { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 2rem; color: white; } .info-card { margin-bottom: 2rem; padding: 1.5rem; background: rgba(255,255,255,0.1); border-radius: 8px; backdrop-filter: blur(10px); } .info-card:last-child { margin-bottom: 0; } .info-icon { font-size: 2rem; margin-bottom: 1rem; } .info-card h3 { font-size: 1.2rem; margin-bottom: 1rem; color: white; } .info-card p { margin: 0.5rem 0; line-height: 1.5; opacity: 0.9; } .info-card a { color: white; text-decoration: none; border-bottom: 1px dotted rgba(255,255,255,0.5); } .info-card a:hover { border-bottom-color: white; } .info-note { font-size: 0.9rem; opacity: 0.8; margin-top: 0.5rem; } .social-links { display: flex; gap: 1rem; flex-wrap: wrap; } .social-link { display: inline-block; padding: 0.3rem 0.8rem; background: rgba(255,255,255,0.2); border-radius: 20px; color: white; text-decoration: none; font-size: 0.9rem; transition: background 0.3s; } .social-link:hover { background: rgba(255,255,255,0.3); } /* Contact Form */ .contact-form-wrapper { padding: 2rem; } .contact-form { max-width: 600px; } .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; } .form-group { margin-bottom: 1.5rem; } .form-group label { display: block; margin-bottom: 0.5rem; font-weight: 500; color: #333; } .form-control { width: 100%; padding: 0.8rem; border: 1px solid #ddd; border-radius: 5px; font-size: 1rem; transition: border-color 0.3s, box-shadow 0.3s; } .form-control:focus { outline: none; border-color: #667eea; box-shadow: 0 0 0 3px rgba(102,126,234,0.1); } .form-control.error { border-color: #dc3545; } .error-message { color: #dc3545; font-size: 0.85rem; margin-top: 0.3rem; min-height: 20px; } /* Honeypot field - hidden from users */ .honeypot { position: absolute; left: -9999px; opacity: 0; pointer-events: none; height: 0; width: 0; } /* Submit Button */ .btn-submit { width: 100%; padding: 1rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 5px; font-size: 1.1rem; font-weight: 500; cursor: pointer; transition: opacity 0.3s, transform 0.3s; } .btn-submit:hover:not(:disabled) { opacity: 0.9; transform: translateY(-2px); } .btn-submit:disabled { opacity: 0.6; cursor: not-allowed; } .btn-loader { display: inline-block; } .form-note { font-size: 0.85rem; color: #888; margin-top: 1rem; text-align: center; } /* Form Messages */ .form-message { margin-top: 1.5rem; padding: 1rem; border-radius: 5px; text-align: center; } .form-message.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .form-message.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } /* reCAPTCHA */ .g-recaptcha { margin-bottom: 1rem; } /* Responsive Design */ @media (max-width: 768px) { .contact-container { grid-template-columns: 1fr; } .contact-info { order: 2; } .contact-form-wrapper { order: 1; } .form-row { grid-template-columns: 1fr; } .contact-header h1 { font-size: 2rem; } } @media (max-width: 480px) { .contact-page { padding: 1rem; } .contact-header { margin-bottom: 2rem; } .contact-form-wrapper, .contact-info { padding: 1.5rem; } }

9. js/contact.js

/** * Contact Form JavaScript * Handles form validation and AJAX submission */ document.addEventListener('DOMContentLoaded', function() { const contactForm = document.getElementById('contact-form'); const submitBtn = document.getElementById('submit-btn'); const btnText = submitBtn.querySelector('.btn-text'); const btnLoader = submitBtn.querySelector('.btn-loader'); const formMessage = document.getElementById('form-message'); if (!contactForm) return; // Real-time validation const inputs = contactForm.querySelectorAll('input, textarea'); inputs.forEach(input => { input.addEventListener('blur', function() { validateField(this); }); input.addEventListener('input', function() { if (this.classList.contains('error')) { validateField(this); } }); }); // Form submission contactForm.addEventListener('submit', async function(e) { e.preventDefault(); // Clear previous messages clearErrors(); formMessage.style.display = 'none'; // Validate all fields let isValid = true; inputs.forEach(input => { if (!validateField(input)) { isValid = false; } }); // Check reCAPTCHA if enabled if (typeof grecaptcha !== 'undefined') { const recaptchaResponse = grecaptcha.getResponse(); if (!recaptchaResponse) { showFieldError('recaptcha-error', 'Please complete the CAPTCHA verification.'); isValid = false; } } if (!isValid) return; // Show loading state setLoadingState(true); // Collect form data const formData = new FormData(contactForm); try { const response = await fetch('includes/contact-process.php', { method: 'POST', body: formData }); const result = await response.json(); if (result.success) { // Success - redirect or show message showSuccessMessage(result.message); contactForm.reset(); // Reset reCAPTCHA if enabled if (typeof grecaptcha !== 'undefined') { grecaptcha.reset(); } // Redirect to success page after 2 seconds setTimeout(() => { window.location.href = 'contact-success.php'; }, 2000); } else { // Show errors if (result.errors) { Object.keys(result.errors).forEach(field => { showFieldError(`${field}-error`, result.errors[field]); }); } if (result.message) { showErrorMessage(result.message); } // Reset reCAPTCHA if there was an error if (typeof grecaptcha !== 'undefined') { grecaptcha.reset(); } } } catch (error) { console.error('Form submission error:', error); showErrorMessage('An unexpected error occurred. Please try again.'); if (typeof grecaptcha !== 'undefined') { grecaptcha.reset(); } } finally { setLoadingState(false); } }); // Field validation function function validateField(field) { const fieldName = field.name; const value = field.value.trim(); const errorElement = document.getElementById(`${fieldName}-error`); if (!errorElement) return true; let isValid = true; let errorMessage = ''; // Skip honeypot field if (fieldName === 'website') return true; // Required fields if (field.hasAttribute('required') && !value) { isValid = false; errorMessage = 'This field is required.'; } // Specific validations if (isValid && value) { switch (field.type) { case 'email': if (!isValidEmail(value)) { isValid = false; errorMessage = 'Please enter a valid email address.'; } break; case 'text': if (fieldName === 'name' && value.length < 2) { isValid = false; errorMessage = 'Name must be at least 2 characters.'; } if (fieldName === 'subject' && value.length < 3) { isValid = false; errorMessage = 'Subject must be at least 3 characters.'; } break; case 'textarea': if (fieldName === 'message' && value.length < 10) { isValid = false; errorMessage = 'Message must be at least 10 characters.'; } break; } } // Update UI if (isValid) { field.classList.remove('error'); errorElement.textContent = ''; } else { field.classList.add('error'); errorElement.textContent = errorMessage; } return isValid; } // Helper functions function isValidEmail(email) { const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return re.test(email); } function showFieldError(elementId, message) { const element = document.getElementById(elementId); if (element) { element.textContent = message; // Highlight corresponding input const fieldName = elementId.replace('-error', ''); const input = document.querySelector(`[name="${fieldName}"]`); if (input) { input.classList.add('error'); } } } function clearErrors() { document.querySelectorAll('.error-message').forEach(el => { el.textContent = ''; }); document.querySelectorAll('.error').forEach(el => { el.classList.remove('error'); }); } function showSuccessMessage(message) { formMessage.className = 'form-message success'; formMessage.textContent = message; formMessage.style.display = 'block'; // Auto hide after 5 seconds setTimeout(() => { formMessage.style.display = 'none'; }, 5000); } function showErrorMessage(message) { formMessage.className = 'form-message error'; formMessage.textContent = message; formMessage.style.display = 'block'; } function setLoadingState(loading) { if (loading) { submitBtn.disabled = true; btnText.style.display = 'none'; btnLoader.style.display = 'inline-block'; } else { submitBtn.disabled = false; btnText.style.display = 'inline-block'; btnLoader.style.display = 'none'; } } });

10. admin/contact-submissions.php

<?php require_once '../includes/config.php'; require_once '../includes/contact-functions.php'; requireAdmin(); // Pagination $page = isset($_GET['page']) ? (int)$_GET['page'] : 1; $limit = 20; $offset = ($page - 1) * $limit; // Filter by status $status_filter = isset($_GET['status']) ? $_GET['status'] : 'all'; $status_condition = ''; if ($status_filter !== 'all') { $status_condition = "WHERE status = '" . mysqli_real_escape_string($conn, $status_filter) . "'"; } // Get total count $count_sql = "SELECT COUNT(*) as total FROM contact_submissions $status_condition"; $count_result = mysqli_query($conn, $count_sql); $total_rows = mysqli_fetch_assoc($count_result)['total']; $total_pages = ceil($total_rows / $limit); // Get submissions $sql = "SELECT * FROM contact_submissions $status_condition ORDER BY created_at DESC LIMIT ? OFFSET ?"; $stmt = mysqli_prepare($conn, $sql); mysqli_stmt_bind_param($stmt, "ii", $limit, $offset); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); $page_title = 'Contact Submissions - Admin'; ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><?php echo $page_title; ?></title> <link rel="stylesheet" href="../css/style.css"> <link rel="stylesheet" href="css/admin-style.css"> <style> .status-badge { display: inline-block; padding: 0.25rem 0.5rem; border-radius: 3px; font-size: 0.8rem; font-weight: 500; } .status-unread { background: #f39c12; color: white; } .status-read { background: #3498db; color: white; } .status-replied { background: #27ae60; color: white; } .filter-bar { background: white; padding: 1rem; border-radius: 5px; margin-bottom: 1rem; display: flex; gap: 1rem; align-items: center; } .export-btn { background: #27ae60; color: white; padding: 0.5rem 1rem; border-radius: 3px; text-decoration: none; margin-left: auto; } .submission-preview { max-width: 300px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #666; } .date-cell { font-size: 0.9rem; color: #888; } </style> </head> <body> <header> <nav class="navbar"> <div class="container"> <h1 class="logo">Contact Submissions</h1> <ul class="nav-links"> <li><a href="index.php">Dashboard</a></li> <li><a href="../index.php">View Site</a></li> <li><a href="logout.php">Logout</a></li> </ul> </div> </nav> </header> <main class="container"> <div class="filter-bar"> <form method="GET" class="filter-form"> <select name="status" onchange="this.form.submit()"> <option value="all" <?php echo $status_filter == 'all' ? 'selected' : ''; ?>>All Status</option> <option value="unread" <?php echo $status_filter == 'unread' ? 'selected' : ''; ?>>Unread</option> <option value="read" <?php echo $status_filter == 'read' ? 'selected' : ''; ?>>Read</option> <option value="replied" <?php echo $status_filter == 'replied' ? 'selected' : ''; ?>>Replied</option> </select> </form> <a href="export-submissions.php?status=<?php echo $status_filter; ?>" class="export-btn"> Export to CSV </a> </div> <table class="admin-table"> <thead> <tr> <th>ID</th> <th>Name</th> <th>Email</th> <th>Subject</th> <th>Message</th> <th>Status</th> <th>Date</th> <th>Actions</th> </tr> </thead> <tbody> <?php if (mysqli_num_rows($result) > 0): ?> <?php while ($row = mysqli_fetch_assoc($result)): ?> <tr> <td>#<?php echo $row['id']; ?></td> <td><?php echo htmlspecialchars($row['name']); ?></td> <td> <a href="mailto:<?php echo htmlspecialchars($row['email']); ?>"> <?php echo htmlspecialchars($row['email']); ?> </a> </td> <td><?php echo htmlspecialchars($row['subject']); ?></td> <td> <div class="submission-preview"> <?php echo htmlspecialchars(substr($row['message'], 0, 50)) . '...'; ?> </div> </td> <td> <span class="status-badge status-<?php echo $row['status']; ?>"> <?php echo ucfirst($row['status']); ?> </span> </td> <td class="date-cell"> <?php echo date('M j, Y g:i A', strtotime($row['created_at'])); ?> </td> <td> <a href="view-submission.php?id=<?php echo $row['id']; ?>" class="btn-edit"> View </a> <a href="delete-submission.php?id=<?php echo $row['id']; ?>" class="btn-delete" onclick="return confirm('Are you sure you want to delete this submission?')"> Delete </a> </td> </tr> <?php endwhile; ?> <?php else: ?> <tr> <td colspan="8" style="text-align: center; padding: 2rem;"> No contact submissions found. </td> </tr> <?php endif; ?> </tbody> </table> <?php if ($total_pages > 1): ?> <div class="pagination"> <?php if ($page > 1): ?> <a href="?page=<?php echo $page-1; ?>&status=<?php echo $status_filter; ?>" class="page-link"> &laquo; Previous </a> <?php endif; ?> <?php for ($i = 1; $i <= $total_pages; $i++): ?> <a href="?page=<?php echo $i; ?>&status=<?php echo $status_filter; ?>" class="page-link <?php echo $i == $page ? 'active' : ''; ?>"> <?php echo $i; ?> </a> <?php endfor; ?> <?php if ($page < $total_pages): ?> <a href="?page=<?php echo $page+1; ?>&status=<?php echo $status_filter; ?>" class="page-link"> Next &raquo; </a> <?php endif; ?> </div> <?php endif; ?> </main> </body> </html> <?php mysqli_close($conn); ?>

11. admin/view-submission.php

<?php require_once '../includes/config.php'; require_once '../includes/contact-functions.php'; requireAdmin(); $id = isset($_GET['id']) ? (int)$_GET['id'] : 0; // Mark as read $update = $conn->prepare("UPDATE contact_submissions SET status = 'read' WHERE id = ? AND status = 'unread'"); $update->bind_param("i", $id); $update->execute(); // Get submission details $sql = "SELECT * FROM contact_submissions WHERE id = ?"; $stmt = mysqli_prepare($conn, $sql); mysqli_stmt_bind_param($stmt, "i", $id); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); $submission = mysqli_fetch_assoc($result); if (!$submission) { header('Location: contact-submissions.php'); exit(); } $page_title = 'View Submission - Admin'; ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><?php echo $page_title; ?></title> <link rel="stylesheet" href="../css/style.css"> <link rel="stylesheet" href="css/admin-style.css"> <style> .submission-detail { background: white; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); padding: 2rem; margin: 2rem 0; } .submission-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; padding-bottom: 1rem; border-bottom: 1px solid #eee; } .submission-meta { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; padding: 1rem; background: #f8f9fa; border-radius: 5px; } .meta-item { display: flex; flex-direction: column; } .meta-label { font-size: 0.85rem; color: #888; margin-bottom: 0.3rem; } .meta-value { font-weight: 500; color: #333; } .message-box { background: #f8f9fa; border-radius: 5px; padding: 1.5rem; margin: 2rem 0; line-height: 1.6; white-space: pre-wrap; } .reply-section { margin-top: 2rem; padding-top: 2rem; border-top: 1px solid #eee; } .reply-form textarea { width: 100%; padding: 1rem; border: 1px solid #ddd; border-radius: 5px; margin-bottom: 1rem; font-family: inherit; } .action-buttons { display: flex; gap: 1rem; margin-top: 2rem; } .btn-reply { background: #3498db; color: white; padding: 0.5rem 1rem; border: none; border-radius: 3px; cursor: pointer; } .btn-mark-replied { background: #27ae60; color: white; padding: 0.5rem 1rem; border: none; border-radius: 3px; cursor: pointer; } .ip-info { font-family: monospace; color: #666; } </style> </head> <body> <header> <nav class="navbar"> <div class="container"> <h1 class="logo">View Submission #<?php echo $submission['id']; ?></h1> <ul class="nav-links"> <li><a href="index.php">Dashboard</a></li> <li><a href="contact-submissions.php">Back to List</a></li> <li><a href="logout.php">Logout</a></li> </ul> </div> </nav> </header> <main class="container"> <div class="submission-detail"> <div class="submission-header"> <h2><?php echo htmlspecialchars($submission['subject']); ?></h2> <span class="status-badge status-<?php echo $submission['status']; ?>"> <?php echo ucfirst($submission['status']); ?> </span> </div> <div class="submission-meta"> <div class="meta-item"> <span class="meta-label">From:</span> <span class="meta-value"> <?php echo htmlspecialchars($submission['name']); ?> (<?php echo htmlspecialchars($submission['email']); ?>) </span> </div> <div class="meta-item"> <span class="meta-label">Received:</span> <span class="meta-value"> <?php echo date('F j, Y g:i:s A', strtotime($submission['created_at'])); ?> </span> </div> <div class="meta-item"> <span class="meta-label">IP Address:</span> <span class="meta-value ip-info"><?php echo $submission['ip_address'] ?? 'Unknown'; ?></span> </div> <?php if (!empty($submission['user_agent'])): ?> <div class="meta-item"> <span class="meta-label">User Agent:</span> <span class="meta-value ip-info"><?php echo htmlspecialchars($submission['user_agent']); ?></span> </div> <?php endif; ?> </div> <h3>Message:</h3> <div class="message-box"> <?php echo nl2br(htmlspecialchars($submission['message'])); ?> </div> <div class="reply-section"> <h3>Reply to this message:</h3> <form class="reply-form" action="mailto:<?php echo htmlspecialchars($submission['email']); ?>" method="POST" enctype="text/plain"> <textarea name="message" rows="6" placeholder="Type your reply here..."></textarea> <div class="action-buttons"> <button type="submit" class="btn-reply">Send Reply via Email Client</button> <button type="button" class="btn-mark-replied" onclick="markAsReplied(<?php echo $submission['id']; ?>)"> Mark as Replied </button> </div> </form> </div> </div> </main> <script> function markAsReplied(id) { if (confirm('Mark this submission as replied?')) { fetch('update-submission-status.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: 'id=' + id + '&status=replied' }) .then(response => response.json()) .then(data => { if (data.success) { location.reload(); } else { alert('Error updating status'); } }); } } </script> </body> </html>

🚀 How to Use This Project Step by Step

Step 1: Environment Setup

  1. Ensure you have PHP 7.4+ and MySQL installed
  2. Install Composer if not already installed

Step 2: Install Dependencies

Navigate to your project root and run:

composer install

This will install PHPMailer and dotenv libraries

Step 3: Configure Environment

  1. Copy .env.example to .env
  2. Update database credentials
  3. Configure SMTP settings:
  • For Gmail: Use App Password (not regular password)
  • For other providers: Use their SMTP settings
  1. Set up reCAPTCHA (optional):
  • Get keys from https://www.google.com/recaptcha/admin

Step 4: Database Setup

  1. Run the SQL script in database/contact.sql to create tables
  2. Verify tables are created in phpMyAdmin

Step 5: Add Navigation Link

Add contact page link to includes/header.php:

<li><a href="contact.php">Contact</a></li>

Step 6: Test the Form

  1. Navigate to http://localhost/blog-website/contact.php
  2. Fill out and submit the form
  3. Check:
  • Database for new entry
  • Email inbox for notification
  • Spam folder if email not received

Step 7: Admin Access

  1. Login to admin panel
  2. Click on "Contact Submissions" in dashboard
  3. View, reply to, and manage submissions

🛡️ Security Features

Implemented Security Measures:

  • ✅ CSRF tokens for form protection
  • ✅ Input sanitization and validation
  • ✅ Honeypot field to catch bots
  • ✅ Rate limiting by IP address
  • ✅ reCAPTCHA integration (optional)
  • ✅ Prepared statements for SQL queries
  • ✅ Environment variables for credentials
  • ✅ XSS prevention with htmlspecialchars

Email Security:

  • SMTP with TLS/SSL encryption
  • App passwords for Gmail (not regular passwords)
  • No credentials in code (uses .env)

📊 Admin Features Summary

Contact Management:

  • View all submissions in paginated table
  • Filter by status (unread/read/replied)
  • Mark messages as read/replied
  • Delete spam or old submissions
  • Export to CSV for analysis

Submission Details:

  • View full message content
  • See sender information
  • Track IP address and user agent
  • Quick reply via email client
  • Status tracking

🔧 Troubleshooting

Common Issues:

  1. Emails not sending:
  • Check SMTP credentials in .env
  • Verify SMTP port is open (587 for TLS)
  • For Gmail, use App Password not regular password
  1. Database connection errors:
  • Verify database credentials
  • Ensure MySQL service is running
  • Check if database exists
  1. reCAPTCHA not working:
  • Verify site key and secret key
  • Ensure domain is registered in Google console
  • Check if RECAPTCHA_ENABLED is true
  1. AJAX form not submitting:
  • Check browser console for errors
  • Verify path to contact-process.php
  • Ensure .env file has correct settings

🚀 Future Enhancements

  1. Email Templates: Customizable HTML email templates
  2. Attachment Support: Allow file uploads with contact form
  3. Multi-language Support: Language selector for form
  4. Analytics Dashboard: Track submission trends over time
  5. Auto-responder Customization: Custom auto-reply messages
  6. Integration with CRM: Connect to popular CRM systems
  7. SMS Notifications: Get text alerts for new submissions
  8. Advanced Spam Filtering: AI-based spam detection

📝 Conclusion

This contact form system provides a complete solution for handling user inquiries on your blog website. With secure email integration, database storage, and a comprehensive admin interface, you can effectively manage communication with your audience. The system is built with security best practices and can be easily customized to fit your specific needs.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper