Sending Mail in Laravel and securing it with Google reCAPTCHA

Ahmed Mansoor
5 min readFeb 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.

  1. Generating necessary files: the Model, Migration, Controller, and Markdown Mailable
  2. Mail setup
  3. reCAPTCHA setup
  4. 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.

--

--