{"id":50791248,"url":"https://github.com/andreapollastri/newsletter","last_synced_at":"2026-06-12T11:02:16.973Z","repository":{"id":344386809,"uuid":"1140107332","full_name":"andreapollastri/newsletter","owner":"andreapollastri","description":"Open Source Newsletter System","archived":false,"fork":false,"pushed_at":"2026-05-25T12:50:15.000Z","size":2650,"stargazers_count":7,"open_issues_count":0,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-25T14:30:59.214Z","etag":null,"topics":["api","api-rest","filament","integration","laravel","mail","marketing","mcp","mcp-server","newsletter","open-source","php","send","smtp","swagger"],"latest_commit_sha":null,"homepage":"https://newsletter.web.ap.it","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andreapollastri.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-01-22T20:50:03.000Z","updated_at":"2026-05-25T12:50:19.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/andreapollastri/newsletter","commit_stats":null,"previous_names":["andreapollastri/newsletter"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/andreapollastri/newsletter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreapollastri%2Fnewsletter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreapollastri%2Fnewsletter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreapollastri%2Fnewsletter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreapollastri%2Fnewsletter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andreapollastri","download_url":"https://codeload.github.com/andreapollastri/newsletter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreapollastri%2Fnewsletter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34240817,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-12T02:00:06.859Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","api-rest","filament","integration","laravel","mail","marketing","mcp","mcp-server","newsletter","open-source","php","send","smtp","swagger"],"created_at":"2026-06-12T11:02:15.919Z","updated_at":"2026-06-12T11:02:16.967Z","avatar_url":"https://github.com/andreapollastri.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Newsletter System\n\n**Version 2.0.7** — [Changelog](CHANGELOG.md)\n\nA 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.\n\n---\n\n## Table of Contents\n\n- [Features](#features)\n- [User Management \u0026 Roles](#user-management--roles)\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Configuration](#configuration)\n- [Quick Start](#quick-start)\n- [Sending Newsletters](#sending-newsletters)\n- [Monitoring \u0026 Analytics](#monitoring--analytics)\n- [Rate Limiting](#rate-limiting)\n- [REST API](#rest-api)\n- [MCP Server (AI Integrations)](#mcp-server-ai-integrations)\n- [Testing Tags](#testing-tags)\n- [Multilingual Support](#multilingual-support)\n- [Public Routes](#public-routes)\n- [Scheduled Tasks](#scheduled-tasks)\n- [Artisan Commands](#artisan-commands)\n- [Troubleshooting](#troubleshooting)\n\n---\n\n## Features\n\n| Feature                   | Description                                                          |\n| ------------------------- | -------------------------------------------------------------------- |\n| **Subscriber Management** | Import/export CSV, tagging, status management                        |\n| **Campaigns \u0026 Messages**  | Hierarchical organization of campaigns and messages                  |\n| **HTML Templates**        | Customizable templates with placeholder support                      |\n| **Scheduled Sending**     | Automatic delivery via cron                                          |\n| **Full Tracking**         | Opens, clicks, and unsubscribe tracking                              |\n| **Targeting**             | Filter recipients by tags and status                                 |\n| **Dashboard**             | Statistics and monitoring widgets                                    |\n| **Rate Limiting**         | Configurable per-minute, per-hour, and per-day limits                |\n| **Bounce Detection**      | IMAP integration for bounce processing                               |\n| **REST API**              | Full CRUD API with Sanctum authentication and OpenAPI docs           |\n| **MCP Server**            | AI integration endpoint for Cursor, Claude Code, and other clients   |\n| **Testing Tags**          | Mark tags as testing to exclude sends from production statistics     |\n| **Multilingual**          | Admin panel in Italian, English, German, French, Spanish, Portuguese |\n| **UTM Tracking**          | Automatic UTM parameters on outbound newsletter links                |\n| **User Roles**            | Three permission levels: Editor, Manager, Administrator              |\n\n---\n\n## User Management \u0026 Roles\n\nThe 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.\n\n### Roles\n\n| Role              | Description                                                                                       |\n| ----------------- | ------------------------------------------------------------------------------------------------- |\n| **Editor**        | Focused on content creation. Can create and manage **draft** messages within existing campaigns.  |\n| **Manager**       | Full operational access. Manages campaigns, subscribers, tags, templates, and all message states. |\n| **Administrator** | Everything a Manager can do, plus **user management** and **API token management**.               |\n\n### Permission Matrix\n\n| Area                                  | Editor | Manager | Administrator |\n| ------------------------------------- | :----: | :-----: | :-----------: |\n| **Dashboard**                         |   —    |    ✓    |       ✓       |\n| **Campaigns** (view)                  |   ✓    |    ✓    |       ✓       |\n| **Campaigns** (create/edit/delete)    |   —    |    ✓    |       ✓       |\n| **Messages** (create)                 |   ✓    |    ✓    |       ✓       |\n| **Messages** (view/edit/delete draft) |   ✓    |    ✓    |       ✓       |\n| **Messages** (view sent/sending)      |   —    |    ✓    |       ✓       |\n| **Subscribers**                       |   —    |    ✓    |       ✓       |\n| **Tags**                              |   —    |    ✓    |       ✓       |\n| **Templates**                         |   —    |    ✓    |       ✓       |\n| **Users**                             |   —    |    —    |       ✓       |\n| **API Tokens**                        |   —    |    —    |       ✓       |\n| **Newsletter Report API**             |   —    |    ✓    |       ✓       |\n\n### Navigation\n\n- **Editor** — lands on the **Campaigns** page after login; the dashboard is not shown in navigation.\n- **Manager / Administrator** — lands on the **Dashboard**; all navigation items are visible according to the matrix above.\n- **User menu** — the **API Tokens** and **Users** links appear only for Administrators.\n\n### Managing Users\n\nAdministrators 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:\n\n- **Name** and **Email** (required)\n- **Password** (required on create, optional on edit — leave blank to keep unchanged)\n- **Role** — select Editor, Manager, or Administrator\n- **Locale** — preferred language for the admin panel UI\n\nAn administrator cannot delete their own account.\n\n### Migrations\n\nWhen upgrading from a version without roles, two migrations handle the transition:\n\n1. `add_role_to_users_table` — adds the `role` column (defaults to `editor`).\n2. `set_all_existing_users_to_legacy_administrator_role` — promotes all pre-existing users to `administrator` so they retain full access.\n\n---\n\n## Requirements\n\n- PHP 8.3+\n- Laravel 13\n- Filament 5\n- Database (SQLite, MySQL, or PostgreSQL)\n- Queue driver (database, Redis, etc.)\n\n---\n\n## Installation\n\n1. **Clone the repository and install dependencies:**\n\n```bash\ngit clone \u003crepository-url\u003e newsletter\ncd newsletter\ncomposer install\n```\n\n1. **Configure the environment:**\n\n```bash\ncp .env.example .env\nphp artisan key:generate\n```\n\n1. **Run migrations:**\n\n```bash\nphp artisan migrate\n```\n\n1. **Build frontend assets:**\n\n```bash\nnpm install\nnpm run build\n```\n\n1. **Create an admin user** (if not using seed data):\n\n```bash\nphp artisan make:filament-user\n```\n\n1. **Generate API documentation** (optional):\n\n```bash\nphp artisan l5-swagger:generate\n```\n\n---\n\n## Configuration\n\n### Mail\n\nConfigure your SMTP settings in `.env`:\n\n```env\nMAIL_MAILER=smtp\nMAIL_HOST=your-smtp-host.com\nMAIL_PORT=587\nMAIL_USERNAME=your-username\nMAIL_PASSWORD=your-password\nMAIL_FROM_ADDRESS=\"newsletter@yourdomain.com\"\nMAIL_FROM_NAME=\"${APP_NAME}\"\n```\n\n### Newsletter\n\n| Variable                           | Description                                 | Default |\n| ---------------------------------- | ------------------------------------------- | ------- |\n| `NEWSLETTER_TRACKING_ENABLED`      | Enable open/click tracking                  | `true`  |\n| `NEWSLETTER_RATE_LIMIT_PER_MINUTE` | Max sends per rolling minute (`0` = no cap) | `0`     |\n| `NEWSLETTER_RATE_LIMIT_PER_HOUR`   | Max sends per rolling hour (`0` = no cap)   | `0`     |\n| `NEWSLETTER_RATE_LIMIT_PER_DAY`    | Max sends per rolling day (`0` = no cap)    | `0`     |\n\nSee [Rate limiting](#rate-limiting) for behaviour and tuning.\n\n### IMAP (Bounce Detection)\n\nOptional configuration for processing bounced emails:\n\n```env\nNEWSLETTER_IMAP_HOST=imap.yourdomain.com\nNEWSLETTER_IMAP_PORT=993\nNEWSLETTER_IMAP_USERNAME=your-username\nNEWSLETTER_IMAP_PASSWORD=your-password\nNEWSLETTER_IMAP_ENCRYPTION=ssl\nNEWSLETTER_IMAP_FOLDER=INBOX\n```\n\n### API Documentation (Swagger)\n\nTo auto-generate the OpenAPI spec on every request in local development:\n\n```env\nL5_SWAGGER_GENERATE_ALWAYS=true\n```\n\nFor production, run `php artisan l5-swagger:generate` during deploy.\n\n---\n\n## Quick Start\n\n1. **Seed sample data** (optional, recommended for testing):\n\n```bash\nphp artisan newsletter:seed-data\n```\n\n1. **Start the queue worker:**\n\n```bash\n./start-worker.sh\n```\n\nOr manually:\n\n```bash\nphp artisan queue:work --tries=3 --timeout=90\n```\n\n1. **Access the admin panel:**\n\n- **URL:** `https://newsletter.test` (Laravel Herd) or `http://localhost:8000`\n- **Default credentials** (after seeding): `admin@newsletter.test` / `password`\n\n---\n\n## Sending Newsletters\n\n### Immediate Sending\n\n1. Go to **Newsletter \u003e Messages**\n2. Create a new message or select an existing one\n3. Set status to **Ready**\n4. Click **Send Now**\n5. Emails are queued and sent automatically via the queue worker\n\n### Scheduled Sending\n\n1. Create or edit a message\n2. Set the **Scheduled Date** field\n3. The system sends automatically at the scheduled time (requires cron — see [Scheduled Tasks](#scheduled-tasks))\n\n---\n\n## Monitoring \u0026 Analytics\n\n- **Dashboard:** Main KPIs and send statistics\n- **Messages:** Status and send counts per message\n- **Message Details:** Individual tracking (opens, clicks) per recipient\n\n---\n\n## Rate Limiting\n\nConfigure 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.\n\n| Env variable                       | Window | Typical use                                                               |\n| ---------------------------------- | ------ | ------------------------------------------------------------------------- |\n| `NEWSLETTER_RATE_LIMIT_PER_MINUTE` | ~1 min | Burst control; low values cause jobs to `release()` often (short delays). |\n| `NEWSLETTER_RATE_LIMIT_PER_HOUR`   | ~1 h   | Provider hourly quotas.                                                   |\n| `NEWSLETTER_RATE_LIMIT_PER_DAY`    | ~24 h  | Daily provider caps.                                                      |\n\n**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.\n\n**Throughput:** in practice the **tightest** of your enabled limits dominates (e.g. high hourly but low per-minute still throttles bursts).\n\n**Estimated time:** while a message is in **Sending**, the UI uses the same three env-driven values to approximate completion (`Message::getEstimatedSendTime()`).\n\n**Example** (adjust to your SMTP limits):\n\n```env\nNEWSLETTER_RATE_LIMIT_PER_MINUTE=0\nNEWSLETTER_RATE_LIMIT_PER_HOUR=1000\nNEWSLETTER_RATE_LIMIT_PER_DAY=10000\n```\n\nKeeping `PER_MINUTE=0` and using only hourly/daily is a common choice for large campaigns to avoid many small queue delays.\n\n**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.\n\n**Check current counters:**\n\n```bash\nphp artisan newsletter:rate-limits\n```\n\n---\n\n## REST API\n\nThe application exposes a full REST API authenticated via **Laravel Sanctum** personal access tokens.\n\n### Authentication\n\n1. Go to the admin panel **user menu \u003e API tokens** (or navigate to `/api-tokens`)\n2. Create a token with the **API** ability enabled\n3. Use the token in the `Authorization` header:\n\n```\nAuthorization: Bearer \u003cyour-token\u003e\n```\n\n### Endpoints\n\nAll endpoints are prefixed with `/api` and require a valid Bearer token with the `api` ability.\n\n| Method   | Endpoint                                       | Description                    |\n| -------- | ---------------------------------------------- | ------------------------------ |\n| `GET`    | `/api/user`                                    | Authenticated user info        |\n| `GET`    | `/api/reports/newsletter`                      | Newsletter report (filterable) |\n| `GET`    | `/api/tags`                                    | List all tags                  |\n| `POST`   | `/api/tags`                                    | Create a tag                   |\n| `GET`    | `/api/tags/{tag}`                              | Show a tag                     |\n| `PUT`    | `/api/tags/{tag}`                              | Update a tag                   |\n| `DELETE` | `/api/tags/{tag}`                              | Delete a tag                   |\n| `GET`    | `/api/subscribers`                             | List subscribers               |\n| `POST`   | `/api/subscribers`                             | Create a subscriber            |\n| `GET`    | `/api/subscribers/{subscriber}`                | Show a subscriber              |\n| `PUT`    | `/api/subscribers/{subscriber}`                | Update a subscriber            |\n| `DELETE` | `/api/subscribers/{subscriber}`                | Delete a subscriber            |\n| `GET`    | `/api/templates`                               | List templates                 |\n| `POST`   | `/api/templates`                               | Create a template              |\n| `GET`    | `/api/templates/{template}`                    | Show a template                |\n| `PUT`    | `/api/templates/{template}`                    | Update a template              |\n| `DELETE` | `/api/templates/{template}`                    | Delete a template              |\n| `GET`    | `/api/campaigns`                               | List campaigns                 |\n| `POST`   | `/api/campaigns`                               | Create a campaign              |\n| `GET`    | `/api/campaigns/{campaign}`                    | Show a campaign                |\n| `PUT`    | `/api/campaigns/{campaign}`                    | Update a campaign              |\n| `DELETE` | `/api/campaigns/{campaign}`                    | Delete a campaign              |\n| `GET`    | `/api/campaigns/{campaign}/messages`           | List messages in a campaign    |\n| `POST`   | `/api/campaigns/{campaign}/messages`           | Create a message               |\n| `GET`    | `/api/campaigns/{campaign}/messages/{message}` | Show a message                 |\n| `PUT`    | `/api/campaigns/{campaign}/messages/{message}` | Update a message               |\n| `DELETE` | `/api/campaigns/{campaign}/messages/{message}` | Delete a message               |\n\n### OpenAPI Documentation\n\nInteractive Swagger UI is available at `/api/documentation`. Generate or update the spec with:\n\n```bash\nphp artisan l5-swagger:generate\n```\n\n---\n\n## MCP Server (AI Integrations)\n\nThe application includes a **Model Context Protocol (MCP)** server that allows AI clients (Cursor, Claude Code, Windsurf, etc.) to interact with your newsletter data.\n\n### Endpoint\n\n```\n/mcp/newsletter\n```\n\nAuthenticated with a Sanctum Bearer token that has the **`mcp`** ability.\n\n### Available Tools\n\n| Tool                           | Description                                                                        |\n| ------------------------------ | ---------------------------------------------------------------------------------- |\n| `list-campaigns`               | Lists campaigns (all campaigns for managers/administrators; editors see their own) |\n| `newsletter-report`            | Delivery report with summary, per-message stats, and daily timeseries              |\n| `send-history-analysis`        | Highlights and trends from recent send history                                     |\n| `subscriber-insights`          | Audience breakdown by tags and statuses                                            |\n| `generate-email-template-html` | Generates responsive HTML email template from a description                        |\n| `create-newsletter-message`    | Creates a draft or ready message inside a campaign                                 |\n\n### Prompt\n\nThe 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.\n\n### Setup in Your AI Client\n\n1. Create a token in the admin panel with the **MCP** ability\n2. Configure your AI client to connect to the MCP endpoint with the token as Bearer auth\n3. Debug locally with: `php artisan mcp:inspector mcp/newsletter`\n\n### VS Code / Cursor `mcp.json` example (HTTP)\n\nPoint the URL at your app’s origin plus `/mcp/newsletter`, and send the Sanctum token in the `Authorization` header:\n\n```json\n{\n    \"mcpServers\": {\n        \"newsletter\": {\n            \"url\": \"https://your-domain.example/mcp/newsletter\",\n            \"headers\": {\n                \"Authorization\": \"Bearer YOUR_SANCTUM_TOKEN\"\n            }\n        }\n    }\n}\n```\n\nReplace `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).\n\n### Example requests\n\nYou can phrase tasks in natural English; the assistant maps them to MCP tools. Examples:\n\n- **Campaigns:** “List all newsletter campaigns.” / “What campaigns do we have?”\n- **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.”\n- **Scoped report:** “Report for the campaign with id **…** between **2026-01-01** and **2026-01-31**.”\n- **Narrative summary:** “Summarize send history and highlights for the last quarter.”\n- **Audience:** “How many subscribers do we have by status? Which tags have the most subscribers?”\n- **HTML:** “Generate a responsive HTML email template for a product launch: hero, short intro, three feature bullets, primary CTA button.”\n- **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.”\n\nFor **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).\n\n---\n\n## Testing Tags\n\nTags can be marked as **testing** (`is_testing` flag) in the Filament admin panel. When a message targets recipients **only** through testing tags:\n\n- Sends are **excluded from dashboard statistics** (emails sent, opens, clicks, bounces, charts)\n- After the send completes, per-recipient send rows and related bounces are **removed** from the database\n- Testing tags and normal tags **cannot be mixed** on the same message\n\nThis allows safe end-to-end testing of the sending pipeline without polluting production metrics.\n\n---\n\n## Multilingual Support\n\nThe admin panel supports six languages: **Italian**, **English**, **German**, **French**, **Spanish**, and **Portuguese**.\n\n- Logged-in users can choose their language from the **Profile** page\n- Before login, the panel detects the browser `Accept-Language` header and uses the closest supported language (defaults to English)\n- New users automatically inherit the browser language on account creation\n\n---\n\n## Public Routes\n\nThe following routes are available for public use:\n\n| Route                               | Method | Description                          |\n| ----------------------------------- | ------ | ------------------------------------ |\n| `/subscribe`                        | GET    | Subscription form                    |\n| `/subscribe`                        | POST   | Process subscription                 |\n| `/subscribe/confirm/{token}`        | GET    | Confirm subscription (double opt-in) |\n| `/unsubscribe/{subscriber}`         | GET    | Unsubscribe form                     |\n| `/unsubscribe/{subscriber}/confirm` | POST   | Confirm unsubscribe                  |\n\n---\n\n## Scheduled Tasks\n\nAdd the Laravel scheduler to your crontab:\n\n```bash\n* * * * * cd /path-to-your-project \u0026\u0026 php artisan schedule:run \u003e\u003e /dev/null 2\u003e\u00261\n```\n\n| Task                         | Schedule         | Description                                    |\n| ---------------------------- | ---------------- | ---------------------------------------------- |\n| `newsletter:send-scheduled`  | Every minute     | Sends messages with scheduled date in the past |\n| `newsletter:process-bounces` | Every 15 minutes | Processes bounced emails via IMAP              |\n| `backup:run`                 | Daily at 03:00   | Runs application backup                        |\n| `backup:clean`               | Daily at 04:00   | Cleans old backups                             |\n\n---\n\n## Artisan Commands\n\n| Command                        | Description                                                        |\n| ------------------------------ | ------------------------------------------------------------------ |\n| `newsletter:seed-data`         | Populate database with sample subscribers, campaigns, and messages |\n| `newsletter:send-scheduled`    | Manually trigger scheduled message sending                         |\n| `newsletter:process-pending`   | Process pending emails in the queue                                |\n| `newsletter:process-bounces`   | Process bounced emails from IMAP                                   |\n| `newsletter:rate-limits`       | Display current rate limit status                                  |\n| `l5-swagger:generate`          | Generate or update the OpenAPI specification                       |\n| `mcp:inspector mcp/newsletter` | Debug the MCP server locally                                       |\n\n---\n\n## Take it to production — cipi.sh\n\nOnce your app is ready, deploy it to any Ubuntu VPS with [cipi.sh](https://cipi.sh) — an open-source CLI built exclusively for Laravel.\n\n- Full app isolation — own Linux user, PHP-FPM pool \u0026 database per app\n- Zero-downtime deploys with instant rollback via Deployer\n- Let's Encrypt SSL, Fail2ban, UFW firewall — all automated\n- Multi-PHP (7.4 → 8.5), queue workers, S3 backups, auto-deploy webhooks\n- AI Agent ready — any AI with SSH access can deploy \u0026 manage your server\n\n```bash\nwget -O - https://raw.githubusercontent.com/andreapollastri/cipi/refs/heads/latest/install.sh | bash\n```\n\n→ Learn more at [cipi.sh](https://cipi.sh)\n\n---\n\n## Troubleshooting\n\n### Emails not sending\n\n1. Ensure the queue worker is running: `./start-worker.sh` or `php artisan queue:work`\n2. Manually process pending: `php artisan newsletter:process-pending`\n3. Check the jobs table: `php artisan tinker --execute=\"DB::table('jobs')-\u003ecount()\"`\n\n### Test email delivery\n\n```bash\nphp artisan tinker --execute=\"Mail::raw('Test', fn(\\$m) =\u003e \\$m-\u003eto('test@example.com'))\"\n```\n\n### Verify rate limits\n\n```bash\nphp artisan newsletter:rate-limits\n```\n\n### Frontend changes not visible\n\nRun `npm run build` or `npm run dev` to compile assets.\n\n### API issues\n\n1. Verify the token is valid: check the **API tokens** page in the admin panel\n2. Ensure the token has the correct ability (`api` for REST endpoints, `mcp` for the MCP server)\n3. Check the OpenAPI docs at `/api/documentation` for request/response format\n\n---\n\n## License\n\nMIT\n\n---\n\n## Credits\n\nCreated by [Andrea Pollastri](https://web.ap.it)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandreapollastri%2Fnewsletter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandreapollastri%2Fnewsletter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandreapollastri%2Fnewsletter/lists"}