DMARC management SaaS application — monitor email authentication (DMARC, SPF, DKIM), receive and parse aggregate reports, identify sending sources, score domain compliance, and alert on issues.
Built with Laravel 12, Livewire, Jetstream (Teams), Tailwind CSS, and Stripe billing.
- PHP 8.2+
- Composer
- Node.js 18+ & npm
- SQLite (default) or MySQL/PostgreSQL
git clone https://github.com/tombeech/dmarcwatch.git
cd dmarcwatch
composer install
npm installcp .env.example .env
php artisan key:generateSQLite is the default. Create the database file and run migrations:
touch database/database.sqlite
php artisan migrate
php artisan db:seed # Seeds known sending sources (Google, Microsoft, SendGrid, etc.)composer devThis starts all services concurrently:
- Laravel server on
http://localhost:8688 - Laravel Horizon (queue worker)
- Laravel Scheduler
- Laravel Pail (log viewer)
- Vite dev server on port 5174
Alternatively, run each individually:
php artisan serve --port=8688
php artisan horizon
npm run devnpm run buildDMARCWatch uses Laravel Cashier for subscription billing with three tiers (Free, Pro, Enterprise) in three currencies (USD, GBP, EUR).
- Create a Stripe account
- Create products and prices in the Stripe dashboard:
| Product | Billing | You need a price ID for each currency (USD/GBP/EUR) |
|---|---|---|
| Pro | Monthly | STRIPE_PRO_MONTHLY_PRICE_{USD,GBP,EUR} |
| Pro | Yearly | STRIPE_PRO_YEARLY_PRICE_{USD,GBP,EUR} |
| Enterprise | Monthly | STRIPE_ENTERPRISE_MONTHLY_PRICE_{USD,GBP,EUR} |
| Enterprise | Yearly | STRIPE_ENTERPRISE_YEARLY_PRICE_{USD,GBP,EUR} |
| Domain Add-on | Monthly | STRIPE_DOMAIN_ADDON_PRICE_{USD,GBP,EUR} |
- Set these in
.env:
STRIPE_KEY=pk_test_...
STRIPE_SECRET=sk_test_...
CASHIER_CURRENCY=usd
# Repeat for each currency (USD, GBP, EUR)
STRIPE_PRO_MONTHLY_PRICE_USD=price_xxxxx
STRIPE_PRO_YEARLY_PRICE_USD=price_xxxxx
STRIPE_ENTERPRISE_MONTHLY_PRICE_USD=price_xxxxx
STRIPE_ENTERPRISE_YEARLY_PRICE_USD=price_xxxxx
STRIPE_DOMAIN_ADDON_PRICE_USD=price_xxxxx
# ... GBP and EUR variants- Set up the Stripe webhook in the Stripe dashboard to point at
https://yourdomain.com/stripe/webhookand add the signing secret to.envasSTRIPE_WEBHOOK_SECRET.
DMARC aggregate reports are received via email. Each monitored domain gets a unique RUA address like abc12345@reports.dmarcwatch.app. Mailgun handles inbound routing.
- Create a Mailgun account
- Add and verify your inbound domain (e.g.,
reports.dmarcwatch.app) - Set up MX records for the inbound domain pointing to Mailgun
- Create an inbound route in Mailgun:
- Expression:
match_recipient(".*@reports.dmarcwatch.app") - Action:
forward("https://yourdomain.com/webhooks/inbound-report")andstop()
- Expression:
- Set environment variables:
DMARCWATCH_INBOUND_DOMAIN=reports.dmarcwatch.app
MAILGUN_INBOUND_SIGNING_KEY=your-mailgun-inbound-signing-keyThe signing key is found under Mailgun Dashboard > Settings > API Security > Inbound Signing Key (this is different from the API key).
Configure any mail driver. For production, use Mailgun, Postmark, SES, etc.:
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailgun.org
MAIL_PORT=587
MAIL_USERNAME=postmaster@yourdomain.com
MAIL_PASSWORD=your-smtp-password
MAIL_FROM_ADDRESS=hello@dmarcwatch.app
MAIL_FROM_NAME=DMARCWatchFor local development, MAIL_MAILER=log writes emails to storage/logs/.
DMARCWatch uses Laravel Horizon to manage queues. The following jobs run automatically:
| Job | Queue | Schedule |
|---|---|---|
ScheduleDnsChecks |
default | Every minute |
CheckDomainDns |
dns-checking | Dispatched by ScheduleDnsChecks |
ProcessInboundReport |
report-processing | Dispatched on inbound webhook |
SendAlertNotification |
alerts | Dispatched by AlertDispatcher |
SendWeeklyDigests |
default | Mondays at 09:00 |
PruneOldReports |
default | Daily |
Publish the Horizon config if you need to customize queue workers:
php artisan vendor:publish --provider="Laravel\Horizon\HorizonServiceProvider"Horizon dashboard is available at /horizon (restricted to local environment by default).
For production, run Horizon as a daemon process (use Supervisor or similar):
php artisan horizonFor the production domain (dmarcwatch.app):
- A/CNAME record — Point your domain to your server
- MX records for
reports.dmarcwatch.app— Point to Mailgun's MX servers - SPF record for outbound email — Include your mail provider
- DKIM — Set up via your mail provider
| Feature | Free | Pro | Enterprise |
|---|---|---|---|
| Domains | 1 | 10 | Unlimited |
| Reports/month | 100 | 10,000 | Unlimited |
| Retention | 7 days | 90 days | 365 days |
| DNS check interval | 1440 min (daily) | 60 min | 15 min |
| Alert channels | 1 | 5 | Unlimited |
| Weekly digests | No | Yes | Yes |
| API access | No | Yes | Yes |
Authenticated API available at /api/v1/ using Sanctum tokens. Public lookup endpoints (rate-limited 30/min):
GET /api/v1/lookup/dmarc?domain=example.com
GET /api/v1/lookup/spf?domain=example.com
GET /api/v1/lookup/dkim?domain=example.com&selector=google
Authenticated endpoints (require API token + Pro/Enterprise plan):
GET/POST /api/v1/domains
GET/PUT/DELETE /api/v1/domains/{id}
POST /api/v1/domains/{id}/check-dns
GET /api/v1/domains/{id}/reports
GET /api/v1/reports
GET /api/v1/reports/{id}
CRUD /api/v1/alert-channels
CRUD /api/v1/alert-rules
# Run full test suite (111 tests)
./vendor/bin/pest
# Run static analysis
./vendor/bin/phpstan analyse
# Run code formatting
./vendor/bin/pintapp/
├── Enums/ # SubscriptionPlan, DmarcPolicy, AlertChannelType, etc.
├── Http/
│ ├── Controllers/ # API v1 controllers + inbound webhook
│ ├── Middleware/ # Team scope, plan limits, API access, webhook verification
│ └── Resources/ # API JSON resources
├── Jobs/ # Report processing, DNS checks, alerts, digests, pruning
├── Livewire/ # Dashboard, Onboarding, Domains, Reports, Sources, Tools, Alerts, Billing
├── Mail/ # WelcomeEmail, DmarcAlertEmail, WeeklyDigest, SubscriptionChangeEmail
├── Models/ # Domain, DmarcReport, ReportRecord, SendingSource, AlertChannel, etc.
└── Services/ # DmarcAnalyzer, SpfAnalyzer, DkimVerifier, ReportParser, ComplianceScorer, etc.
config/dmarcwatch.php # Stripe prices, trial days, DNS resolvers, inbound email config
routes/
├── web.php # Marketing pages + authenticated app routes
├── api.php # Public lookups + authenticated API v1
└── console.php # Scheduled jobs
| Variable | Required | Description |
|---|---|---|
APP_KEY |
Yes | Generated by php artisan key:generate |
APP_URL |
Yes | Your application URL |
DB_CONNECTION |
Yes | sqlite, mysql, or pgsql |
STRIPE_KEY |
For billing | Stripe publishable key |
STRIPE_SECRET |
For billing | Stripe secret key |
STRIPE_WEBHOOK_SECRET |
For billing | Stripe webhook signing secret |
STRIPE_*_PRICE_* |
For billing | 15 Stripe price IDs (5 products x 3 currencies) |
DMARCWATCH_INBOUND_DOMAIN |
For reports | Domain for receiving DMARC reports |
MAILGUN_INBOUND_SIGNING_KEY |
For reports | Mailgun webhook signature verification |
DMARCWATCH_TRIAL_DAYS |
No | Trial period in days (default: 14) |
MAIL_* |
For emails | Outbound mail configuration |
QUEUE_CONNECTION |
No | database (default), redis, or sync |