
In this article we will implement the contact form in our website, and we will use the mailtrap.io dummy mailing service to send and test email through Lumen.
Mailer Installation and Configuration In Lumen
Lumen not comes with mailer installed so you have to install and configure it yourself, at the beginning ensure that you have illuminate/mail package installed if not install the illuminate/mail with this command:
composer require illuminate/mail:7.x --dev
Depending upon your lumen version you should pick a specific version of illuminate/mail as shown here i picked 7.x because not any version is going to be installed and composer will ask you to upgrade other packages as well.
Next update the bootstrap/app.php to register the MailServiceProvider under $app->register(App\Providers\EventServiceProvider::class) like so:
$app->register(Illuminate\Mail\MailServiceProvider::class); $app->configure('mail'); $app->alias('mailer', Illuminate\Mail\Mailer::class); $app->alias('mailer', Illuminate\Contracts\Mail\Mailer::class); $app->alias('mailer', Illuminate\Contracts\Mail\MailQueue::class);
Â
Configuration
I will use mailtrap.io for testing my emails but for production deployment you should consider using real server configuration or email delivery so you haven’t used mailtrap before just go to https://mailtrap.io and register an account then mailtrap will create a demo mailbox for you for the free plan.
Next go to the demo mailbox in the integrations section choose laravel integration and copy these configuration and add it to the end of .env file as shown:
MAIL_MAILER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=add mailtrap username MAIL_PASSWORD=add mailtrap password MAIL_ENCRYPTION=tls
Laravel provides a config file for configuring email settings, this file doesn’t exist in lumen by default so you to create it. I created this file for you and added this code below:
config/mail.php
<?php return [ /* |-------------------------------------------------------------------------- | Default Mailer |-------------------------------------------------------------------------- | | This option controls the default mailer that is used to send any email | messages sent by your application. Alternative mailers may be setup | and used as needed; however, this mailer will be used by default. | */ 'default' => env('MAIL_MAILER', 'smtp'), /* |-------------------------------------------------------------------------- | Mailer Configurations |-------------------------------------------------------------------------- | | Here you may configure all of the mailers used by your application plus | their respective settings. Several examples have been configured for | you and you are free to add your own as your application requires. | | Laravel supports a variety of mail "transport" drivers to be used while | sending an e-mail. You will specify which one you are using for your | mailers below. You are free to add additional mailers as required. | | Supported: "smtp", "sendmail", "mailgun", "ses", | "postmark", "log", "array" | */ 'mailers' => [ 'smtp' => [ 'transport' => 'smtp', 'host' => env('MAIL_HOST', 'smtp.mailtrap.io'), 'port' => env('MAIL_PORT', 2525), 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 'username' => env('MAIL_USERNAME'), 'password' => env('MAIL_PASSWORD'), 'timeout' => null, 'auth_mode' => null, ], 'ses' => [ 'transport' => 'ses', ], 'mailgun' => [ 'transport' => 'mailgun', ], 'postmark' => [ 'transport' => 'postmark', ], 'sendmail' => [ 'transport' => 'sendmail', 'path' => '/usr/sbin/sendmail -bs', ], 'log' => [ 'transport' => 'log', 'channel' => env('MAIL_LOG_CHANNEL'), ], 'array' => [ 'transport' => 'array', ], ], /* |-------------------------------------------------------------------------- | Global "From" Address |-------------------------------------------------------------------------- | | You may wish for all e-mails sent by your application to be sent from | the same address. Here, you may specify a name and address that is | used globally for all e-mails that are sent by your application. | */ 'from' => [ 'address' => env('MAIL_FROM_ADDRESS', 'noreply@online-shop.com'), 'name' => env('MAIL_FROM_NAME', 'online shop'), ], /* |-------------------------------------------------------------------------- | Markdown Mail Settings |-------------------------------------------------------------------------- | | If you are using Markdown based email rendering, you may configure your | theme and component paths here, allowing you to customize the design | of the emails. Or, you may simply stick with the Laravel defaults! | */ 'markdown' => [ 'theme' => 'default', 'paths' => [ resource_path('views/vendor/mail'), ], ], ];
Mailer Class
Now after we configured the mailer, the next step to begin sending emails is to create a mailer class, this is a clean way in laravel to separate areas of concerns.
All mailer classes reside in the app/Mailer directory. If you don’t have this directory just create it, then add this simple mailer class for the contact form:
app/Mailer/ContactMailer.php
<?php namespace App\Mail; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; class ContactMailer extends Mailable { use SerializesModels; public $name; public $email; public $subject; public $body; public function __construct($name, $email, $subject, $message) { $this->name = $name; $this->email = $email; $this->subject = $subject; $this->body = $message; } public function build() { return $this->view("email.contact"); } }
Any mailer class extends from Illuminate\Mail\Mailable class which is a wrapper class that provides some methods to implement in order to send mails. The above class is the simplest class of the mailer, i implemented just one method which is the build() method, this method return the mail view that will be used when sending emails, we will create it below.
resources/email/contact.blade.php
<!doctype html> <html> <head> <meta name="viewport" content="width=device-width" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Contact Message From Online Shop</title> <style> /* ------------------------------------- GLOBAL RESETS ------------------------------------- */ /*All the styling goes here*/ img { border: none; -ms-interpolation-mode: bicubic; max-width: 100%; } body { background-color: #f6f6f6; font-family: sans-serif; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.4; margin: 0; padding: 0; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; } table { border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; } table td { font-family: sans-serif; font-size: 14px; vertical-align: top; } /* ------------------------------------- BODY & CONTAINER ------------------------------------- */ .body { background-color: #f6f6f6; width: 100%; } /* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */ .container { display: block; margin: 0 auto !important; /* makes it centered */ max-width: 580px; padding: 10px; width: 580px; } /* This should also be a block element, so that it will fill 100% of the .container */ .content { box-sizing: border-box; display: block; margin: 0 auto; max-width: 580px; padding: 10px; } /* ------------------------------------- HEADER, FOOTER, MAIN ------------------------------------- */ .main { background: #ffffff; border-radius: 3px; width: 100%; } .wrapper { box-sizing: border-box; padding: 20px; } .content-block { padding-bottom: 10px; padding-top: 10px; } .footer { clear: both; margin-top: 10px; text-align: center; width: 100%; } .footer td, .footer p, .footer span, .footer a { color: #999999; font-size: 12px; text-align: center; } /* ------------------------------------- TYPOGRAPHY ------------------------------------- */ h1, h2, h3, h4 { color: #000000; font-family: sans-serif; font-weight: 400; line-height: 1.4; margin: 0; margin-bottom: 30px; } h1 { font-size: 35px; font-weight: 300; text-align: center; text-transform: capitalize; } p, ul, ol { font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; margin-bottom: 15px; } p li, ul li, ol li { list-style-position: inside; margin-left: 5px; } a { color: #3498db; text-decoration: underline; } /* ------------------------------------- BUTTONS ------------------------------------- */ .btn { box-sizing: border-box; width: 100%; } .btn > tbody > tr > td { padding-bottom: 15px; } .btn table { width: auto; } .btn table td { background-color: #ffffff; border-radius: 5px; text-align: center; } .btn a { background-color: #ffffff; border: solid 1px #3498db; border-radius: 5px; box-sizing: border-box; color: #3498db; cursor: pointer; display: inline-block; font-size: 14px; font-weight: bold; margin: 0; padding: 12px 25px; text-decoration: none; text-transform: capitalize; } .btn-primary table td { background-color: #3498db; } .btn-primary a { background-color: #3498db; border-color: #3498db; color: #ffffff; } /* ------------------------------------- OTHER STYLES THAT MIGHT BE USEFUL ------------------------------------- */ .last { margin-bottom: 0; } .first { margin-top: 0; } .align-center { text-align: center; } .align-right { text-align: right; } .align-left { text-align: left; } .clear { clear: both; } .mt0 { margin-top: 0; } .mb0 { margin-bottom: 0; } .preheader { color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0; } .powered-by a { text-decoration: none; } hr { border: 0; border-bottom: 1px solid #f6f6f6; margin: 20px 0; } /* ------------------------------------- RESPONSIVE AND MOBILE FRIENDLY STYLES ------------------------------------- */ @media only screen and (max-width: 620px) { table[class=body] h1 { font-size: 28px !important; margin-bottom: 10px !important; } table[class=body] p, table[class=body] ul, table[class=body] ol, table[class=body] td, table[class=body] span, table[class=body] a { font-size: 16px !important; } table[class=body] .wrapper, table[class=body] .article { padding: 10px !important; } table[class=body] .content { padding: 0 !important; } table[class=body] .container { padding: 0 !important; width: 100% !important; } table[class=body] .main { border-left-width: 0 !important; border-radius: 0 !important; border-right-width: 0 !important; } table[class=body] .btn table { width: 100% !important; } table[class=body] .btn a { width: 100% !important; } table[class=body] .img-responsive { height: auto !important; max-width: 100% !important; width: auto !important; } } /* ------------------------------------- PRESERVE THESE STYLES IN THE HEAD ------------------------------------- */ @media all { .ExternalClass { width: 100%; } .ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div { line-height: 100%; } .apple-link a { color: inherit !important; font-family: inherit !important; font-size: inherit !important; font-weight: inherit !important; line-height: inherit !important; text-decoration: none !important; } #MessageViewBody a { color: inherit; text-decoration: none; font-size: inherit; font-family: inherit; font-weight: inherit; line-height: inherit; } .btn-primary table td:hover { background-color: #34495e !important; } .btn-primary a:hover { background-color: #34495e !important; border-color: #34495e !important; } } </style> </head> <body class=""> <span class="preheader">This is preheader text. Some clients will show this text as a preview.</span> <table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body"> <tr> <td> </td> <td class="container"> <div class="content"> <!-- START CENTERED WHITE CONTAINER --> <table role="presentation" class="main"> <!-- START MAIN CONTENT AREA --> <tr> <td class="wrapper"> <table role="presentation" border="0" cellpadding="0" cellspacing="0"> <tr> <td> <p>Hi there,</p> <p>Someone just send this contact message with the below details.</p> <table role="presentation" border="0" cellpadding="0" cellspacing="0" class="btn btn-primary"> <tbody> <tr> <td align="left"> <table role="presentation" border="0" cellpadding="0" cellspacing="0"> <tbody> <tr><td>Username: </td><td style="background-color: #fff">{{ $name }}</td></tr> <tr><td>Email: </td><td style="background-color: #fff">{{ $email }}</td></tr> <tr><td>Subject: </td><td style="background-color: #fff">{{ $subject }}</td></tr> <tr><td>Message: </td><td style="background-color: #fff">{{ $body }}</td></tr> </tbody> </table> </td> </tr> </tbody> </table> <p>Best Regards,</p> </td> </tr> </table> </td> </tr> <!-- END MAIN CONTENT AREA --> </table> <!-- END CENTERED WHITE CONTAINER --> <!-- START FOOTER --> <div class="footer"> <table role="presentation" border="0" cellpadding="0" cellspacing="0"> <tr> <td class="content-block"> <span class="apple-link">Online Shop</span> </td> </tr> </table> </div> <!-- END FOOTER --> </div> </td> <td> </td> </tr> </table> </body> </html>
This a simple html email template and i printed the variables like $name, $email from the mailer class because these properties marked as public so we can reference and access them without sending them with the view() method.
Now let’s create the Contact Controller.
Create a new controller ContactController.php
app/Http/Controllers/ContactController.php
<?php namespace App\Http\Controllers; use App\Mail\ContactMailer; use Illuminate\Http\Request; use Illuminate\Support\Facades\Mail; class ContactController extends Controller { public function store(Request $request) { $name = $request->input("name"); $email = $request->input("email"); $subject = $request->input("subject"); $message = $request->input("message"); Mail::to("noreply@onlineshop.com")->send(new ContactMailer($name, $email, $subject, $message)); return response()->json(["message" => "Message sent successfully and we will reply to you as soon as possible"]); } }
The bove controller contains the store() method but this method not storing just sending the email message. You can add a mysql table to store the email messages if you want. As you see i used laravel Mail facade and invoked the Mail::to() method and provided a dummy email address. Next in the send() method i supplied our mailer class ContactMailer() and passed the $name, $email, $subject, and $message params.
After we created the controller let’s update the routes to include the contact route.
routes/web.php
<?php /* |-------------------------------------------------------------------------- | Application Routes |-------------------------------------------------------------------------- | | Here is where you can register all of the routes for an application. | It is a breeze. Simply tell Lumen the URIs it should respond to | and give it the Closure to call when that URI is requested. | */ $router->get('/', function () use ($router) { return $router->app->version(); }); $router->group(['prefix' => 'api'], function () use ($router) { $router->post('/login', 'Auth\\LoginController@login'); $router->post('/register', 'Auth\\RegisterController@register'); $router->group(['prefix' => 'category'], function () use ($router) { $router->get('/', 'CategoriesController@index'); $router->get('/htmltree', 'CategoriesController@getCategoryHtmlTree'); $router->get('/menutree', 'CategoriesController@getCategoryMenuHtmlTree'); $router->get('/featured-categories', 'CategoriesController@featuredCategories'); $router->get('/{id}', 'CategoriesController@show'); }); $router->group(['prefix' => 'brand'], function () use ($router) { $router->get('/', 'BrandsController@index'); $router->get('/brands-by-category', 'BrandsController@brandsByCategory'); $router->get('/{id}', 'BrandsController@show'); }); $router->group(['prefix' => 'product'], function () use ($router) { $router->get('/', 'ProductsController@index'); $router->get('/slider-products', 'ProductsController@sliderProducts'); $router->get('/latest-products', 'ProductsController@latestProducts'); $router->get('/featured-products', 'ProductsController@featuredProducts'); $router->get('/search-products', 'ProductsController@searchProducts'); $router->get('/products-by-ids', 'ProductsController@productsByIds'); $router->get('/{id}', 'ProductsController@show'); }); $router->group(['prefix' => 'user'], function () use ($router) { $router->get('/', 'UsersController@index'); $router->get('/{id}', 'UsersController@show'); }); $router->group(['prefix' => 'contact'], function () use ($router) { $router->post('/', 'ContactController@store'); }); $router->group(['middleware' => 'auth:api'], function () use ($router) { $router->get('/me', 'Auth\\LoginController@userDetails'); $router->get('/logout', 'Auth\\LoginController@logout'); $router->get('/check-login', 'Auth\\LoginController@checkLogin'); $router->post('/update-profile', 'Auth\\LoginController@updateProfile'); $router->group(['prefix' => 'category'], function () use ($router) { $router->post('/', 'CategoriesController@store'); $router->put('/{id}', 'CategoriesController@update'); $router->delete('/{id}', 'CategoriesController@destroy'); }); $router->group(['prefix' => 'brand'], function () use ($router) { $router->post('/', 'BrandsController@store'); $router->put('/{id}', 'BrandsController@update'); $router->delete('/{id}', 'BrandsController@destroy'); }); $router->group(['prefix' => 'product'], function () use ($router) { $router->post('/', 'ProductsController@store'); $router->put('/{id}', 'ProductsController@update'); $router->delete('/delete-image/{id}', 'ProductsController@destroyImage'); $router->delete('/{id}', 'ProductsController@destroy'); }); $router->group(['prefix' => 'user'], function () use ($router) { $router->post('/', 'UsersController@store'); $router->put('/{id}', 'UsersController@update'); $router->delete('/{id}', 'UsersController@destroy'); }); $router->group(['prefix' => 'cart'], function () use ($router) { $router->get('/', 'ShoppingCartController@index'); $router->post('/', 'ShoppingCartController@store'); $router->put('/', 'ShoppingCartController@update'); $router->get('/{id}', 'ShoppingCartController@show'); $router->delete('/clearAll', 'ShoppingCartController@clearAll'); $router->delete('/{id}', 'ShoppingCartController@destroy'); }); $router->group(['prefix' => 'shippingAddress'], function () use ($router) { $router->get('/', 'ShippingAddressesController@index'); $router->post('/', 'ShippingAddressesController@store'); $router->get('/{id}', 'ShippingAddressesController@show'); $router->put('/{id}', 'ShippingAddressesController@update'); $router->delete('/{id}', 'ShippingAddressesController@destroy'); }); $router->group(['prefix' => 'paymentMethods'], function () use ($router) { $router->get('/', 'PaymentMethodsController@index'); }); $router->group(['prefix' => 'orders'], function () use($router) { $router->get('/', 'OrdersController@index'); $router->post('/', 'OrdersController@store'); $router->get('/latest-pending-orders', 'OrdersController@getLatestPendingOrders'); $router->get('/{id}', 'OrdersController@show'); $router->put('/{id}', 'OrdersController@update'); }); }); });
Now the server part is complete, let’s switch to Nuxt project and add the required code for the contact form.
Updating The Contact Form
Open the Nuxt project and go to the pages/ directory, then open the contactus.vue and update it with this code:
<template> <div id="contact-page" class="container"> <div class="bg"> <div class="row"> <div class="col-sm-12"> <h2 class="title text-center">Contact <strong>Us</strong></h2> </div> </div> <div class="row"> <div class="col-sm-12"> <div class="contact-form"> <div class="status alert alert-success" v-if="this.success_message !== ''">{{ this.success_message }}</div> <div v-if="this.errors.length > 0" class="alert alert-danger"> <p>Please fix these errors</p><br/> <ul style="padding: 0"> <li v-for="error in this.errors" :key="error">{{ error }}</li> </ul> </div> <form id="main-contact-form" class="contact-form row" name="contact-form" method="post" @submit.prevent="submit()"> <div class="form-group col-md-6"> <input type="text" name="name" class="form-control" placeholder="Name" v-model="name"> </div> <div class="form-group col-md-6"> <input type="email" name="email" class="form-control" placeholder="Email" v-model="email"> </div> <div class="form-group col-md-12"> <input type="text" name="subject" class="form-control" placeholder="Subject" v-model="subject"> </div> <div class="form-group col-md-12"> <textarea name="message" id="message" class="form-control" rows="8" placeholder="Your Message Here" v-model="message"></textarea> </div> <div class="form-group col-md-12"> <input type="submit" name="submit" class="btn btn-primary pull-right" value="Submit" > </div> </form> </div> </div> </div> </div> </div> </template> <script> export default { name: "Contactus", data() { return { name: "", email: "", subject: "", message: "", errors: [], success_message: "" } }, head() { return { title: 'Online Shop | Contact us', meta: [ { hid: 'description', name: 'description', content: 'Contact us Page' } ] } }, methods: { submit() { const errors = []; if(this.name === "") { errors.push("name required"); } if(this.email === "") { errors.push("email required"); } if(this.subject === "") { errors.push("subject required"); } if(this.message === "") { errors.push("message contents required"); } if(errors.length > 0) { this.errors = errors; return false; } this.errors = []; const data = { name: this.name, email: this.email, subject: this.subject, message: this.message }; this.$axios.$post("/api/contact", data).then(res => { this.success_message = res.message; this.name = ""; this.email = ""; this.subject = ""; this.message = ""; }).catch(err => { console.error(err); }); } } } </script>
I cleaned the contact page and updated the form. Then i binded the form fields using v-model directive which bind data from the data() method with attributes like name, email, subject, message.
Next i added the submit() method triggered on form submit and submit the data to the server. In the submit() first i validate the form fields, if there is errors it will be displayed in the top of the template. If the data is valid it will posted the data to the server using $axios. In successful response i display a success message and clear the form fields.
Now you can try to fill the form and check the mailtrap mailbox for messages.
In a real world project you should check for message delivery and show error message if that happen.
Sometimes it’s better to use postman to test the contact endpoint to check if there is server error before testing in Nuxtjs
Continue to Part 24: Finishing