π§ 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">
« 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 »
</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
- Ensure you have PHP 7.4+ and MySQL installed
- 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
- Copy
.env.exampleto.env - Update database credentials
- Configure SMTP settings:
- For Gmail: Use App Password (not regular password)
- For other providers: Use their SMTP settings
- Set up reCAPTCHA (optional):
- Get keys from https://www.google.com/recaptcha/admin
Step 4: Database Setup
- Run the SQL script in
database/contact.sqlto create tables - 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
- Navigate to
http://localhost/blog-website/contact.php - Fill out and submit the form
- Check:
- Database for new entry
- Email inbox for notification
- Spam folder if email not received
Step 7: Admin Access
- Login to admin panel
- Click on "Contact Submissions" in dashboard
- 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:
- Emails not sending:
- Check SMTP credentials in
.env - Verify SMTP port is open (587 for TLS)
- For Gmail, use App Password not regular password
- Database connection errors:
- Verify database credentials
- Ensure MySQL service is running
- Check if database exists
- reCAPTCHA not working:
- Verify site key and secret key
- Ensure domain is registered in Google console
- Check if RECAPTCHA_ENABLED is true
- AJAX form not submitting:
- Check browser console for errors
- Verify path to contact-process.php
- Ensure .env file has correct settings
π Future Enhancements
- Email Templates: Customizable HTML email templates
- Attachment Support: Allow file uploads with contact form
- Multi-language Support: Language selector for form
- Analytics Dashboard: Track submission trends over time
- Auto-responder Customization: Custom auto-reply messages
- Integration with CRM: Connect to popular CRM systems
- SMS Notifications: Get text alerts for new submissions
- 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.