wp-email-templates — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited wp-email-templates (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Model note: Mechanical refactoring — extract strings, create template files, wirewp_mail().haikuhandles end-to-end.
Move email bodies out of inline PHP strings into templates/emails/, where every message reuses one branded shell. Adding a new email = adding one content file.
wp_mail() heredocs.Not for: REST API webhooks, push notifications, or Slack integrations. Transactional email in themes — this skill targets plugin-delivered mail only.
templates/emails/
base.php shared shell: header, body slot ($content), footer
<name>.php per-email content (greeting, copy, CTA button)<style>/external CSS). Receives $subject, $preheader, $content, $site_name, $site_url. Echoes $content raw (// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped — content templates escape their own values).esc_html, esc_url); wrap copy in __()/esc_html__() with the text domain; use _n() for plurals. Guard each $var = $var ?? default; so the file is robust if rendered standalone.public static function render_template( string $path, array $vars = [] ): string {
if ( ! is_readable( $path ) ) return '';
// phpcs:ignore WordPress.PHP.DontExtract.extract_extract -- controlled template vars
extract( $vars, EXTR_SKIP );
ob_start();
include $path;
return (string) ob_get_clean();
}
public static function render_email( string $template, array $vars = [] ): string {
$dir = (string) plugin_config( 'templates' ) . 'emails/'; // your assets/templates path helper
$vars['content'] = self::render_template( $dir . $template . '.php', $vars );
$vars['site_name'] = $vars['site_name'] ?? get_bloginfo( 'name' );
$vars['site_url'] = $vars['site_url'] ?? home_url( '/' );
return self::render_template( $dir . 'base.php', $vars ); // always wrap in the shell
}A missing content template yields an empty body but the shell still renders — callers always get a valid document.
$body = Helpers::render_email( 'welcome', [ 'subject' => $subject, 'user_name' => $u->display_name, ... ] );
$headers = [ 'Content-Type: text/html; charset=UTF-8' ]; // REQUIRED for HTML
return wp_mail( $to, $subject, $body, $headers );Drive any TTL/expiry copy from the same constant the code uses (e.g. a token transient TTL) so email text can't drift from behavior.
© double-escapes through esc_html__ → use the literal ©.$year etc. trip WordPress.WP.GlobalVariablesOverride) — inline gmdate('Y') instead.WP's test suite captures wp_mail via MockPHPMailer:
reset_phpmailer_instance();
// ... trigger the send ...
$sent = tests_retrieve_phpmailer_instance()->get_sent();
$this->assertStringContainsString( 'text/html', $sent->header );
$this->assertStringContainsString( '<!DOCTYPE html', $sent->body ); // base shell used
$this->assertStringContainsString( $expected_cta, $sent->body );Force a send failure with add_filter( 'pre_wp_mail', '__return_false' ) to test the error branch.
references/base.php — the full responsive HTML base shell (header/body/footer, inline CSS), copy into templates/emails/base.php.references/content-example.php — an example content template (greeting, CTA button, optional expiry, fallback link) to copy and adapt per email.references/html-email-patterns.md — HTML email patterns: table-based layout rules, inline CSS requirements, dark-mode support, and Outlook-specific fixesreferences/wp-mail-api.md — WordPress mail API reference: wp_mail() parameters, phpmailer_init hook, SMTP configuration, and deliverability checklist~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.