Download the PHP package rlnks/php-mail-audit without Composer
On this page you can find all versions of the php package rlnks/php-mail-audit. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download rlnks/php-mail-audit
More information about rlnks/php-mail-audit
Files in rlnks/php-mail-audit
Package php-mail-audit
Short Description Email HTML quality analysis engine — detect bad practices, score compatibility, get actionable insights before sending.
License MIT
Homepage https://github.com/rlnks/php-mail-audit
Informations about the package php-mail-audit
php-mail-audit
Email HTML quality analysis engine for PHP.
Analyze email templates before sending — detect compatibility issues, score your HTML against major email clients, and get actionable insights to fix problems before they reach your users' inboxes.
"Grammarly for HTML emails"
Table of Contents
- Requirements
- Installation
- Quick Start
- Configuration
- Analyzing HTML
- Result Format
- Bundled Rules
- Detection Types
- Localization
- Remote KB Sync
- CLI
- Custom Rules
- Custom Detectors
- Score Calculation
- Integration Examples
- Running Tests
- License
Requirements
- PHP 8.1 or higher
- No external dependencies — uses PHP's native
DOMDocument - No framework required — works with Laravel, Symfony, Slim, or plain PHP
Installation
Quick Start
Example output:
Configuration
All configuration is optional. The package works out of the box with the bundled rule set.
Config reference
| Key | Type | Default | Description |
|---|---|---|---|
auto_update |
bool |
false |
Enable remote KB fetch |
ttl_days |
int |
7 |
Days before cache is considered stale |
endpoint |
string\|null |
null |
Remote URL to fetch rules from |
api_key |
string\|null |
null |
Bearer token sent in Authorization header |
cache_path |
string\|null |
null |
Absolute path to the local cache file |
Config file pattern
Analyzing HTML
Pass the raw HTML string of the email template. The HTML does not need to be a complete document — partials and fragments are accepted.
Result Format
analyze() returns an array with four keys:
score
An integer between 0 and 100. Higher is better. See Score Calculation.
insights
Each triggered rule produces one insight:
Each location entry points to one occurrence of the issue in the source HTML:
| Field | Type | Description |
|---|---|---|
line |
int |
Line number (1-based) |
column |
int |
Column within that line (1-based) |
offset_start |
int |
Byte offset of the match start in the HTML string |
offset_end |
int |
Byte offset of the match end (exclusive) |
This is designed for editor integration — use offset_start/offset_end with CodeMirror or Monaco Range objects to highlight the exact positions, and line/column to scroll and place the cursor.
When multiple locales are requested, message and fix are associative arrays keyed by locale instead of strings:
passed
Rules that did not trigger and carry a success_message appear here — useful for showing positive feedback alongside issues (similar to htmlemailcheck.com):
Not every rule generates a passed item — only rules that define a success_message in their JSON (those where the absence of an issue is meaningfully good news).
summary
Bundled Rules
56 rules ship with the package. The philosophy: flag bad usage, not feature presence. Media queries, hover states, and class selectors used correctly (with inline fallbacks) score well. The engine penalizes the absence of fallbacks, not the features themselves.
Errors — break rendering in major clients
| Rule ID | Description | Weight |
|---|---|---|
no-flexbox |
CSS display: flex not supported in Outlook |
15 |
no-grid |
CSS display: grid not supported anywhere |
15 |
no-form-elements |
<form>, <input>, <button> stripped by all clients |
15 |
no-script |
<script> stripped by all clients for security reasons |
15 |
no-iframe |
<iframe> blocked by all clients |
15 |
no-svg |
SVG not rendered in Outlook or Gmail | 12 |
no-video |
<video> not supported in Outlook or Gmail |
12 |
no-audio |
<audio> not supported in any major client |
10 |
no-css-gap |
CSS gap / row-gap / column-gap not supported anywhere |
9 |
no-object-fit |
object-fit not supported in any major client |
8 |
no-css-filter |
CSS filter not supported in Outlook or Gmail |
8 |
no-clip-path |
clip-path not supported in any major client |
8 |
no-css-variables |
CSS var() used without a fallback value — Outlook and Gmail silently ignore the property entirely |
7 |
Warnings — real problems when fallbacks are missing
| Rule ID | Description | Weight |
|---|---|---|
style-no-inline-fallback |
<style> block present but zero inline styles — layout breaks entirely when Gmail/Outlook strip the style block |
12 |
html-too-large |
HTML exceeds 102 KB — Gmail clips the message and shows a "Message clipped" link | 10 |
media-no-inline-base |
@media queries present but no inline style baseline — responsive layout has no fallback for Gmail/Outlook |
10 |
img-dimensions |
<img> without width/height — layout breaks when images are blocked |
8 |
no-float |
float breaks column layouts in Outlook 2007–2019 |
8 |
font-no-fallback |
External font loaded but no inline font-family fallback stack — text falls back to client default when font is stripped |
8 |
no-picture |
<picture> / srcset not supported in Outlook or Gmail |
8 |
missing-alt-img |
<img> without alt shows broken icons when images blocked |
7 |
no-css-calc |
calc() not supported in Outlook 2007–2019 or Gmail |
7 |
missing-https |
HTTP (non-HTTPS) src/href detected — email clients block mixed content, breaking images and links |
6 |
no-div-layout |
<div> with layout CSS (width, float, margin, etc.) — box model ignored by Outlook |
6 |
no-animation |
CSS animation / @keyframes ignored by Outlook and Gmail |
6 |
url-unencoded |
Unencoded space in a URL (href or src) — breaks the link in all clients |
5 |
css-at-import |
@import in <style> silently ignored by Gmail/Outlook |
5 |
no-transform |
CSS transform not supported in Outlook or Gmail |
5 |
css-at-import-no-link |
@import in <style> with no <link> fallback — font will not load in clients that strip <style> blocks |
5 |
link-no-text |
<a> with no accessible text or descriptive image alt — screen readers announce it as an unlabeled link |
5 |
text-image-ratio |
Email is mostly images with very little readable text — high spam filter risk, renders blank when images are blocked | 6 |
email-max-width |
Fixed-width <table> over 600 px — overflows the Outlook rendering pane and narrow viewports |
5 |
Info — usage noted, minimal score impact
Rules in this category flag the presence of a feature that is often used correctly as progressive enhancement. They fire when the feature is detected, regardless of fallback quality — the corresponding warning-level rules handle the bad cases.
| Rule ID | Description | Weight |
|---|---|---|
no-position-absolute |
position: absolute/fixed ignored in most clients |
5 |
no-border-radius |
border-radius ignored by Outlook desktop |
4 |
no-box-shadow |
box-shadow not supported in Outlook |
3 |
no-transition |
CSS transition not supported in Outlook or Gmail |
3 |
table-role-presentation |
Layout tables without role="presentation" confuse screen readers |
3 |
preheader-missing |
No preheader div found — inbox preview will show unrelated body text | 3 |
inline-css |
<style> block present — acceptable when inline fallback styles are defined |
2 |
css-class-selectors |
Class-based CSS in <style> — Gmail strips class attributes |
2 |
css-media-queries |
@media queries detected — great when paired with inline styles |
2 |
no-external-fonts |
External font loaded — supported in Apple Mail, not Gmail/Outlook | 2 |
missing-lang |
<html> without lang attribute — screen readers and translation tools rely on it |
2 |
missing-viewport |
No <meta name="viewport"> — mobile clients may render at desktop width |
2 |
preheader-too-long |
Preheader text exceeds 150 characters (filler excluded) — most clients truncate at 85–150 chars | 2 |
css-pseudo-selectors |
:hover, :focus etc. detected — ignored in Outlook/Gmail, use as enhancement only |
1 |
div-content |
<div> used as content wrapper — acceptable, but <td> preferred for compatibility |
1 |
empty-alt-img |
<img alt=""> detected — verify image is truly decorative and carries no information |
1 |
nbsp-missing |
Regular space between a number and a currency/unit symbol — may break across lines on narrow screens | 1 |
heading-order |
Heading levels skipped (e.g. <h1> directly followed by <h3>) — hurts accessibility and screen reader navigation |
2 |
tracking-pixel |
1×1 tracking pixel detected — note that Apple Mail Privacy Protection may trigger false open events | 0 |
font-family-unquoted |
Multi-word font name used without quotes in font-family — may be misinterpreted by some CSS parsers |
2 |
missing-charset |
No character encoding declaration in <head> — some clients may misrender special characters |
2 |
missing-doctype |
No <!DOCTYPE html> declaration — some clients fall back to quirks mode rendering |
2 |
table-cellspacing |
<table> without cellpadding="0" cellspacing="0" — default cell spacing varies across clients |
2 |
missing-body-bgcolor |
No background color on <body> — some clients display a grey or off-white default background |
1 |
Detection Types
Every rule declares a detection object that specifies how the engine finds the issue. All detectors return exact character positions (line, column, byte offsets) for every match — see locations in the result format.
css_property
Matches CSS patterns anywhere in the document — inline style="" attributes and <style> blocks.
Supports optional "regex": true for patterns that require precision (e.g. to avoid false positives with similar property names):
html_tag
Fires when the specified HTML tag is present at least once. Uses DOMDocument for accurate parsing.
Patterns are tag names (no angle brackets).
html_attribute_missing
Fires when at least one instance of tag is missing a required attribute, or has an attribute with the wrong value.
With value check:
With "only_empty": true — fires when the attribute is present but empty (alt=""). Useful for distinguishing missing alt text from intentionally empty alt text:
html_content
Matches arbitrary string patterns anywhere in the raw HTML string.
Supports "regex": true for patterns requiring precision. The ~ character is used as the regex delimiter internally — escape it as \\~ if needed in a pattern:
html_tag_with_style
Fires when a tag is present and its inline style attribute contains one of the given CSS patterns. Useful for distinguishing structural divs from decorative ones.
Supports "regex": true for precise matching (e.g. to avoid matching max-width: when looking for width:):
correlation
Fires when a trigger pattern is present but an expected fallback pattern is absent. Use this to flag bad usage of a feature rather than its mere presence.
The rule above fires only when an external font is loaded and no inline font-family fallback stack is found — correctly scoring emails that use custom fonts with proper fallbacks.
style_block
Searches exclusively inside <style> block content. Supports plain strings or regular expressions.
With regex:
preheader
Detects the standard email preheader pattern — a <div> with both display:none and overflow:hidden in its inline style. Two modes:
missing— fires when no preheader div is found in a complete document (one that contains a<body>tag). Fragments are skipped.too_long— fires when the visible preheader text exceedsmax_lengthcharacters. Filler characters ( ,‌, and their Unicode equivalents) are stripped before measuring, so filling with spacers does not inflate the count.
html_metric
Measures a numeric property of the HTML and fires when it exceeds a threshold. Currently supported metrics:
| Metric | Description |
|---|---|
size |
Total byte length of the HTML string (strlen) |
heading_order
Detects heading levels that are skipped in document order (e.g. <h1> directly followed by <h3>). No configuration options.
html_link_no_text
Fires when an <a> element has no accessible text — no text node content and no child <img> with a non-empty alt. No configuration options.
html_tracking_pixel
Detects <img> elements with width="1" and height="1", the classic open-tracking pattern. No configuration options.
css_font_family
Detects multi-word font names used without quotes in font-family declarations (e.g. font-family: Open Sans instead of font-family: 'Open Sans'). No configuration options.
html_table_width
Fires when a <table> element carries an inline width in pixels that exceeds max_width (default: 600).
Localization
Five locales are bundled: en (English), fr (French), es (Spanish), de (German), pt (Portuguese).
Single locale
If a locale is missing for a rule, it falls back to en automatically.
Multiple locales
Pass an array to receive all translations in a single pass:
This is useful when building multi-language UIs without running analyze() twice.
Adding a locale
Add the locale key to message, fix, and optionally success_message in each rule JSON:
Remote KB Sync
By default the package uses the bundled rule set. You can point it at a remote endpoint to receive updated rules without a Composer update.
How it works
Enabling sync
Tier behavior
| Condition | Rules returned |
|---|---|
No api_key |
Free rules only |
Valid api_key |
Free + Pro rules |
Expired / invalid api_key |
401 response → silent fallback to bundled rules |
Cache behavior
| Situation | Behavior |
|---|---|
| First install, no cache | Bundled rules |
| Cache exists, not stale | Cache used |
| Cache stale or missing | Fetch from endpoint, write cache |
| Fetch fails (network error) | Bundled rules (silent fallback) |
auto_update = false |
Always bundled rules |
CLI
A command-line tool is available at vendor/bin/mailaudit after installation.
sync — refresh the local cache
Using environment variables:
Using a config file:
Dry run (fetch but do not write cache):
Available options
| Option | Description |
|---|---|
--config=<path> |
PHP file returning a config array |
--dry-run |
Fetch without writing the cache |
Environment variables
| Variable | Description |
|---|---|
MAILAUDIT_ENDPOINT |
Remote KB endpoint URL |
MAILAUDIT_API_KEY |
API key for pro tier |
MAILAUDIT_CACHE_PATH |
Absolute path to the local cache file |
audit — analyze an email file
Example output (text):
With --format=json, the full analyze() result array is printed as pretty-printed JSON — same structure as documented in Result Format.
audit options
| Option | Description |
|---|---|
--locale=<code> |
Locale for messages: en (default), fr, es, de, pt |
--format=json |
Output the full result as JSON instead of the formatted summary |
Custom Rules
You can add your own rules without modifying the package.
1. Create a rule JSON file
2. Load it alongside the bundled rules
Or subclass MailAudit to make this reusable in your project.
Rule JSON reference
| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
Yes | Unique identifier |
version |
string |
Yes | Semver string, bumped on changes |
updated_at |
string |
Yes | ISO date YYYY-MM-DD |
source |
string |
No | Reference URL (e.g. caniemail.com) |
tier |
string |
Yes | free or pro |
severity |
string |
Yes | error, warning, or info |
weight |
int |
Yes | Points deducted from score (0–100) |
tags |
string[] |
No | Categorization tags |
detection |
object |
Yes | See Detection Types |
affected_clients |
object |
No | Per-client support data |
message |
object |
Yes | Locale-keyed problem description |
fix |
object |
Yes | Locale-keyed fix suggestion |
success_message |
object |
No | Locale-keyed message shown when the rule passes. When present, the rule appears in the passed array of the result. |
Custom Detectors
You can register new detection types to support custom rule patterns.
1. Implement DetectorInterface
2. Register it with the factory
3. Use it in a rule JSON
Registration is global and static — register once at application bootstrap before calling analyze().
Score Calculation
The score starts at 100. Each triggered rule deducts a weighted amount based on its severity:
Example:
| Rule triggered | Severity | Weight | Multiplier | Deduction |
|---|---|---|---|---|
no-svg |
error | 12 | × 1.0 | 12.0 |
style-no-inline-fallback |
warning | 12 | × 0.6 | 7.2 |
no-css-calc |
warning | 7 | × 0.6 | 4.2 |
css-media-queries |
info | 2 | × 0.3 | 0.6 |
| Total deduction | 24.0 | |||
| Final score | 76 / 100 |
The score cannot go below 0.
Severity vs weight
Severity (error, warning, info) is a qualitative label indicating the nature of the problem. Weight is the nominal importance of the rule. The multiplier ensures that warnings and info items have a proportionally smaller impact on the score than blocking errors, so a well-crafted email with minor compatibility caveats scores realistically (75–90) rather than being penalized alongside fundamentally broken emails.
Integration Examples
Vanilla PHP
Symfony
Laravel
In a CI/CD pipeline (GitHub Actions)
Running Tests
Run static analysis:
License
MIT — © 2026 rlnks