Preserve an existing design
Target a form by CSS selector and let the generated JavaScript handle validation, loading state, submission messages and redirects without replacing the form markup or brand styling.
Connect an existing branded form without rebuilding it, or generate a complete form with a standalone PHP mail endpoint, bundled PHPMailer and optional reCAPTCHA.
These are the signals I would review manually when deciding what needs fixing first.
Review this area to understand whether it is helping or hurting growth.
Review this area to understand whether it is helping or hurting growth.
Review this area to understand whether it is helping or hurting growth.
Review this area to understand whether it is helping or hurting growth.
A little context makes the numbers more useful. Use these notes to understand where this tool fits inside content, SEO and website reviews.
Target a form by CSS selector and let the generated JavaScript handle validation, loading state, submission messages and redirects without replacing the form markup or brand styling.
The downloadable package includes a pinned PHPMailer distribution, PHP endpoint, configuration file, JavaScript handler and setup guide for shared hosting.
Use authenticated SMTP through PHPMailer for dependable delivery or select native PHP mail() when the host already provides a configured mail service.
Every package includes a honeypot, origin allowlist and server-side validation, with optional Google reCAPTCHA v2 or v3 verification.
Quick answers about how this tool works and when to use it.
No. The generated JavaScript attaches submission behavior to the CSS selector you provide. It keeps the existing HTML classes and styling, and only adds a small status element when needed.
No. The ZIP includes the required PHPMailer source files and loads them directly from PHP, so it can be uploaded to typical shared hosting without running Composer.
Package generation keeps the SMTP password in local browser state. If you explicitly test the SMTP connection or send a test email, the credentials are transmitted for that temporary request and are not persisted. Keep the downloaded configuration file private.
Yes. A class selector can match several forms, and a comma-separated selector can target different form classes on the same website.
Yes. Generate another package with reCAPTCHA v2 or v3 enabled, then add the public site key to the frontend and the secret key to the server configuration.
Connect an existing design or generate a complete starter form.
Complete the Mail, Security, and Email Template tabs. Click Download full package, then extract the ZIP on your computer.
Upload the complete contact-api folder without changing its internal PHPMailer structure. No separate JavaScript file is required.
public_html/
├── index.html
└── contact-api/
├── send.php
├── config.php
└── PHPMailer/Open the page containing your form, copy the complete script below, and paste it immediately before </body>. You do not need to change the existing form design.
The handler will connect every form matching .contact-form. Confirm those classes or IDs exist in the live page HTML. Inputs should have a name attribute whenever possible.
The current endpoint is /contact-api/send.php. If PHP is hosted on another domain or subfolder, enter its complete URL in the field above and keep the website origin allowed in Mail settings.
Open the deployed page over HTTPS, submit a test enquiry, confirm the success message or redirect, and verify that the email reaches the recipient inbox.
<script>
/* Static Contact Form Handler | generated by irahulsaini.com */
(function () {
"use strict";
var config = {
selector: ".contact-form",
endpoint: "/contact-api/send.php",
successMessage: "Thank you. Your message has been sent.",
errorMessage: "Sorry, the message could not be sent. Please try again.",
redirectUrl: "",
redirectDelay: 800,
resetOnSuccess: true,
recaptcha: "disabled",
recaptchaSiteKey: ""
};
function fieldKey(field, index) {
if (field.name) return field.name;
if (field.id) return field.id;
var label = field.closest("label") || document.querySelector('label[for="' + field.id + '"]');
var text = label ? label.textContent : field.placeholder;
return String(text || "field_" + (index + 1)).trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
}
function statusNode(form) {
var node = form.querySelector("[data-contact-form-status]");
if (!node) {
node = document.createElement("div");
node.setAttribute("data-contact-form-status", "");
node.setAttribute("role", "status");
node.setAttribute("aria-live", "polite");
node.style.marginTop = "12px";
form.appendChild(node);
}
return node;
}
function setBusy(form, busy) {
form.setAttribute("aria-busy", busy ? "true" : "false");
form.querySelectorAll('[type="submit"]').forEach(function (button) {
button.disabled = busy;
});
}
function formData(form) {
var data = new FormData();
form.querySelectorAll("input, textarea, select").forEach(function (field, index) {
if (field.disabled || ["submit", "button", "reset", "file"].indexOf(field.type) !== -1) return;
if ((field.type === "checkbox" || field.type === "radio") && !field.checked) return;
var key = fieldKey(field, index);
if (field.tagName === "SELECT" && field.multiple) {
Array.from(field.selectedOptions).forEach(function (option) { data.append(key, option.value); });
} else {
data.append(key, field.value);
}
});
data.append("_page", window.location.href);
return data;
}
function recaptchaToken() {
if (config.recaptcha !== "v3" || !window.grecaptcha || !config.recaptchaSiteKey) return Promise.resolve("");
return new Promise(function (resolve, reject) {
window.grecaptcha.ready(function () {
window.grecaptcha.execute(config.recaptchaSiteKey, { action: "contact_form" }).then(resolve).catch(reject);
});
});
}
function loadRecaptcha() {
if (config.recaptcha === "disabled" || !config.recaptchaSiteKey) return;
if (!document.querySelector('script[data-contact-recaptcha]')) {
var script = document.createElement("script");
script.src = "https://www.google.com/recaptcha/api.js" + (config.recaptcha === "v3" ? "?render=" + encodeURIComponent(config.recaptchaSiteKey) : "?render=explicit");
script.async = true;
script.defer = true;
script.dataset.contactRecaptcha = "true";
document.head.appendChild(script);
}
}
function mountCheckbox(form) {
if (config.recaptcha !== "v2" || form.querySelector(".g-recaptcha")) return;
var container = document.createElement("div");
container.className = "g-recaptcha";
container.dataset.sitekey = config.recaptchaSiteKey;
var submit = form.querySelector('[type="submit"]');
if (submit) submit.parentNode.insertBefore(container, submit);
else form.appendChild(container);
var attempts = 0;
var timer = window.setInterval(function () {
attempts += 1;
if (window.grecaptcha && window.grecaptcha.render) {
window.clearInterval(timer);
window.grecaptcha.render(container, { sitekey: config.recaptchaSiteKey });
} else if (attempts > 50) window.clearInterval(timer);
}, 100);
}
loadRecaptcha();
document.querySelectorAll(config.selector).forEach(function (form) {
if (form.dataset.contactHandlerReady) return;
form.dataset.contactHandlerReady = "true";
mountCheckbox(form);
form.addEventListener("submit", async function (event) {
event.preventDefault();
if (!form.checkValidity()) { form.reportValidity(); return; }
var status = statusNode(form);
status.textContent = "Sending...";
setBusy(form, true);
try {
var data = formData(form);
if (config.recaptcha === "v2" && !data.get("g-recaptcha-response")) throw new Error("Please complete the spam verification.");
var token = await recaptchaToken();
if (token) data.append("g-recaptcha-response", token);
var response = await fetch(config.endpoint, { method: "POST", body: data, headers: { "Accept": "application/json" } });
var result = await response.json().catch(function () { return {}; });
if (!response.ok || result.success !== true) throw new Error(result.message || config.errorMessage);
status.textContent = result.message || config.successMessage;
status.dataset.state = "success";
if (config.resetOnSuccess) form.reset();
form.dispatchEvent(new CustomEvent("contact-form:success", { detail: result }));
if (config.redirectUrl) window.setTimeout(function () { window.location.assign(config.redirectUrl); }, config.redirectDelay);
} catch (error) {
status.textContent = error.message || config.errorMessage;
status.dataset.state = "error";
form.dispatchEvent(new CustomEvent("contact-form:error", { detail: error }));
} finally {
setBusy(form, false);
}
});
});
})();
</script>