Skip to content

About

Self-hosted Laravel dashboard for AWS SES delivery, bounce, complaint, open, click analytics via SNS

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

SES Tracking — Self-hosted AWS SES email analytics dashboard

SES Tracking is a self-hosted Amazon SES (Simple Email Service) analytics application built with Laravel. It ingests SES event notifications through Amazon SNS webhooks and turns them into dashboards, reports, and exports so you can monitor email delivery, bounces, complaints, opens, clicks, and related metrics across teams and projects.

Website: sestracking.com

Who it is for

  • Teams sending mail through AWS SES who want private, self-hosted visibility into the full event stream—not only sends, but per-recipient outcomes.
  • Operators who need multi-project isolation (separate webhook URLs and access) and multi-user admin vs regular roles.
  • Anyone migrating from or comparing to sesdashboard and wanting a Laravel 11 stack, tests, optional webhook payload logging, and robust handling of edge cases (for example open and click attribution when one message has multiple recipients).

This project takes structural inspiration from SES Dashboard (MIT, Copyright (c) 2020 Nikeev) but is maintained separately: different codebase, Laravel 11, automated tests, built-in debug logging for webhooks (toggle via env), multi-user and multi-project support, and corrected SES event handling for tricky real-world cases.

License: MIT

Suggested GitHub topics: aws-ses, amazon-ses, sns, email-tracking, email-analytics, webhooks, laravel, php, self-hosted, transactional-email, dashboard

Features

  • Multi-user support: Administrator and regular user roles
  • Multi-project management: Dedicated webhook endpoints per project (/webhook/{project_token})
  • Email event tracking: Delivery, bounces, complaints, opens, clicks, delays (as surfaced by SES/SNS)
  • AWS integration: SNS → HTTPS webhook processing with idempotent handling
  • Analytics dashboard: Chart.js charts, filters by project and date range
  • Data export: CSV and Excel with filters
  • Responsive UI: Bootstrap 5 frontend (including Vue components where used), compiled assets included in the repo

Screenshots

Dashboard

Overview of send, delivery, opens, clicks, and not-delivered metrics with project and date filters, plus a multi-series trend chart (send, delivery, bounce, complaint, delivery delay, open, click).

SES Tracking dashboard: AWS SES email analytics summary cards and Chart.js activity trends

Architecture

  • Backend: Laravel 11, PHP 8.2+, MySQL 8
  • Frontend: JavaScript with Bootstrap 5 and Chart.js
  • Build: Vite (via laravel-vite-plugin)
  • Email provider: Amazon SES
  • Ingest: Amazon SNS notifications → Laravel webhook controllers (idempotent processing)

Project Kickstart

Prerequisites

  • PHP 8.2+
  • MySQL 8.0+
  • Composer
  • Node.js 18+ and npm (to build the frontend assets)

Note: Compiled frontend assets are not committed to git. Run npm install && npm run build on every install and deploy; the built files go to public/build/.

Quick Start

  1. Clone and install dependencies:

    git clone https://github.com/alephcom/sestracking.git
    cd sestracking
    composer install
    npm install
    npm run build
  2. Environment configuration:

    cp .env.example .env
    php artisan key:generate
  3. Database setup:

    php artisan migrate
    php artisan db:seed
  4. Start the development server:

    php artisan serve

Frontend Development

  1. Start the Vite dev server (hot reloads styles and scripts as you edit them):

    npm run dev

    Keep it running alongside php artisan serve.

  2. Build for production:

    npm run build

Upgrading from an older version? Assets used to be committed under public/js, public/css and public/fonts and built with Laravel Mix. Those folders are no longer used; run npm install && npm run build as part of your deploy, and delete the old folders if they are left over.

Initial Data & Admin User

The database seeder creates an initial admin user:

  • Email: admin@example.com
  • Password: password
  • Role: Administrator

This user has full access to all features including user management and project administration.

Environment Configuration

Update the following parameters in your .env file:

Database Configuration

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=your_database_name
DB_USERNAME=your_username
DB_PASSWORD=your_password

AWS SES Configuration (send test vs suppression list)

Laravel’s SES mail transport (send test) uses AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_DEFAULT_REGION in .env as usual.

SES account-level suppression list features (admin UI list/add/remove, webhook auto-push, and the scheduled daily import into ses_suppressed_destinations) use only the per-project Access Key ID, Secret Access Key, and region fields on each project in the admin UI. Global .env credentials are not used for those APIs.

Required IAM actions for suppression are listed on each project’s edit page and in config/ses_suppression.php (ses:PutSuppressedDestination, ses:ListSuppressedDestinations, ses:DeleteSuppressedDestination; ses:GetSuppressedDestination is available on the service for future use). Use "Resource": "*" in the policy unless your organization scopes further. The project’s AWS region must match the region where you send with SES for that account.

A queued job runs daily (see routes/console.php) to copy the account suppression list into ses_suppressed_destinations for each project that has per-project SES keys and a resolvable region. Ensure php artisan schedule:run is on your server crontab so the scheduler fires.

If your SES account already adds recipients to the account suppression list automatically for bounces or complaints, enabling auto-push in this app can be redundant but is otherwise safe.

AWS_ACCESS_KEY_ID=your_aws_access_key
AWS_SECRET_ACCESS_KEY=your_aws_secret_key
AWS_DEFAULT_REGION=your_aws_region

MAIL_MAILER=ses
MAIL_FROM_ADDRESS=your_verified_email@domain.com
MAIL_FROM_NAME="${APP_NAME}"

Application Settings

APP_NAME="SES Tracking"
APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost:8000

Optional: Faster dashboard updates (cache)

Dashboard figures are cached for up to 5 minutes. With a cache store that supports tags (Redis or Memcached), each new webhook event clears the cache for its project, so the dashboard shows new events on the next load:

CACHE_STORE=redis

With the default file or database stores the dashboard still works, but can be up to 5 minutes behind.

Optional: Webhook Debug Logging

# Enable webhook payload logging for debugging (disable in production)
WEBHOOK_DEBUG_LOG=false

Testing

Run the test suite:

vendor/bin/phpunit

Two-factor authentication (email / password users)

Users who sign in with email and password (not Google/Microsoft SSO) must enroll an authenticator app (TOTP). Recovery codes are shown once after enrollment.

Operator lockouts: Clear 2FA for a user so they can enroll again on next sign-in:

php artisan user:reset-two-factor {user@email.com|user_id}

SSO-only users are rejected by that command (they do not use in-app TOTP).

Application key rotation: two_factor_secret is stored encrypted with your APP_KEY. If you run php artisan key:generate or change APP_KEY without decrypting existing data, existing TOTP secrets become unreadable. After a key rotation, affected users cannot sign in with TOTP until an operator runs user:reset-two-factor for each affected account (they set up a new authenticator on next login).

Webhook Setup

Point Amazon SES event publishing at an Amazon SNS topic, then subscribe your app’s HTTPS endpoint to that topic. Each project exposes a unique path used as the SNS HTTPS subscription URL (confirm the subscription in SNS when prompted).

Configure configuration sets and event types (send, delivery, bounce, complaint, open, click, etc.) in the SES console so SES publishes notifications to SNS; SNS delivers JSON payloads to:

POST https://<your-domain.com>/webhook/{project_token}

Each project has a unique token for webhook authentication.

Signature verification

Every webhook request must be signed by Amazon SNS. The app checks the signature against AWS's signing certificate (downloaded from sns.<region>.amazonaws.com and cached for a day) and rejects anything else with 403, so events can't simply be posted to the URL by hand. Your server needs outbound HTTPS access to sns.*.amazonaws.com.

The signature proves a message came through Amazon SNS, not which SNS topic sent it, so still treat the webhook URL as a secret.

If a project posts SES events to the webhook directly, without SNS, those requests carry no signature. A project admin can allow them under Manage Projects → Edit → Webhook security → Accept webhook requests that are not signed by Amazon SNS. Only turn this on when you need it: anyone who knows that project's webhook URL could then send fake events.

Queue worker (required)

The webhook only checks each notification and puts it on a queue, then answers SNS straight away. A queue worker does the actual saving, so events will not show up on the dashboard unless a worker is running. With the default QUEUE_CONNECTION=database:

php artisan queue:work --tries=3

In production, keep it running with a process manager such as Supervisor or systemd, and restart it after each deploy (php artisan queue:restart) so it picks up new code. Jobs that fail every attempt are kept in the failed_jobs table; see them with php artisan queue:failed and retry with php artisan queue:retry all.

For local development you can set QUEUE_CONNECTION=sync to process webhooks immediately without a worker.

TODO

  • Add more detailed documentation for setting up AWS SES and SNS

About

Self-hosted Laravel dashboard for AWS SES delivery, bounce, complaint, open, click analytics via SNS

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages