feat:E-Sign 서명 요청/리마인더 메일 발송 구현

- EsignRequestMail Mailable 클래스 추가 (리마인더 구분 지원)
- 이메일 HTML 템플릿 추가 (서명하기 버튼 + 계약 정보)
- send()에서 첫 번째 서명자에게 메일 발송
- remind()에서 다음 서명 대상자에게 리마인더 메일 발송

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
김보곤
2026-02-12 16:15:53 +09:00
parent 837fe42663
commit 9094a82f0a
3 changed files with 131 additions and 1 deletions

View File

@@ -3,12 +3,14 @@
namespace App\Http\Controllers\ESign;
use App\Http\Controllers\Controller;
use App\Mail\EsignRequestMail;
use App\Models\ESign\EsignContract;
use App\Models\ESign\EsignSigner;
use App\Models\ESign\EsignSignField;
use App\Models\ESign\EsignAuditLog;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
class EsignApiController extends Controller
@@ -256,10 +258,11 @@ public function send(Request $request, int $id): JsonResponse
'updated_by' => auth()->id(),
]);
// 첫 번째 서명자 상태 변경
// 첫 번째 서명자 상태 변경 + 메일 발송
$nextSigner = $contract->signers()->orderBy('sign_order')->first();
if ($nextSigner) {
$nextSigner->update(['status' => 'notified']);
Mail::to($nextSigner->email)->send(new EsignRequestMail($contract, $nextSigner));
}
EsignAuditLog::create([
@@ -295,6 +298,7 @@ public function remind(Request $request, int $id): JsonResponse
if ($nextSigner) {
$nextSigner->update(['status' => 'notified']);
Mail::to($nextSigner->email)->send(new EsignRequestMail($contract, $nextSigner, isReminder: true));
}
EsignAuditLog::create([

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Mail;
use App\Models\ESign\EsignContract;
use App\Models\ESign\EsignSigner;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class EsignRequestMail extends Mailable
{
use Queueable, SerializesModels;
public function __construct(
public EsignContract $contract,
public EsignSigner $signer,
public bool $isReminder = false,
) {}
public function envelope(): Envelope
{
$prefix = $this->isReminder ? '[리마인더] ' : '';
return new Envelope(
subject: "{$prefix}[SAM] 전자계약 서명 요청 - {$this->contract->title}",
);
}
public function content(): Content
{
$signUrl = config('app.url') . '/esign/sign/' . $this->signer->access_token;
return new Content(
html: 'emails.esign.request',
with: [
'contractTitle' => $this->contract->title,
'signerName' => $this->signer->name,
'signUrl' => $signUrl,
'expiresAt' => $this->contract->expires_at?->format('Y-m-d H:i'),
'isReminder' => $this->isReminder,
],
);
}
}

View File

@@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>전자계약 서명 요청</title>
</head>
<body style="margin: 0; padding: 0; background-color: #f5f5f5; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
<table width="100%" cellpadding="0" cellspacing="0" style="padding: 40px 20px;">
<tr>
<td align="center">
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
<!-- Header -->
<tr>
<td style="background-color: #2563eb; padding: 32px 40px; text-align: center;">
<h1 style="margin: 0; color: #ffffff; font-size: 24px; font-weight: 600;">
@if($isReminder ?? false)
[리마인더] 전자계약 서명 요청
@else
전자계약 서명 요청
@endif
</h1>
</td>
</tr>
<!-- Body -->
<tr>
<td style="padding: 40px;">
<p style="margin: 0 0 16px; font-size: 16px; color: #333;">안녕하세요, <strong>{{ $signerName }}</strong>.</p>
<p style="margin: 0 0 24px; font-size: 15px; color: #555; line-height: 1.6;">
@if($isReminder ?? false)
아래 전자계약에 대한 서명이 아직 완료되지 않았습니다.<br>
서명 기한 내에 서명을 완료해 주세요.
@else
아래 전자계약에 대한 서명이 요청되었습니다.<br>
링크를 클릭하여 서명을 진행해 주세요.
@endif
</p>
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f8fafc; border-radius: 6px; margin-bottom: 24px;">
<tr>
<td style="padding: 20px;">
<p style="margin: 0 0 8px; font-size: 13px; color: #888;">계약 제목</p>
<p style="margin: 0 0 16px; font-size: 16px; color: #333; font-weight: 600;">{{ $contractTitle }}</p>
<p style="margin: 0 0 8px; font-size: 13px; color: #888;">서명 기한</p>
<p style="margin: 0; font-size: 15px; color: #ef4444;">{{ $expiresAt }}</p>
</td>
</tr>
</table>
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td align="center" style="padding: 8px 0;">
<a href="{{ $signUrl }}" style="display: inline-block; background-color: #2563eb; color: #ffffff; text-decoration: none; padding: 14px 40px; border-radius: 6px; font-size: 16px; font-weight: 600;">
서명하기
</a>
</td>
</tr>
</table>
<p style="margin: 24px 0 0; font-size: 13px; color: #999; line-height: 1.6;">
버튼이 동작하지 않으면 아래 링크를 브라우저에 직접 입력해 주세요:<br>
<a href="{{ $signUrl }}" style="color: #2563eb; word-break: break-all;">{{ $signUrl }}</a>
</p>
</td>
</tr>
<!-- Footer -->
<tr>
<td style="background-color: #f8fafc; padding: 24px 40px; text-align: center; border-top: 1px solid #e5e7eb;">
<p style="margin: 0; font-size: 12px; color: #999;">
메일은 SAM 전자계약 시스템에서 자동 발송되었습니다.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>