Introduction

In Laravel, sending multiple emails and attaching several documents is one of the key features in every application. Being able to write several lines of code and be able to send an email, has its own satisfaction. Well, Laravel just like any other framework today can do so. My aim in this project was to emulate what any email service provider today can do. For instance, be able to send an email, carbon copy (CC), have a subject and body, and attach several documents regardless of the type of the file (PDF, Image, SVG’s, etc).

See here to view my code. Image from cloudways.com.

Glossary

  • Composer – is a tool for dependency management in PHP. It is a great tool to have mostly since you can do a lot with it
  • Git – Git is a free and open-source distributed version control system designed to handle everything from small to very large projects with speed and efficiency. If you are using Windows git will help you like when you right-click you can open the terminal on any directory.
  • Laravel – Laravel is a web application framework with an expressive, elegant syntax. They’ve already laid the foundation — freeing you to create without sweating the small things.
  • PHP – PHP is a popular general-purpose scripting language especially suited for web development. Which is fast, flexible, and pragmatic. So, PHP powers everything from your blog to the most popular websites in the world.
  • XAMPP – XAMPP is a completely free, easy to install Apache distribution containing MariaDB, PHP, and Perl. Because XAMPP is an open source package, is set up to be incredibly easy to install and to use.
  • Browser – a computer program with a graphical user interface for displaying HTML files, it is used to navigate the World Wide Web.

NOTE: For this tutorial, I used a Xampp web server.

Step by Step Procedure

Setup

Laravel has its own server shipped with it. Since we are going to save all emails sent, we require a local server. Hence there is clear documentation online on how to install the above-mentioned software on their official sites depending on the operating system that you are using.

To check if you have installed xampp on:

Windows – Clicking the start button and search “XAMPP” xampp control panel is supposed to appear from your search history.

Linux – Run the following command. This command starts xampp web server

sudo /opt/lampp/lampp start

To check if you have composer installed, open your terminal and run the command, composer. The terminal outputs the following

______
/ ____/___ ____ ___ ____ ____ ________ _____
/ / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
/_/
Composer version 2.1.5 2021-07-23 10:35:47
Usage:
command [options] [arguments]
Options:
-h, --help Display this help message
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi Force ANSI output
--no-ansi Disable ANSI output
-n, --no-interaction Do not ask any interactive question
--profile Display timing and memory usage information
--no-plugins Whether to disable plugins.
-d, --working-dir=WORKING-DIR If specified, use the given directory as working directory.
--no-cache Prevent use of the cache
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
Available commands:
about Shows a short information about Composer.
archive Creates an archive of this composer package.
browse Opens the package's repository URL or homepage in your browser.
cc Clears composer's internal package cache.
check-platform-reqs Check that platform requirements are satisfied.
clear-cache Clears composer's internal package cache.
clearcache Clears composer's internal package cache.
config Sets config options.
create-project Creates new project from a package into given directory.
depends Shows which packages cause the given package to be installed.
diagnose Diagnoses the system to identify common errors.
dump-autoload Dumps the autoloader.
dumpautoload Dumps the autoloader.
exec Executes a vendored binary/script.
fund Discover how to help fund the maintenance of your dependencies.
global Allows running commands in the global composer dir ($COMPOSER_HOME).
help Displays help for a command
home Opens the package's repository URL or homepage in your browser.
i Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json.
info Shows information about packages.
init Creates a basic composer.json file in current directory.
install Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json.
licenses Shows information about licenses of dependencies.
list Lists commands
outdated Shows a list of installed packages that have updates available, including their latest version.
prohibits Shows which packages prevent the given package from being installed.
reinstall Uninstalls and reinstalls the given package names
remove Removes a package from the require or require-dev.
require Adds required packages to your composer.json and installs them.
run Runs the scripts defined in composer.json.
run-script Runs the scripts defined in composer.json.
search Searches for packages.
self-update Updates composer.phar to the latest version.
selfupdate Updates composer.phar to the latest version.
show Shows information about packages.
status Shows a list of locally modified packages.
suggests Shows package suggestions.
u Upgrades your dependencies to the latest version according to composer.json, and updates the composer.lock file.
update Upgrades your dependencies to the latest version according to composer.json, and updates the composer.lock file.
upgrade Upgrades your dependencies to the latest version according to composer.json, and updates the composer.lock file.
validate Validates a composer.json and composer.lock.
why Shows which packages cause the given package to be installed.
why-not Shows which packages prevent the given package from being installed.
view raw composer hosted with ❤ by GitHub

Once done with the setup, we will be able to code our small app sending multiple emails with attachments using laravel. Sending an email from your own application is always interesting but for beginners, it’s always a real pain. For beginners, they need to figure out what to loop, configuration, and all that stuff.

Step 1: Installing Laravel

To install laravel just run the following command. projectName is the name of the project/folder.

composer create-project laravel/laravel projectName

Once laravel has been installed you can change the directory to the project folder. To access the newly installed laravel run the following command

php artisan serve


Another way to access is by moving your project to the htdocs folder.

127.0.0.1/projectName/public/

Step 2: Creating a Model, Migration and Controller.

To create the above files you only need to run the below command. Once created, we are going to start with the controller.

php artisan make:model Attachment -mc

Step 3: Creating Database and Updating Migration

Before we get started we need to create a database and update our .env file. Once you are done, head over to the database/migration and open your newly created migration. Update it as follows:

<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateAttachmentsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('attachments', function (Blueprint $table) {
$table->id();
$table->string('email');
$table->string('cc');
$table->string('subject');
$table->string('message');
$table->text('attachments');
$table->string('send')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('attachments');
}
}

Once done run the following command

php artisan migrate. 

This creates all unmigrated migrations and pushes them to your database.

Step 4: Updating already created controller.

Once done head over to your controllers. Our first method inside our controller will be the one to access our view file. So it will look as follows

<?php
public function index()
{
$users = User::orderBy('id')->get();
$emails = Attachment::orderBy('id')->get();
return view('welcome', compact('users', 'emails'));
}
view raw indexmethod.php hosted with ❤ by GitHub

In the above method, we are returning the view welcome (which is shipped with laravel). The next step is to create a send method. Its code will be as follows. Since we are saving all emails send, the index method is returning our view file, all users saved and emails stored.

<?php
namespace App\Http\Controllers;
use App\Models\Attachment;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Mail;
class AttachmentController extends Controller
{
//
public function index()
{
$users = User::orderBy('id')->get();
$emails = Attachment::orderBy('id')->get();
return view('welcome', compact('users', 'emails'));
}
//
public function send(Request $request)
{
$data = request()->validate([
'email' => 'required',
'cc' => 'required',
'subject' => 'min:2|required',
'message' => 'required',
'attachments' => 'max:5060',
]);
$message = new Attachment();
$message->email = $data['email'];
$message->cc = $data['cc'];
$message->subject = $data['subject'];
$message->message = $data['message'];
foreach ($request->attachments as $file) {
$filename =$file->getClientOriginalName();
$path = $file->store('emails', 'public');
$message->attachments = $path;
}
$message->save();
if ($message) {
$count = 1;
foreach ($data['cc'] as $cc) {
$count++;
}
DB::update('update attachments set send=? where id=?', [$count, $message->id]);
}
$emailData = array(
'email' => $data['email'],
'cc' => $data['cc'],
'subject' => $data['subject'],
'message' => $data['message'],
'attachments' => $path,
);
view()->share(compact('emailData'));
$files = $request->attachments;
\Mail::send('mails', function ($message) use ($data, $file,$files, $path) {
$message->to($data['email']);
$message->cc($data['cc']);
$message->from(env('MAIL_FROM_ADDRESS'));
$message->subject($data['subject']);
foreach ($files as $f){
$message->attach(
$f->getRealPath(),array(
'as'=>$f->getClientOriginalName(),
'mime'=>$f->getMimeType(),
)
);
}
});
return redirect()->back();
}
}

The send method is the one I am using to send emails and store the data. First I am validating all inputs. Then I save the data. I wanted to count all emails that are being sent, and that is where the count is coming in. For each loop, it’s counting one. It will count all emails in the cc input. I had set the count to be one because of the primary email.

After saving, we are now able to send emails. The view()->share() we are making the variable $emailData available globally. In Laravel, we use the Mail::send function to send emails. To be able to achieve our main goal which is to send all emails with attachments that have been selected, the most important part is to make sure to loop through all files. You might encounter an error when trying to send. I had previously written a blog about how to address that error. In case you encounter click this link. The model file is a connection to your migration. In ORM(object-relational mapper), each model represents a table which we call migrations.

Step 5: Updating .env File

Your .env file is supposed to look as follows. We are mostly updating settings for mail. I have used google mail to do the task. For Google to be able to send email from a third party, you need to authorize it. To do so, on your Gmail profile and click manage your google account, head over to security, and then turn on less secure app access.

APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:SxrEf+ScYiqJt9eiQ8cUUCJGE3CvUD96XsZO7n40lQ0=
APP_DEBUG=true
APP_URL=http://localhost
LOG_CHANNEL=stack
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=multipleE
DB_USERNAME=root
DB_PASSWORD=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DRIVER=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_DRIVER=smtp
MAIL_HOST=smtp.googlemail.com
MAIL_PORT=465
MAIL_USERNAME=yourmail
MAIL_PASSWORD=yourmailpassword
MAIL_FROM_ADDRESS=yourmail
MAIL_FROM_NAME=yourmail/or random name
MAIL_ENCRYPTION=ssl
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
view raw .env hosted with ❤ by GitHub

Step 6: Updating our welcome view file.

On your welcome.blade.php file update it as follows.

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.15/js/bootstrap-multiselect.min.js">
</script>
<link rel = "stylesheet" href = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.15/css/bootstrap-multiselect.css"/>
<title>Mailer</title>
</head>
<body>
<div class="container">
<h2>Send Multiple Mails and Attachments</h2>
@if(count($errors)>0)
@foreach($errors->all() as $error)
<li class="" role="alert">
<strong>{{$error}}!</strong>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</li>
@endforeach
@endif
<div class="card">
<div class="card-body">
<button type="button" class="btn btn-primary btn-sm mb-3" data-toggle="modal" data-target="#sendEmail">
Send Email
</button>
<table class="table table-bordered table-stripped">
<thead>
<tr>
<th>#</th>
<th>Email</th>
<th>CC</th>
<th>Subject</th>
<th>Message</th>
<th>Files</th>
</tr>
</thead>
<tbody>
<?php $count=1?>
@foreach($emails as $email)
<tr>
<td>{{$count++}}</td>
<td>{{$email->email}}</td>
<td>
@foreach($email->cc as $cc)
<li style="list-style: none">{{$cc}}</li>
@endforeach
</td>
<td>{{$email->subject}}</td>
<td>{{$email->message}}</td>
<td>{{$email->message}}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="modal fade" id="sendEmail" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST" action="{{url('/send')}}" enctype="multipart/form-data">
@csrf
<div class="modal-body">
<div class="row">
<div class="form-group">
<label class="col-form-label">Email</label>
<select name="email" class="form-control">
@forelse($users as $user)
<option >{{$user->email}}</option>
@empty
<option >No User</option>
@endforelse
</select>
</div>
<div class="form-group">
<label class="col-form-label">CC</label>
<select id = "mltislct" name="cc[]" multiple = "multiple">
@forelse($users as $user)
<option >{{$user->email}}</option>
@empty
<option >No User</option>
@endforelse
</select>
</div>
<div class="form-group">
<label class="col-form-label">Subject</label>
<input type="text" class="form-control" name="subject">
</div>
<div class="form-group">
<label class="col-form-label">Message</label>
<textarea class="form-control" name="message"></textarea>
</div>
<div class="form-group">
<label class="col-form-label">Attachment</label>
<input type="file" class="form-control" name="attachments[]" multiple>
</div>
</div>
</div>
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">
Close
</button>
<button type="submit" class="btn btn-sm btn-success">
Send
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function() {
$('#mltislct').multiselect({
includeSelectAllOption: true,
enableFiltering: true,
enableCaseInsensitiveFiltering: true,
filterPlaceholder:'Search Here..'
});
});
</script>
</body>
</html>

Once done, create a mails.blade.php file since we require it to send to be able to send the emails. This view file will be just a simple HTML file. you can customize it to your liking though.

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Mailer</title>
</head>
<body>
{{$emailData->message}}
</body>
</html>
view raw mails.blade.php hosted with ❤ by GitHub

Step 7: Routes

Update your routes as follows. Note that, laravel 8 routes are very different from other versions of laravel

<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\AttachmentController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/',[AttachmentController::class,'index']);
Route::post('/send',[AttachmentController::class,'send']);
Auth::routes();
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
view raw web.php hosted with ❤ by GitHub

Step 8: Serving our application.

To serve Laravel just run the following command

php artisan serve

This creates a default port 8000 and creates a clickable link for you which is http://localhost:8000/. Once you click the link you will be able to access our application. It is supposed to look as follows.

Now you can send emails. Make sure your user’s table has some valid emails.

Learning Tools

There are a lot of learning tools you can use to equip you which may include;

  • Laravel Docs – The no 1 website stuck you supposed to reference first before searching elsewhere.
  • PHP – For any PHP related problem always refer here
  • Stack Overflow – This is a community of developers, you can find some answers and some may never work for you but its a good place to check out.

Leaning Strategy

To be able to complete this blog project, I used the above tools. I recommend the use of google search to the maximum level. If by any chance you’re experiencing errors it is okay to ask for help from colleagues or developers communities around the globe.

Reflective Analysis

Sending emails using laravel can sometimes be challenging if you’re a beginner. Once you completely understand how to do it, things become easier, and you become less of a beginner. If you are building that for production I would recommend you use cron jobs or queues. For more information, you can read on it on Laravel docs.

Conclusion

The above processes will show you how to attach multiple attachments, and carbon copy emails when sending an email to several users at once.