Skip to content

Commit 050c60b

Browse files
initial commit
1 parent b0fa808 commit 050c60b

21 files changed

+970
-0
lines changed
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
{
2+
"info": {
3+
"name": "ESolution Laravel Email",
4+
"_postman_id": "laremail-2025-10-06",
5+
"description": "Postman collection for elgibor-solution/laravel-email",
6+
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
7+
},
8+
"item": [
9+
{
10+
"name": "Create Template",
11+
"request": {
12+
"method": "POST",
13+
"header": [
14+
{
15+
"key": "Content-Type",
16+
"value": "application/json"
17+
}
18+
],
19+
"url": {
20+
"raw": "{{base_url}}/laravel-email/templates",
21+
"host": [
22+
"{{base_url}}"
23+
],
24+
"path": [
25+
"laravel-email",
26+
"templates"
27+
]
28+
},
29+
"body": {
30+
"mode": "raw",
31+
"raw": "{\"name\": \"promo_sep\", \"subject\": \"Promo September\", \"html\": \"<p>Hi {{name}} {{tracking_pixel}}</p>\", \"text\": \"Hi {{name}}\", \"from_email\": \"[email protected]\", \"from_name\": \"Promo Team\"}"
32+
}
33+
}
34+
},
35+
{
36+
"name": "List Templates",
37+
"request": {
38+
"method": "GET",
39+
"url": {
40+
"raw": "{{base_url}}/laravel-email/templates",
41+
"host": [
42+
"{{base_url}}"
43+
],
44+
"path": [
45+
"laravel-email",
46+
"templates"
47+
]
48+
}
49+
}
50+
},
51+
{
52+
"name": "Create Broadcast",
53+
"request": {
54+
"method": "POST",
55+
"header": [
56+
{
57+
"key": "Content-Type",
58+
"value": "application/json"
59+
}
60+
],
61+
"url": {
62+
"raw": "{{base_url}}/laravel-email/broadcasts",
63+
"host": [
64+
"{{base_url}}"
65+
],
66+
"path": [
67+
"laravel-email",
68+
"broadcasts"
69+
]
70+
},
71+
"body": {
72+
"mode": "raw",
73+
"raw": "{\"name\": \"Promo 10.10\", \"template_id\": 1, \"provider_key\": \"sendgrid_1\"}"
74+
}
75+
}
76+
},
77+
{
78+
"name": "Add Recipients",
79+
"request": {
80+
"method": "POST",
81+
"header": [
82+
{
83+
"key": "Content-Type",
84+
"value": "application/json"
85+
}
86+
],
87+
"url": {
88+
"raw": "{{base_url}}/laravel-email/broadcasts/{{broadcast_id}}/recipients",
89+
"host": [
90+
"{{base_url}}"
91+
],
92+
"path": [
93+
"laravel-email",
94+
"broadcasts",
95+
"{{broadcast_id}}",
96+
"recipients"
97+
]
98+
},
99+
"body": {
100+
"mode": "raw",
101+
"raw": "{\"recipients\": [{\"email\": \"[email protected]\", \"name\": \"A\"}, {\"email\": \"[email protected]\", \"name\": \"B\"}]}"
102+
}
103+
}
104+
},
105+
{
106+
"name": "Start Broadcast",
107+
"request": {
108+
"method": "POST",
109+
"url": {
110+
"raw": "{{base_url}}/laravel-email/broadcasts/{{broadcast_id}}/start",
111+
"host": [
112+
"{{base_url}}"
113+
],
114+
"path": [
115+
"laravel-email",
116+
"broadcasts",
117+
"{{broadcast_id}}",
118+
"start"
119+
]
120+
}
121+
}
122+
},
123+
{
124+
"name": "SendGrid Webhook (test)",
125+
"request": {
126+
"method": "POST",
127+
"header": [
128+
{
129+
"key": "Content-Type",
130+
"value": "application/json"
131+
}
132+
],
133+
"url": {
134+
"raw": "{{base_url}}/laravel-email/webhook/sendgrid",
135+
"host": [
136+
"{{base_url}}"
137+
],
138+
"path": [
139+
"laravel-email",
140+
"webhook",
141+
"sendgrid"
142+
]
143+
},
144+
"body": {
145+
"mode": "raw",
146+
"raw": "[{\"event\": \"open\", \"broadcast_id\": 1, \"recipient_id\": 1, \"recipient_token\": \"{{recipient_token}}\", \"email\": \"[email protected]\"}]"
147+
}
148+
}
149+
},
150+
{
151+
"name": "List Suppressions",
152+
"request": {
153+
"method": "GET",
154+
"url": {
155+
"raw": "{{base_url}}/laravel-email/suppressions?q=",
156+
"host": [
157+
"{{base_url}}"
158+
],
159+
"path": [
160+
"laravel-email",
161+
"suppressions"
162+
],
163+
"query": [
164+
{
165+
"key": "q",
166+
"value": ""
167+
}
168+
]
169+
}
170+
}
171+
},
172+
{
173+
"name": "Add Suppression",
174+
"request": {
175+
"method": "POST",
176+
"header": [
177+
{
178+
"key": "Content-Type",
179+
"value": "application/json"
180+
}
181+
],
182+
"url": {
183+
"raw": "{{base_url}}/laravel-email/suppressions",
184+
"host": [
185+
"{{base_url}}"
186+
],
187+
"path": [
188+
"laravel-email",
189+
"suppressions"
190+
]
191+
},
192+
"body": {
193+
"mode": "raw",
194+
"raw": "{\"email\": \"[email protected]\", \"reason\": \"manual\"}"
195+
}
196+
}
197+
},
198+
{
199+
"name": "Delete Suppression",
200+
"request": {
201+
"method": "DELETE",
202+
"url": {
203+
"raw": "{{base_url}}/laravel-email/suppressions/{{suppression_id}}",
204+
"host": [
205+
"{{base_url}}"
206+
],
207+
"path": [
208+
"laravel-email",
209+
"suppressions",
210+
"{{suppression_id}}"
211+
]
212+
}
213+
}
214+
}
215+
],
216+
"variable": [
217+
{
218+
"key": "base_url",
219+
"value": "http://localhost"
220+
},
221+
{
222+
"key": "broadcast_id",
223+
"value": "1"
224+
},
225+
{
226+
"key": "recipient_token",
227+
"value": "REPLACE_ME"
228+
},
229+
{
230+
"key": "suppression_id",
231+
"value": "1"
232+
}
233+
]
234+
}

README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# ESolution Laravel Email (v1.1)
2+
3+
Package Laravel untuk broadcast email multi-provider (SendGrid-ready) dengan template, tracking event, **global suppression list**, dan optimasi deliverability.
4+
5+
## Install
6+
```bash
7+
composer require elgibor-solution/laravel-email
8+
php artisan vendor:publish --tag=laravel-email-config
9+
php artisan vendor:publish --tag=laravel-email-migrations
10+
php artisan migrate
11+
```
12+
Tambahkan ENV:
13+
```env
14+
SENDGRID_API_KEY=SG_xxx
15+
LAREMAIL_STRATEGY=round_robin
16+
LAREMAIL_DEFAULT=sendgrid_1
17+
LAREMAIL_RPM=600
18+
```
19+
Jalankan queue:
20+
```bash
21+
php artisan queue:work
22+
```
23+
24+
## Fitur
25+
- Multi-account provider (round-robin/fixed)
26+
- Driver **SendGrid** (bisa ditambah driver baru via interface)
27+
- Template dengan placeholder: `{{name}}`, `{{email}}`, `{{unsubscribe_url}}`, `{{tracking_pixel}}`
28+
- Broadcast + throttling berdasarkan RPM
29+
- Tracking event via webhook (open, bounce, unsubscribe, spamreport, dropped, ...)
30+
- **Global suppression list** (unsubscribe/bounce/spam/manual)
31+
- Auto `List-Unsubscribe` header + link unsubscribe per penerima
32+
33+
## Endpoint (prefix `/laravel-email`)
34+
- `POST /templates` — buat template
35+
- `GET /templates` — list template
36+
- `POST /broadcasts` — buat broadcast
37+
- `POST /broadcasts/{id}/recipients` — tambah penerima
38+
- `POST /broadcasts/{id}/start` — mulai kirim (queue + throttle)
39+
- `POST /webhook/sendgrid` — endpoint webhook
40+
- `GET /t/{token}` — tracking pixel 1×1
41+
- `GET /u/{token}` — unsubscribe
42+
- `GET /suppressions` — list suppression
43+
- `POST /suppressions` — tambah/update suppression
44+
- `DELETE /suppressions/{id}` — hapus suppression
45+
46+
## Global Suppression List
47+
- Tabel: `le_suppressions (email UNIQUE, reason ENUM)`
48+
- Otomatis terisi dari webhook: `unsubscribe`, `bounce/dropped` → reason=bounce, `spamreport` → reason=spam
49+
- Dicek sebelum pengiriman di `SendEmailJob` → email diskip jika ada di suppression list
50+
51+
## Tips Deliverability
52+
- Setup SPF/DKIM, warm-up domain/IP, kirim ke segment engaged, gunakan custom tracking domain jika ada.
53+
- Hindari spam trigger words, tambah plain-text version, dan pertahankan ratio teks/HTML yang sehat.
54+
55+
## Ekstensi Driver
56+
Implement `ESolution\LaravelEmail\Contracts\MailDriver`, daftarkan di `config/laravel_email.php``providers`.
57+
58+
## License
59+
MIT

composer.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "elgibor-solution/laravel-email",
3+
"description": "Laravel package for multi-provider broadcast email with SendGrid, templates, events tracking, suppression list, and anti-spam optimizations.",
4+
"type": "library",
5+
"license": "MIT",
6+
"authors": [
7+
{
8+
"name": "PT Elgibor Solusi Digital",
9+
"email": "[email protected]"
10+
}
11+
],
12+
"require": {
13+
"php": ">=8.1",
14+
"illuminate/support": "^10.0|^11.0|^12.0",
15+
"illuminate/http": "^10.0|^11.0|^12.0",
16+
"illuminate/queue": "^10.0|^11.0|^12.0",
17+
"illuminate/database": "^10.0|^11.0|^12.0",
18+
"sendgrid/sendgrid": "^8.1"
19+
},
20+
"autoload": {
21+
"psr-4": {
22+
"ESolution\\\\LaravelEmail\\\\": "src/"
23+
}
24+
},
25+
"extra": {
26+
"laravel": {
27+
"providers": [
28+
"ESolution\\\\LaravelEmail\\\\LaravelEmailServiceProvider"
29+
]
30+
}
31+
}
32+
}

config/laravel_email.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
return [
4+
'strategy' => env('LAREMAIL_STRATEGY', 'round_robin'), // round_robin|fixed
5+
'default_provider' => env('LAREMAIL_DEFAULT', 'sendgrid_1'),
6+
7+
'providers' => [
8+
'sendgrid_1' => [
9+
'driver' => 'sendgrid',
10+
'api_key' => env('SENDGRID_API_KEY'),
11+
'from_email' => env('MAIL_FROM_ADDRESS', '[email protected]'),
12+
'from_name' => env('MAIL_FROM_NAME', 'No Reply'),
13+
'sandbox_mode' => env('SENDGRID_SANDBOX', false),
14+
],
15+
],
16+
17+
'webhook' => [
18+
'route' => '/laravel-email/webhook/sendgrid',
19+
],
20+
21+
'routes' => [
22+
'track' => '/laravel-email/t/{token}',
23+
'unsubscribe' => '/laravel-email/u/{token}',
24+
],
25+
26+
'rate_limit_per_minute' => env('LAREMAIL_RPM', 600),
27+
'list_unsubscribe' => true,
28+
];

0 commit comments

Comments
 (0)