https://github.com/andreapollastri/newsletter
Open Source Newsletter System
https://github.com/andreapollastri/newsletter
api api-rest filament integration laravel mail marketing mcp mcp-server newsletter open-source php send smtp swagger
Last synced: 6 days ago
JSON representation
Open Source Newsletter System
- Host: GitHub
- URL: https://github.com/andreapollastri/newsletter
- Owner: andreapollastri
- License: mit
- Created: 2026-01-22T20:50:03.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-05-25T12:50:15.000Z (24 days ago)
- Last Synced: 2026-05-25T14:30:59.214Z (24 days ago)
- Topics: api, api-rest, filament, integration, laravel, mail, marketing, mcp, mcp-server, newsletter, open-source, php, send, smtp, swagger
- Language: PHP
- Homepage: https://newsletter.web.ap.it
- Size: 2.53 MB
- Stars: 7
- Watchers: 0
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
README
# Newsletter System
**Version 2.0.7** — [Changelog](CHANGELOG.md)
A complete newsletter management system for Laravel, built with Filament. Manage subscribers, campaigns, HTML templates, scheduled sending, and full tracking — all from a modern admin panel. Includes a REST API, OpenAPI documentation, and an MCP server for AI integrations.
---
## Table of Contents
- [Features](#features)
- [User Management & Roles](#user-management--roles)
- [Requirements](#requirements)
- [Installation](#installation)
- [Configuration](#configuration)
- [Quick Start](#quick-start)
- [Sending Newsletters](#sending-newsletters)
- [Monitoring & Analytics](#monitoring--analytics)
- [Rate Limiting](#rate-limiting)
- [REST API](#rest-api)
- [MCP Server (AI Integrations)](#mcp-server-ai-integrations)
- [Testing Tags](#testing-tags)
- [Multilingual Support](#multilingual-support)
- [Public Routes](#public-routes)
- [Scheduled Tasks](#scheduled-tasks)
- [Artisan Commands](#artisan-commands)
- [Troubleshooting](#troubleshooting)
---
## Features
| Feature | Description |
| ------------------------- | -------------------------------------------------------------------- |
| **Subscriber Management** | Import/export CSV, tagging, status management |
| **Campaigns & Messages** | Hierarchical organization of campaigns and messages |
| **HTML Templates** | Customizable templates with placeholder support |
| **Scheduled Sending** | Automatic delivery via cron |
| **Full Tracking** | Opens, clicks, and unsubscribe tracking |
| **Targeting** | Filter recipients by tags and status |
| **Dashboard** | Statistics and monitoring widgets |
| **Rate Limiting** | Configurable per-minute, per-hour, and per-day limits |
| **Bounce Detection** | IMAP integration for bounce processing |
| **REST API** | Full CRUD API with Sanctum authentication and OpenAPI docs |
| **MCP Server** | AI integration endpoint for Cursor, Claude Code, and other clients |
| **Testing Tags** | Mark tags as testing to exclude sends from production statistics |
| **Multilingual** | Admin panel in Italian, English, German, French, Spanish, Portuguese |
| **UTM Tracking** | Automatic UTM parameters on outbound newsletter links |
| **User Roles** | Three permission levels: Editor, Manager, Administrator |
---
## User Management & Roles
The application supports three user roles with progressively broader permissions. Every user is assigned exactly one role, stored in the `role` column of the `users` table.
### Roles
| Role | Description |
| ----------------- | ------------------------------------------------------------------------------------------------- |
| **Editor** | Focused on content creation. Can create and manage **draft** messages within existing campaigns. |
| **Manager** | Full operational access. Manages campaigns, subscribers, tags, templates, and all message states. |
| **Administrator** | Everything a Manager can do, plus **user management** and **API token management**. |
### Permission Matrix
| Area | Editor | Manager | Administrator |
| ------------------------------------- | :----: | :-----: | :-----------: |
| **Dashboard** | — | ✓ | ✓ |
| **Campaigns** (view) | ✓ | ✓ | ✓ |
| **Campaigns** (create/edit/delete) | — | ✓ | ✓ |
| **Messages** (create) | ✓ | ✓ | ✓ |
| **Messages** (view/edit/delete draft) | ✓ | ✓ | ✓ |
| **Messages** (view sent/sending) | — | ✓ | ✓ |
| **Subscribers** | — | ✓ | ✓ |
| **Tags** | — | ✓ | ✓ |
| **Templates** | — | ✓ | ✓ |
| **Users** | — | — | ✓ |
| **API Tokens** | — | — | ✓ |
| **Newsletter Report API** | — | ✓ | ✓ |
### Navigation
- **Editor** — lands on the **Campaigns** page after login; the dashboard is not shown in navigation.
- **Manager / Administrator** — lands on the **Dashboard**; all navigation items are visible according to the matrix above.
- **User menu** — the **API Tokens** and **Users** links appear only for Administrators.
### Managing Users
Administrators can create, edit, and delete users from the **Users** page in the admin panel. When creating or editing a user, the following fields are available:
- **Name** and **Email** (required)
- **Password** (required on create, optional on edit — leave blank to keep unchanged)
- **Role** — select Editor, Manager, or Administrator
- **Locale** — preferred language for the admin panel UI
An administrator cannot delete their own account.
### Migrations
When upgrading from a version without roles, two migrations handle the transition:
1. `add_role_to_users_table` — adds the `role` column (defaults to `editor`).
2. `set_all_existing_users_to_legacy_administrator_role` — promotes all pre-existing users to `administrator` so they retain full access.
---
## Requirements
- PHP 8.3+
- Laravel 13
- Filament 5
- Database (SQLite, MySQL, or PostgreSQL)
- Queue driver (database, Redis, etc.)
---
## Installation
1. **Clone the repository and install dependencies:**
```bash
git clone newsletter
cd newsletter
composer install
```
1. **Configure the environment:**
```bash
cp .env.example .env
php artisan key:generate
```
1. **Run migrations:**
```bash
php artisan migrate
```
1. **Build frontend assets:**
```bash
npm install
npm run build
```
1. **Create an admin user** (if not using seed data):
```bash
php artisan make:filament-user
```
1. **Generate API documentation** (optional):
```bash
php artisan l5-swagger:generate
```
---
## Configuration
### Mail
Configure your SMTP settings in `.env`:
```env
MAIL_MAILER=smtp
MAIL_HOST=your-smtp-host.com
MAIL_PORT=587
MAIL_USERNAME=your-username
MAIL_PASSWORD=your-password
MAIL_FROM_ADDRESS="newsletter@yourdomain.com"
MAIL_FROM_NAME="${APP_NAME}"
```
### Newsletter
| Variable | Description | Default |
| ---------------------------------- | ------------------------------------------- | ------- |
| `NEWSLETTER_TRACKING_ENABLED` | Enable open/click tracking | `true` |
| `NEWSLETTER_RATE_LIMIT_PER_MINUTE` | Max sends per rolling minute (`0` = no cap) | `0` |
| `NEWSLETTER_RATE_LIMIT_PER_HOUR` | Max sends per rolling hour (`0` = no cap) | `0` |
| `NEWSLETTER_RATE_LIMIT_PER_DAY` | Max sends per rolling day (`0` = no cap) | `0` |
See [Rate limiting](#rate-limiting) for behaviour and tuning.
### IMAP (Bounce Detection)
Optional configuration for processing bounced emails:
```env
NEWSLETTER_IMAP_HOST=imap.yourdomain.com
NEWSLETTER_IMAP_PORT=993
NEWSLETTER_IMAP_USERNAME=your-username
NEWSLETTER_IMAP_PASSWORD=your-password
NEWSLETTER_IMAP_ENCRYPTION=ssl
NEWSLETTER_IMAP_FOLDER=INBOX
```
### API Documentation (Swagger)
To auto-generate the OpenAPI spec on every request in local development:
```env
L5_SWAGGER_GENERATE_ALWAYS=true
```
For production, run `php artisan l5-swagger:generate` during deploy.
---
## Quick Start
1. **Seed sample data** (optional, recommended for testing):
```bash
php artisan newsletter:seed-data
```
1. **Start the queue worker:**
```bash
./start-worker.sh
```
Or manually:
```bash
php artisan queue:work --tries=3 --timeout=90
```
1. **Access the admin panel:**
- **URL:** `https://newsletter.test` (Laravel Herd) or `http://localhost:8000`
- **Default credentials** (after seeding): `admin@newsletter.test` / `password`
---
## Sending Newsletters
### Immediate Sending
1. Go to **Newsletter > Messages**
2. Create a new message or select an existing one
3. Set status to **Ready**
4. Click **Send Now**
5. Emails are queued and sent automatically via the queue worker
### Scheduled Sending
1. Create or edit a message
2. Set the **Scheduled Date** field
3. The system sends automatically at the scheduled time (requires cron — see [Scheduled Tasks](#scheduled-tasks))
---
## Monitoring & Analytics
- **Dashboard:** Main KPIs and send statistics
- **Messages:** Status and send counts per message
- **Message Details:** Individual tracking (opens, clicks) per recipient
---
## Rate Limiting
Configure how many emails the queue may send using rolling windows (stored in the app cache). Set a variable to **`0`** to turn off that cap; the other caps still apply.
| Env variable | Window | Typical use |
| ---------------------------------- | ------ | ------------------------------------------------------------------------- |
| `NEWSLETTER_RATE_LIMIT_PER_MINUTE` | ~1 min | Burst control; low values cause jobs to `release()` often (short delays). |
| `NEWSLETTER_RATE_LIMIT_PER_HOUR` | ~1 h | Provider hourly quotas. |
| `NEWSLETTER_RATE_LIMIT_PER_DAY` | ~24 h | Daily provider caps. |
**How sending works:** for each queued send, `SendNewsletterEmail` checks **daily**, then **hourly**, then **per-minute** (`App\Services\EmailRateLimiter`). The send proceeds only if it fits under every enabled limit; otherwise the job is **released** with a delay and no quota is consumed for that attempt.
**Throughput:** in practice the **tightest** of your enabled limits dominates (e.g. high hourly but low per-minute still throttles bursts).
**Estimated time:** while a message is in **Sending**, the UI uses the same three env-driven values to approximate completion (`Message::getEstimatedSendTime()`).
**Example** (adjust to your SMTP limits):
```env
NEWSLETTER_RATE_LIMIT_PER_MINUTE=0
NEWSLETTER_RATE_LIMIT_PER_HOUR=1000
NEWSLETTER_RATE_LIMIT_PER_DAY=10000
```
Keeping `PER_MINUTE=0` and using only hourly/daily is a common choice for large campaigns to avoid many small queue delays.
**Queue / worker:** ensure the worker `--timeout` stays a few seconds **below** your database queue `retry_after` (see `config/queue.php`, `DB_QUEUE_RETRY_AFTER`) so jobs are not processed twice.
**Check current counters:**
```bash
php artisan newsletter:rate-limits
```
---
## REST API
The application exposes a full REST API authenticated via **Laravel Sanctum** personal access tokens.
### Authentication
1. Go to the admin panel **user menu > API tokens** (or navigate to `/api-tokens`)
2. Create a token with the **API** ability enabled
3. Use the token in the `Authorization` header:
```
Authorization: Bearer
```
### Endpoints
All endpoints are prefixed with `/api` and require a valid Bearer token with the `api` ability.
| Method | Endpoint | Description |
| -------- | ---------------------------------------------- | ------------------------------ |
| `GET` | `/api/user` | Authenticated user info |
| `GET` | `/api/reports/newsletter` | Newsletter report (filterable) |
| `GET` | `/api/tags` | List all tags |
| `POST` | `/api/tags` | Create a tag |
| `GET` | `/api/tags/{tag}` | Show a tag |
| `PUT` | `/api/tags/{tag}` | Update a tag |
| `DELETE` | `/api/tags/{tag}` | Delete a tag |
| `GET` | `/api/subscribers` | List subscribers |
| `POST` | `/api/subscribers` | Create a subscriber |
| `GET` | `/api/subscribers/{subscriber}` | Show a subscriber |
| `PUT` | `/api/subscribers/{subscriber}` | Update a subscriber |
| `DELETE` | `/api/subscribers/{subscriber}` | Delete a subscriber |
| `GET` | `/api/templates` | List templates |
| `POST` | `/api/templates` | Create a template |
| `GET` | `/api/templates/{template}` | Show a template |
| `PUT` | `/api/templates/{template}` | Update a template |
| `DELETE` | `/api/templates/{template}` | Delete a template |
| `GET` | `/api/campaigns` | List campaigns |
| `POST` | `/api/campaigns` | Create a campaign |
| `GET` | `/api/campaigns/{campaign}` | Show a campaign |
| `PUT` | `/api/campaigns/{campaign}` | Update a campaign |
| `DELETE` | `/api/campaigns/{campaign}` | Delete a campaign |
| `GET` | `/api/campaigns/{campaign}/messages` | List messages in a campaign |
| `POST` | `/api/campaigns/{campaign}/messages` | Create a message |
| `GET` | `/api/campaigns/{campaign}/messages/{message}` | Show a message |
| `PUT` | `/api/campaigns/{campaign}/messages/{message}` | Update a message |
| `DELETE` | `/api/campaigns/{campaign}/messages/{message}` | Delete a message |
### OpenAPI Documentation
Interactive Swagger UI is available at `/api/documentation`. Generate or update the spec with:
```bash
php artisan l5-swagger:generate
```
---
## MCP Server (AI Integrations)
The application includes a **Model Context Protocol (MCP)** server that allows AI clients (Cursor, Claude Code, Windsurf, etc.) to interact with your newsletter data.
### Endpoint
```
/mcp/newsletter
```
Authenticated with a Sanctum Bearer token that has the **`mcp`** ability.
### Available Tools
| Tool | Description |
| ------------------------------ | ---------------------------------------------------------------------------------- |
| `list-campaigns` | Lists campaigns (all campaigns for managers/administrators; editors see their own) |
| `newsletter-report` | Delivery report with summary, per-message stats, and daily timeseries |
| `send-history-analysis` | Highlights and trends from recent send history |
| `subscriber-insights` | Audience breakdown by tags and statuses |
| `generate-email-template-html` | Generates responsive HTML email template from a description |
| `create-newsletter-message` | Creates a draft or ready message inside a campaign |
### Prompt
The server ships with a `newsletter-assistant` prompt — a reusable skill template that guides AI assistants through campaign planning, report interpretation, content drafting, and message creation.
### Setup in Your AI Client
1. Create a token in the admin panel with the **MCP** ability
2. Configure your AI client to connect to the MCP endpoint with the token as Bearer auth
3. Debug locally with: `php artisan mcp:inspector mcp/newsletter`
### VS Code / Cursor `mcp.json` example (HTTP)
Point the URL at your app’s origin plus `/mcp/newsletter`, and send the Sanctum token in the `Authorization` header:
```json
{
"mcpServers": {
"newsletter": {
"url": "https://your-domain.example/mcp/newsletter",
"headers": {
"Authorization": "Bearer YOUR_SANCTUM_TOKEN"
}
}
}
}
```
Replace `your-domain.example` with your deployment host (for local development, something like `http://127.0.0.1:8000/mcp/newsletter`). Replace `YOUR_SANCTUM_TOKEN` with the plaintext token string from the admin panel (treat it like a password).
### Example requests
You can phrase tasks in natural English; the assistant maps them to MCP tools. Examples:
- **Campaigns:** “List all newsletter campaigns.” / “What campaigns do we have?”
- **Delivery stats:** “Give me a newsletter delivery report from **2026-01-01** to **2026-01-31**.” / “Show sends, opens, clicks, and bounces for the last 30 days.”
- **Scoped report:** “Report for the campaign with id **…** between **2026-01-01** and **2026-01-31**.”
- **Narrative summary:** “Summarize send history and highlights for the last quarter.”
- **Audience:** “How many subscribers do we have by status? Which tags have the most subscribers?”
- **HTML:** “Generate a responsive HTML email template for a product launch: hero, short intro, three feature bullets, primary CTA button.”
- **New message:** “Create a **draft** message in the given campaign id, using the given template id, subject **Weekly digest**, HTML body **…**, and the listed tag ids.”
For **send statistics** and **campaign listing**, use a token belonging to a **Manager** or **Administrator** if your organization splits campaign ownership across users; those roles see all campaigns in reports (same idea as the admin panel).
---
## Testing Tags
Tags can be marked as **testing** (`is_testing` flag) in the Filament admin panel. When a message targets recipients **only** through testing tags:
- Sends are **excluded from dashboard statistics** (emails sent, opens, clicks, bounces, charts)
- After the send completes, per-recipient send rows and related bounces are **removed** from the database
- Testing tags and normal tags **cannot be mixed** on the same message
This allows safe end-to-end testing of the sending pipeline without polluting production metrics.
---
## Multilingual Support
The admin panel supports six languages: **Italian**, **English**, **German**, **French**, **Spanish**, and **Portuguese**.
- Logged-in users can choose their language from the **Profile** page
- Before login, the panel detects the browser `Accept-Language` header and uses the closest supported language (defaults to English)
- New users automatically inherit the browser language on account creation
---
## Public Routes
The following routes are available for public use:
| Route | Method | Description |
| ----------------------------------- | ------ | ------------------------------------ |
| `/subscribe` | GET | Subscription form |
| `/subscribe` | POST | Process subscription |
| `/subscribe/confirm/{token}` | GET | Confirm subscription (double opt-in) |
| `/unsubscribe/{subscriber}` | GET | Unsubscribe form |
| `/unsubscribe/{subscriber}/confirm` | POST | Confirm unsubscribe |
---
## Scheduled Tasks
Add the Laravel scheduler to your crontab:
```bash
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
```
| Task | Schedule | Description |
| ---------------------------- | ---------------- | ---------------------------------------------- |
| `newsletter:send-scheduled` | Every minute | Sends messages with scheduled date in the past |
| `newsletter:process-bounces` | Every 15 minutes | Processes bounced emails via IMAP |
| `backup:run` | Daily at 03:00 | Runs application backup |
| `backup:clean` | Daily at 04:00 | Cleans old backups |
---
## Artisan Commands
| Command | Description |
| ------------------------------ | ------------------------------------------------------------------ |
| `newsletter:seed-data` | Populate database with sample subscribers, campaigns, and messages |
| `newsletter:send-scheduled` | Manually trigger scheduled message sending |
| `newsletter:process-pending` | Process pending emails in the queue |
| `newsletter:process-bounces` | Process bounced emails from IMAP |
| `newsletter:rate-limits` | Display current rate limit status |
| `l5-swagger:generate` | Generate or update the OpenAPI specification |
| `mcp:inspector mcp/newsletter` | Debug the MCP server locally |
---
## Take it to production — cipi.sh
Once your app is ready, deploy it to any Ubuntu VPS with [cipi.sh](https://cipi.sh) — an open-source CLI built exclusively for Laravel.
- Full app isolation — own Linux user, PHP-FPM pool & database per app
- Zero-downtime deploys with instant rollback via Deployer
- Let's Encrypt SSL, Fail2ban, UFW firewall — all automated
- Multi-PHP (7.4 → 8.5), queue workers, S3 backups, auto-deploy webhooks
- AI Agent ready — any AI with SSH access can deploy & manage your server
```bash
wget -O - https://raw.githubusercontent.com/andreapollastri/cipi/refs/heads/latest/install.sh | bash
```
→ Learn more at [cipi.sh](https://cipi.sh)
---
## Troubleshooting
### Emails not sending
1. Ensure the queue worker is running: `./start-worker.sh` or `php artisan queue:work`
2. Manually process pending: `php artisan newsletter:process-pending`
3. Check the jobs table: `php artisan tinker --execute="DB::table('jobs')->count()"`
### Test email delivery
```bash
php artisan tinker --execute="Mail::raw('Test', fn(\$m) => \$m->to('test@example.com'))"
```
### Verify rate limits
```bash
php artisan newsletter:rate-limits
```
### Frontend changes not visible
Run `npm run build` or `npm run dev` to compile assets.
### API issues
1. Verify the token is valid: check the **API tokens** page in the admin panel
2. Ensure the token has the correct ability (`api` for REST endpoints, `mcp` for the MCP server)
3. Check the OpenAPI docs at `/api/documentation` for request/response format
---
## License
MIT
---
## Credits
Created by [Andrea Pollastri](https://web.ap.it)