Sending Mail in Laravel and securing it with Google reCAPTCHA
Ahmed Mansoor
Posted on February 9, 2023
I spent some time digging up the internet on how to send mail in Laravel and integrate reCaptcha to secure. Although the documentation is pretty straightforward, I wanted some samples and a step-by-step guide. So, here I’ll show you how to integrate reCAPTCHA into your Laravel application to enhance the security of your mail forms.
Note:
I’m using Laravel 9 and will be programmatically invoking the challenge when using reCAPTCHA v3. You may automatically bind the challenge to a button.
- Generating necessary files: the Model, Migration, Controller, and Markdown Mailable
- Mail setup
- reCAPTCHA setup
- Form view file
1. Generating necessary files: the Model, Migration, Controller, and Markdown Mailable
php artisan make:model ContactMail -mrc
php artisan make:mail ContactMail --markdown=emails.contact-mail
2. Mail setup
Add the mail host, port, address .etc to the .env file.
MAIL_MAILER=log
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=
MAIL_FROM_ADDRESS="email@email.com"
MAIL_FROM_NAME="${APP_NAME}"
ContactMail.php
Update the constructor.
public $data;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct($data)
{
$this->data = $data;
}
contact-mail.blade
<x-mail::message>
email: {{ $data->email }}
**{{ $data->subject }}**<br>
{{ $data->message }} <br>
</x-mail::message>
web.php (route)
Route::name('contact.')
->prefix('contact/')
->group(function () {
Route::get('', 'ContactMailController@index')->name('index');
Route::post('store', 'ContactMailController@store')->name('store');
});
3. reCAPTCHA setup
Register your reCAPTCHA v3 keys on the reCAPTCHA Admin console here. Add it to your .env file.
RECAPTCHA_SITE_KEY=<paste key here>
RECAPTCHA_SECRET_KEY=<paste key here>
ContactMailController.php
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return view('pages.contact.index');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
// form validation
$data = $request->all();
$rules = [
'email' => 'nullable|email',
'subject' => 'required',
'message' => 'required',
];
$validator = Validator::make($data, $rules);
// if form validation fails
if ($validator->fails()) {
return Response::json(array(
'validation' => false,
'message' => $validator->getMessageBag()->toArray()
), 200); // 400 invalid requests
}
// verify and get validation
$response = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [
'secret' => env('RECAPTCHA_SECRET_KEY'),
'response' => $request->recaptchaToken,
]);
$recaptchaResponse = $response->json();
// if captcha valid
if ($recaptchaResponse['success'] == true) {
ContactMail::create($request->all());
$message = [
'success' => true,
'message' => 'Thank you for taking the time to report your concerns.',
];
return response()->json($message, 200);
}
// if captcha invalid
elseif ($recaptchaResponse['success'] == false) {
$message = [
'success' => false,
'message' => 'You a robot?',
];
return response()->json($message, 200);
} else {
$recaptchaFail = 'Something went wrong.';
return response()->json($recaptchaFail, 200);
}
}
You got to import the necessary facades.
4. Form view file
contact.blade
<form id="contactForm" method="POST" action="{{ route('contact.store') }}" class="flex flex-col space-y-4">
{{ csrf_field() }}
<div class="row">
<div class="col-md-6 flex flex-col space-y-5">
<div class="flex flex-row w-full space-x-3 justify-between">
<!-- from Email -->
<div class="w-full col-md-6">
<div class="form-group flex flex-col space-y-2">
<label>From <small class="p-0.5 px-1 rounded-md bg-gray-100 text-gray-500">optional</small></label>
<input id="email" type="email" name="email" placeholder="Your email address" value{{old('email')}}"
class="hover:shadow bg-gray-50 border
border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary
focus:border-primary block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary
dark:focus:border-primary dark:shadow-sm-light">
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6 flex flex-col space-y-5">
<!-- subject -->
<div class="form-group space-y-2">
<label for="subject">Subject <small class="p-0.5 px-1 rounded-md bg-sky-100 text-sky-500">required</small></label>
<input type="text" id="subject" name="subject" value="{{ old('subject') }}"
class="hover:shadow bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary
focus:border-primary block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary
dark:focus:border-primary dark:shadow-sm-light">
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="form-group flex flex-col space-y-2">
<label>Message <small class="p-0.5 px-1 rounded-md bg-sky-100 text-sky-500">required</small></label>
<textarea id="message" name="message" rows="5" required class="hover:shadow bg-gray-50 border
border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary
focus:border-primary block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600
dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary
dark:focus:border-primary dark:shadow-sm-light">{{ old('message') }}</textarea>
</div>
</div>
</div>
<div id="success-message"></div>
<div class="form-group">
<button id="submit-button"
data-sitekey="{{env('RECAPTCHA_SITE_KEY')}}"
data-callback='onSubmit'
data-action='submit'
class="g-recaptcha btn-primary">
<span id="submit-text">Report</span>
</button>
</div>
</form>
<script>
function onSubmit(token) {
var bodyFormData = {
'email' : $('#email').val(),
'subject' : $('#subject').val(),
'message' : $('#message').val(),
'recaptchaToken': token,
};
axios({
method: "post",
url: "{{route('contact.store')}}",
data: bodyFormData,
})
.then(function (response) {
// if form validation fails
if (response.data.validation === false) {
let messages = response.data.message;
for (let key in messages) {
if (messages.hasOwnProperty(key)) {
let errorMessage = messages[key][0];
let formField = document.getElementById(key);
formField.classList.add('border-gray-300');
let errorElement = document.createElement('div');
errorElement.classList.add('text-sm','text-orange-500');
errorElement.innerHTML = errorMessage;
formField.parentNode.appendChild(errorElement);
}
}
}
// if ok
else if(response.data.success == true) {
var message = response.data.message;
var successMessage = "<div class='bg-primary bg-opacity-10 text-primary p-4 text-center rounded-lg'>" + message + "</div>";
$("#success-message").html(successMessage);
setTimeout(function() {
$('#success-message').delay(5000).fadeOut(1000);
}, 5000);
}
// if form validation fails
else if(response.data.success == false) {
var message = response.data.message;
var successMessage = "<div class='bg-orange-500 bg-opacity-10 text-orange-500 p-4 text-center rounded-lg'>" + message + "</div>";
$("#success-message").html(successMessage);
setTimeout(function() {
$('#success-message').delay(5000).fadeOut(1000);
}, 5000);
}
})
// if any other error
.catch(function (error) {
var message = 'Something went wrong.';
var successMessage = "<div class='bg-orange-500 bg-opacity-10 text-orange-500 p-4 text-center rounded-lg'>" + message + "</div>";
$("#success-message").html(successMessage);
setTimeout(function() {
$('#success-message').delay(5000).fadeOut(1000);
}, 5000);
});
}
</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
Integrating Google reCAPTCHA into your Laravel mail forms is an effective solution for enhancing the security of your web application. This guide provides a step-by-step approach for adding reCAPTCHA, making it easy for developers of all skill levels to send secure emails.
Posted on February 9, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.