Laravel
Mail(메일)
짱구를왜말려?
2020. 11. 1. 19:12
반응형
SMALL
# .env 설정
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
# 메일 템플릿 생성
php artisan make:mail PasswordResetCreated --markdown=emails.passwordResets.created
@ PasswordReset.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class PasswordReset extends Model
{
use HasFactory;
protected $fillable = ["email", "token"];
protected $primaryKey = "email";
public $timestamps = false;
public function resetUrl()
{
return config("app.url")."/passwordResets/$this->token/edit";
}
}
@ PasswordResetCreated.php
<?php
namespace App\Mail;
use App\Models\PasswordReset;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class PasswordResetCreated extends Mailable
{
use Queueable, SerializesModels;
protected $receiver;
protected $passwordReset;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct(User $receiver, PasswordReset $passwordReset)
{
$this->receiver = $receiver;
$this->passwordReset = $passwordReset;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->markdown('emails.passwordResets.created')
->subject("[".config("app.name")."] ".__("socialLogin.passwordReset")["send"])
->with(["receiver" => $this->receiver, "passwordReset" => $this->passwordReset]);
}
}
# 메일 기본 템플릿 커스텀하기
php artisan vendor:publish --tag=laravel-mail
* html은 resources/views/vendor/mail/html의 layout, footer, header, message 등을 고치면 됨.
(내가 markdown으로 새로 만든 메일 view가 message.blade.php의 $slot 내용으로 들어가는거임)
* css는 resources/views/vendor/mail/html/themes/default.css를 수정
@ default.css
@charset "utf-8";
@import url(https://fonts.googleapis.com/earlyaccess/notosanskr.css);
html {margin:0;padding:0;}
body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,address,big,cite,code,del,dfn,em,font,img,ins,q,s,samp,small,strike,sub,sup,tt,var,b,u,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,input,textarea,select,button,
table,th,td,ul, li {margin:0;padding:0;border:0;font-size:16px; font-family: 'Noto Sans KR', sans-serif;color:#333;font-weight:normal;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0; box-sizing: border-box;}
header, footer, section, article, aside, nav, details, menu, figure, figcaption {display:block;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}
a:link , a:visited {text-decoration:none;}
a:hover,a:focus {text-decoration:none;}
*{-webkit-text-size-adjust:none;}
input,textarea,select,button{font-size:16px;font-family:'Noto Sans KR', sans-serif;}
img,fieldset {border:0;}
form,fieldset,input {margin:0;padding:0;}
em,address,optgroup {font-style:normal;font-weight:normal;}
button {border:none 0;margin:0;padding:0;overflow:visible;cursor:pointer;background:none;}
ol,ul,li {list-style:none;}
table {border-spacing:0;border-collapse:collapse;}
hr {display:none;}
legend {position:absolute;top:-1000px;left:-1000px;visibility:hidden;}
caption {height:0;line-height:0;font-size:0px;visibility:hidden;}
.skipNavi {position:absolute;left:0;top:0;z-index:999;width:100%;text-align:center;}
.skipNavi a {position:absolute;top:-999px;left:-999px;}
.skipNavi a:focus, .skipNavi a:active, .skipNavi a:hover {display:block;top:0;left:0;padding:7px 10px 5px;background:#000;color:#fff;font-weight:bold;font-size:16px;text-decoration:none;}
input[placeholder] { color:#999;}
::-webkit-input-placeholder { /* WebKit browsers */ color:#999;}
:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ color:#999;}
::-moz-placeholder {/* Mozilla Firefox 19+*/ color:#999;}
:-ms-input-placeholder {/* ie10+*/ color:#999;}
input {color:#333 !important;}
select {
cursor:pointer;
}
p,a,span, select, input {word-break:break-all;}
summary {display:none;}
@media screen and (max-width:500px){
a, p, span, button {font-size:14px;}
}
.wrap-mobile {width:800px; margin:100px auto; padding:40px; max-width:100%; background-color:#fafafa;}
.wrap-mobile .container {background-color:#fff;}
.header {padding:20px; background-color:#1C3351; text-align: center;}
.header .logo {font-size:24px; color:#fff; text-align: center; font-weight:600;}
.content {padding:40px;}
.content .title.type01 {margin-bottom:20px; font-size:18px; font-weight:500; text-align: center;}
.content .btn.type01 {display:inline-block; max-width:200px; margin:0 auto; margin-top:20px; padding:10px 20px; color:#fff; background-color:#1C3351; text-align: center;}
.footer {padding:40px; text-align: center;}
.footer p {font-size:14px; color:#999;}
.body.type01 {margin-bottom:10px;}
@ layout.blade.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<div class="wrap-mobile">
<div class="container">
{{ $header ?? '' }}
<div class="content">
{{ $slot }}
</div>
{{ $footer ?? '' }}
</div>
</div>
</body>
</html>
@ message.blade.php(여기의 $slot부분이 우리가 만든 메일 view 내용이 들어갈 부분)
@component('mail::layout')
{{-- Header --}}
@slot('header')
@component('mail::header', ['url' => config('app.url')])
{{ config('app.name') }}
@endcomponent
@endslot
{{-- Body --}}
{{ $slot }}
{{-- Footer --}}
@slot('footer')
@component('mail::footer')
© {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.')
@endcomponent
@endslot
@endcomponent
@ header.blade.php
<header class="header">
<a href="{{ config("app.url") }}">
<!-- <img src="/img/logo.png" alt=""> -->
{{config("app.name")}}
</a>
</header>
@ footer.blade.php
<footer class="footer">
{{ Illuminate\Mail\Markdown::parse($slot) }}
</footer>
@ views/passwordResets/created.blade.php(이 부분이 message.blade.php의 slot로 들어가는거임)
@component('mail::message')
<div id="resetPassword">
<h3 class="title type01">비밀번호 초기화</h3>
<p class="body type01">안녕하세요, {{$receiver ? $receiver->email : ""}}님,</p>
<p class="body type01">
{{config("app.name")}} 서비스 계정의 비밀번호를 초기화하시려면 비밀번호 초기화 버튼을 눌러주시기 바랍니다.
</p>
<div class="btns" style="text-align:center;">
<a href="{{$passwordReset ? $passwordReset->resetUrl() : ''}}" class="btn type01 width-100 bg-primary">비밀번호 초기화</a>
</div>
</div>
@endcomponent
# 메일 보내기
@ PasswordResetController.php
<?php
namespace ShinHyungJune\SocialLogin\Http;
use App\Mail\PasswordResetCreated;
use App\Models\PasswordReset;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Facades\Redirect;
use Inertia\Inertia;
use Laravel\Socialite\Facades\Socialite;
class PasswordResetController extends Controller
{
public function create()
{
return Inertia::render("PasswordResets/Create");
}
public function store(Request $request)
{
$request->validate([
"email" => "required|email|string|max:500"
]);
$message = "";
if(!User::where("email", $request->email)->exists()) {
$message = __("socialLogin.passwordReset.send_fail");
return Inertia::render("PasswordResets/Create", ["message" => $message]);
}
$token = random_int(100000000,999999999);
$passwordReset = PasswordReset::where("email", $request->email)->first();
$passwordReset ? $passwordReset->update([
"email" => $request->email,
"token" => $token
]) : $passwordReset = PasswordReset::create([
"email" => $request->email,
"token" => $token
]);
Mail::to($request->email)->send(new PasswordResetCreated(User::where("email", $request->email)->first(), $passwordReset));
$message = __("socialLogin.passwordReset.send_success");
return Inertia::render("PasswordResets/Create", ["message" => $message]);
}
public function edit(Request $request)
{
return Inertia::render("PasswordResets/Edit", [
"email" => $request->email,
"token" => $request->token
]);
}
public function update(Request $request)
{
$request->validate([
"email" => "required|email|max:500",
"token" => "required|string|max:5000",
"password" => "required|string|min:8|max:500|confirmed"
]);
$passwordReset = PasswordReset::where("email", $request->email)
->where("token", $request->token)
->first();
$user = User::where("email", $request->email)->first();
$message = __("socialLogin.passwordReset.reset_fail");
if($user && $passwordReset){
$user->update(["password" => Hash::make($request->password)]);
$message = __("socialLogin.passwordReset.reset_success");
}
return Inertia::render("PasswordResets/Edit", ["message" => $message]);
}
}
# 메일 미리 보기
@ web.php
<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| 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("/mailable", function(){
return (new \App\Mail\PasswordResetCreated(new \App\User(), new \App\PasswordReset()));
});
LIST