Emailens
Integrations

CI/CD Integration

Integrate Emailens into your continuous integration pipeline beyond GitHub Actions.

Generic CI (curl-based)

Any CI system can call the Emailens API:

#!/bin/bash
THRESHOLD=70
API_KEY="${EMAILENS_API_KEY}"

for file in emails/*.html; do
  HTML=$(cat "$file")
  RESPONSE=$(curl -s -X POST https://emailens.dev/api/preview \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $API_KEY" \
    -d "{\"html\": $(echo "$HTML" | jq -Rs .)}")

  OVERALL=$(echo "$RESPONSE" | jq '.overallScore')

  if [ "$OVERALL" -lt "$THRESHOLD" ]; then
    echo "FAIL: $file scored $OVERALL (threshold: $THRESHOLD)"
    exit 1
  fi

  echo "PASS: $file scored $OVERALL"
done

GitLab CI

email-check:
  stage: test
  image: curlimages/curl:latest
  script:
    - |
      for file in emails/*.html; do
        HTML=$(cat "$file")
        SCORE=$(curl -s -X POST https://emailens.dev/api/preview \
          -H "Content-Type: application/json" \
          -H "Authorization: Bearer $EMAILENS_API_KEY" \
          -d "{\"html\": $(echo "$HTML" | jq -Rs .)}" | jq '.overallScore')
        echo "$file: $SCORE/100"
        [ "$SCORE" -lt 70 ] && exit 1
      done
  only:
    changes:
      - emails/**/*.html

Using the engine directly

For faster CI (no API calls), install @emailens/engine and run analysis locally:

import { analyzeEmail, generateCompatibilityScore } from "@emailens/engine";
import { readFileSync, readdirSync } from "fs";

const files = readdirSync("emails").filter((f) => f.endsWith(".html"));
let failed = false;

for (const file of files) {
  const html = readFileSync(`emails/${file}`, "utf-8");
  const warnings = analyzeEmail(html);
  const scores = generateCompatibilityScore(warnings);

  const overall =
    Object.values(scores).reduce((sum, s) => sum + s.score, 0) /
    Object.values(scores).length;

  if (overall < 70) {
    console.error(`FAIL: ${file} scored ${Math.round(overall)}`);
    failed = true;
  } else {
    console.log(`PASS: ${file} scored ${Math.round(overall)}`);
  }
}

if (failed) process.exit(1);

This approach is faster since it runs locally without network calls, but doesn't include screenshots.

On this page