Prérequis
- Niveau : bases PHP, formulaires HTML.
- Outils : XAMPP/WAMP/Laragon (PHP 8.2+ recommandé en 2026 (informations vérifiées en avril 2026, susceptibles d’évoluer)), Composer pour PHPMailer.
- Optionnel : compte Gmail avec « mot de passe d’application » activé pour SMTP, ou Mailgun / SendGrid / Brevo en production.
- Temps estimé : 1 h 30.
Pourquoi ne pas se contenter de mail() ?
La fonction mail() est peu fiable en production : pas de SPF/DKIM, pas d’authentification SMTP, vos messages atterrissent en spam. Pour un site sérieux, utilisez PHPMailer + un service SMTP réel (Brevo, Mailgun, SendGrid, Amazon SES). Cet article montre les deux approches.
Le formulaire de contact : indispensable pour tout site
Un formulaire de contact permet à vos visiteurs de vous écrire directement depuis votre site. Voici comment créer un formulaire fonctionnel et sécurisé avec PHP.
Le formulaire HTML
<form action="envoyer.php" method="POST" class="form-contact">
<div class="champ">
<label for="nom">Nom complet *</label>
<input type="text" id="nom" name="nom" required minlength="2">
</div>
<div class="champ">
<label for="email">Email *</label>
<input type="email" id="email" name="email" required>
</div>
<div class="champ">
<label for="sujet">Sujet</label>
<select id="sujet" name="sujet">
<option value="info">Demande d'information</option>
<option value="devis">Demande de devis</option>
<option value="support">Support technique</option>
<option value="autre">Autre</option>
</select>
</div>
<div class="champ">
<label for="message">Message *</label>
<textarea id="message" name="message" rows="6" required minlength="10"></textarea>
</div>
<button type="submit" name="envoyer">Envoyer le message</button>
</form>
Le script PHP (envoyer.php)
<?php
// Vérifier que le formulaire a été soumis
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: contact.html');
exit;
}
// 1. Récupérer et nettoyer les données
$nom = htmlspecialchars(strip_tags(trim($_POST['nom'])));
$email = trim($_POST['email']); // Note : FILTER_SANITIZE_EMAIL déprécié PHP 8.1+, on valide plus bas avec FILTER_VALIDATE_EMAIL
$sujet = htmlspecialchars(strip_tags(trim($_POST['sujet'])));
$message = htmlspecialchars(strip_tags(trim($_POST['message'])));
// 2. Valider les données
$erreurs = [];
if (strlen($nom) < 2) {
$erreurs[] = "Le nom est trop court";
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$erreurs[] = "Email invalide";
}
if (strlen($message) < 10) {
$erreurs[] = "Le message est trop court (10 caractères minimum)";
}
// 3. S'il y a des erreurs, les afficher
if (!empty($erreurs)) {
foreach ($erreurs as $erreur) {
echo "<p style='color:red;'>⚠️ $erreur</p>";
}
echo "<a href='javascript:history.back()'>← Retour au formulaire</a>";
exit;
}
// 4. Préparer et envoyer l'email
$destinataire = "contact@votresite.sn";
$sujet_email = "Contact depuis le site : $sujet";
$contenu = "Nouveau message depuis le formulaire de contact\n\n";
$contenu .= "Nom : $nom\n";
$contenu .= "Email : $email\n";
$contenu .= "Sujet : $sujet\n";
$contenu .= "Message :\n$message\n";
// Sécurité : From doit être votre propre domaine, jamais l'email utilisateur (faille header injection)
$headers = "From: contact@votresite.sn\r\n";
$headers .= "Reply-To: $email\r\n";
$headers .= "Content-Type: text/plain; charset=UTF-8\r\n";
if (mail($destinataire, $sujet_email, $contenu, $headers)) {
echo "<h2>✅ Message envoyé avec succès !</h2>";
echo "<p>Nous vous répondrons sous 24h.</p>";
} else {
echo "<h2>❌ Erreur lors de l'envoi</h2>";
echo "<p>Veuillez réessayer ou nous contacter directement.</p>";
}
?>
Version améliorée avec PHPMailer (recommandé)
La fonction mail() native est limitée. PHPMailer est plus fiable et supporte SMTP :
// Installer via Composer
// composer require phpmailer/phpmailer
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
$mail = new PHPMailer(true);
try {
// Configuration SMTP (exemple avec Gmail)
$mail->isSMTP();
$mail->Host = 'smtp.gmail.com';
$mail->SMTPAuth = true;
$mail->Username = 'votre@gmail.com';
$mail->Password = 'mot_de_passe_application'; // Pas votre mot de passe Gmail !
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;
$mail->CharSet = 'UTF-8';
// Expéditeur et destinataire
$mail->setFrom('votre@gmail.com', 'Mon Site');
$mail->addAddress('contact@votresite.sn');
$mail->addReplyTo($email, $nom);
// Contenu
$mail->isHTML(true);
$mail->Subject = "Contact : $sujet";
$mail->Body = "
<h2>Nouveau message de contact</h2>
<p><strong>Nom :</strong> $nom</p>
<p><strong>Email :</strong> $email</p>
<p><strong>Message :</strong></p>
<p>$message</p>
";
$mail->send();
echo "Message envoyé !";
} catch (Exception $e) {
echo "Erreur : " . $mail->ErrorInfo;
}
Protection anti-spam
<!-- Honeypot : champ invisible pour piéger les bots -->
<div style="display:none;">
<input type="text" name="website" value="">
</div>
<!-- PHP : rejeter si le honeypot est rempli -->
<?php
if (!empty($_POST['website'])) {
// C'est un bot ! Ignorer silencieusement
header('Location: merci.html');
exit;
}
// Limiter le taux d'envoi (1 message par minute)
session_start();
if (isset($_SESSION['dernier_envoi']) &&
time() - $_SESSION['dernier_envoi'] < 60) {
die("Veuillez attendre avant d'envoyer un autre message.");
}
$_SESSION['dernier_envoi'] = time();
?>
CSS du formulaire
.form-contact {
max-width: 600px;
margin: 40px auto;
padding: 30px;
}
.champ { margin-bottom: 20px; }
.champ label {
display: block;
margin-bottom: 6px;
font-weight: 600;
color: #333;
}
.champ input, .champ select, .champ textarea {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.3s;
}
.champ input:focus, .champ textarea:focus {
border-color: #667eea;
outline: none;
}
button[type="submit"] {
width: 100%;
padding: 14px;
background: #667eea;
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
transition: background 0.3s;
}
button[type="submit"]:hover { background: #5a6fd6; }
Erreurs fréquentes
Faille « Email Header Injection »
Cause : on met l'email utilisateur dans From: sans valider — un attaquant peut injecter \r\nBcc: et utiliser votre serveur comme relais spam.
Solution : validez d'abord avec filter_var(..., FILTER_VALIDATE_EMAIL), et utilisez une adresse From: de votre propre domaine (avec Reply-To: = email du visiteur).
Vos emails finissent en spam
Cause : pas de SPF/DKIM/DMARC configurés sur votre domaine, ou utilisation de mail() natif depuis un serveur sans réputation.
Solution : passez par un service SMTP transactionnel (Brevo gratuit jusqu'à 300/jour), configurez SPF/DKIM/DMARC dans votre DNS.
Utiliser FILTER_SANITIZE_EMAIL
Cause : ce filtre est déprécié depuis PHP 8.1.
Solution : utilisez directement filter_var($email, FILTER_VALIDATE_EMAIL) pour valider, sans sanitize.
Mot de passe Gmail dans le code
Cause : on hardcode le mot de passe Gmail (qui ne fonctionne plus depuis 2022, Google exige un App Password).
Solution : activez la 2FA Gmail puis générez un mot de passe d'application. Stockez-le dans .env hors versionnement.
Exercice pratique
🎯 Défi : Formulaire de contact complet
- Créez le formulaire HTML avec validation côté client
- Écrivez le script PHP avec validation côté serveur
- Ajoutez un champ honeypot anti-spam
- Ajoutez une limite de taux (1 message/minute)
- Testez en local avec XAMPP/WAMP
Configurer SPF, DKIM et DMARC pour la délivrabilité
Un script PHP qui envoie des emails sans authentification de domaine finit dans le dossier spam de Gmail, Outlook ou Yahoo dans 80 % des cas. La solution n'est pas dans le code PHP mais dans la configuration DNS du domaine émetteur. Trois enregistrements DNS sont indispensables en 2026 : SPF (Sender Policy Framework), DKIM (DomainKeys Identified Mail) et DMARC (Domain-based Message Authentication). Sans les trois correctement configurés, n'espérez pas qu'un formulaire PHP atteigne les boîtes de réception.
Premier enregistrement : SPF. Ajoutez un TXT à votre zone DNS qui liste les serveurs autorisés à envoyer des emails au nom de votre domaine. Pour un envoi via Brevo (anciennement Sendinblue), cela ressemble à v=spf1 include:spf.brevo.com -all. Pour Postmark, v=spf1 include:spf.mtasv.net -all. Le tilde permissif ~all est déconseillé en 2026 — utilisez le hard fail -all qui rejette les imposteurs sans pitié.
Deuxième enregistrement : DKIM. Le fournisseur d'envoi génère une paire de clés et vous donne un sélecteur DNS à publier (par exemple brevo._domainkey.exemple.com avec une clé publique RSA). Vérifiez ensuite avec dig TXT brevo._domainkey.exemple.com que la clé est bien propagée. Troisième enregistrement : DMARC. Démarrez avec une politique permissive (v=DMARC1; p=none; rua=mailto:dmarc@exemple.com) qui collecte les rapports sans bloquer, puis durcissez progressivement vers p=quarantine puis p=reject une fois SPF et DKIM stabilisés.
Utiliser un service tiers transactionnel plutôt que sendmail local
En 2026, envoyer des emails directement depuis un VPS via la fonction mail() de PHP ou un sendmail local devient pratiquement impossible. Les fournisseurs IP cloud (Hetzner, Contabo, OVH) sont régulièrement listés sur les blocklists antispam, et le port 25 sortant est souvent bloqué par défaut. La solution standard est de passer par un service transactionnel API : Brevo, Postmark, Resend, Mailgun ou Amazon SES.
Pour une PME ouest-africaine qui envoie moins de 300 emails par jour, Brevo offre un free tier généreux (300 emails/jour gratuits) avec une délivrabilité solide. Postmark est plus cher (15 USD pour 10 000 emails/mois) mais sa réputation IP est exceptionnelle — les emails arrivent en boîte de réception même sur des comptes Gmail méfiants. Resend (lancé en 2023) gagne en popularité grâce à son API moderne et son tarif compétitif (3 000 emails/mois gratuits, puis 20 USD pour 50 000).
Le code PHP devient alors quelques lignes via PHPMailer ou directement en cURL contre l'API du fournisseur. Vous gardez le contrôle des messages tout en bénéficiant d'une infrastructure d'envoi professionnelle.
Validation et sécurité côté serveur
Au-delà de la délivrabilité, un formulaire PHP doit valider rigoureusement les données reçues pour éviter les abus et les attaques. Quatre validations minimales sont obligatoires en 2026. Première validation : vérifier que le champ email respecte le format avec filter_var($email, FILTER_VALIDATE_EMAIL). Sans cela, votre script accepte n'importe quelle chaîne, ce qui pollue les rapports et facilite l'injection.
Deuxième validation : vérifier la longueur des champs (nom < 100 caractères, message < 5000) pour éviter qu'un attaquant envoie 100 MB dans un champ name. Troisième validation : nettoyer les retours à la ligne dans les en-têtes pour bloquer la faille Email Header Injection — utilisez str_replace([\"\\r\", \"\\n\"], \"\", $email) sur tous les champs qui finissent dans les headers SMTP. Quatrième validation : ajouter un rate limiting basique côté serveur (par exemple max 5 envois par heure et par IP via Redis ou un fichier de log) pour empêcher l'utilisation du formulaire en bot de spam.
Adaptation au contexte ouest-africain
Pour une PME basée à Dakar, Abidjan, Bamako ou Cotonou qui exploite un site WordPress ou une application PHP custom, l'envoi d'emails via Brevo gratuit couvre les besoins de la plupart des sites vitrines (formulaires de contact, devis, newsletters basiques). Au-delà de 300 emails par jour, basculer sur un plan payant à partir de 9 EUR par mois (≈ 5 900 FCFA). C'est dérisoire face à la valeur d'un email qui arrive en inbox plutôt qu'en spam.
Côté délivrabilité spécifique, les serveurs des grands ISP locaux (Sonatel, Orange, MTN au Bénin et Côte d'Ivoire) suivent les standards globaux : un domaine sans SPF/DKIM/DMARC est aussi pénalisé qu'à l'international. La bonne nouvelle est que les meilleurs services transactionnels (Brevo, Postmark) ont des serveurs européens proches qui offrent une bonne latence depuis l'Afrique de l'Ouest. Pour les volumes plus larges, voir aussi le tutoriel complet SPF DKIM DMARC MTA-STS BIMI.
Exemple complet avec Brevo API en PHP moderne
Voici un exemple de code PHP 8 moderne qui envoie un email via l'API Brevo, sans dépendre de PHPMailer ni de la fonction mail() locale. Cette approche reste légère, ne nécessite que cURL (présent par défaut sur tous les hébergements PHP), et donne une délivrabilité professionnelle dès le premier jour.
<?php
declare(strict_types=1);
function envoyer_email_brevo(string $to, string $sujet, string $message, string $apiKey): bool {
$payload = [
'sender' => ['name' => 'ITSkillsCenter', 'email' => 'noreply@exemple.com'],
'to' => [['email' => $to]],
'subject' => $sujet,
'htmlContent' => $message,
];
$ch = curl_init('https://api.brevo.com/v3/smtp/email');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'accept: application/json',
'api-key: ' . $apiKey,
'content-type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$response = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $code === 201;
}
Pour utiliser ce code, il suffit de stocker la clé API Brevo dans une variable d'environnement (jamais dans le code source committé), puis d'appeler la fonction depuis votre script de traitement de formulaire. Le retour booléen permet de logger les échecs et de notifier l'équipe d'astreinte si plusieurs envois échouent en série, signe probable d'un problème de quota ou d'authentification.
Stockage des credentials : les bonnes pratiques 2026
Le piège le plus courant pour un développeur PHP est de stocker la clé API SMTP ou Brevo en clair dans le code source. Ce code finit dans Git, parfois dans un repo public, et la clé fuit. Trois patterns sécurisés sont à adopter dès le premier jour. Premier pattern : variables d'environnement chargées depuis un fichier .env non committé (via la librairie vlucas/phpdotenv). Le fichier .env est dans .gitignore et différent par environnement (dev, staging, prod).
Deuxième pattern : variables d'environnement définies au niveau du serveur web (Apache SetEnv ou Nginx fastcgi_param). C'est plus robuste pour la prod car le secret n'existe nulle part dans le filesystem du projet. Troisième pattern : un coffre-fort dédié comme HashiCorp Vault ou AWS Secrets Manager, avec rotation automatique périodique des credentials. Pour une PME ouest-africaine en démarrage, le pattern .env suffit ; pour une organisation avec exigences de conformité plus fortes, basculer rapidement sur un coffre-fort centralisé.
Tester la délivrabilité avec mail-tester.com
Une fois votre formulaire en place avec SPF/DKIM/DMARC configurés, validez le score de délivrabilité avec un outil comme mail-tester.com. Le service vous donne une adresse email aléatoire à laquelle envoyer un email depuis votre formulaire, puis affiche un score sur 10 avec le détail des points perdus.
Un score 10/10 indique que l'email a passé tous les contrôles antispam standards (SpamAssassin, listes noires, signatures). Un score sous 8/10 signale un problème à corriger avant la mise en production : SPF mal configuré, DKIM cassé, contenu HTML mal formaté, ratio texte/image dégradé. Cet outil gratuit règle 90 % des problèmes de délivrabilité en quelques minutes — utilisez-le systématiquement avant de déployer un formulaire en production.
Pour les équipes qui veulent intégrer le formulaire de contact dans une application complète, basculer sur un framework moderne (Laravel, Symfony, Slim) facilite la gestion des credentials, des migrations DB et des tests automatisés autour de l'envoi d'email.
Sur le même thème
- Formulaire de contact HTML accessible
- Système de connexion PHP/MySQL
- Référence : GitHub — PHPMailer
- Sécurité : OWASP — CRLF / Header Injection
- SMTP gratuit : Brevo (300 emails/jour gratuits)