Automating Social Preview Checks: GitHub Actions, Pre-Deploy Hooks, and CLI

Automating social preview checks means catching missing OG tags, broken Twitter Cards, and slow page loads in your CI pipeline — before a campaign link goes live and drives real traffic to a broken preview. Manual checks done in a browser catch the obvious failures. Automated checks catch the quiet ones: a staging deploy that stripped the og:image meta tag, a CDN rewrite that removed the twitter:card declaration, a redirect that doubled the page load time. This guide shows exactly how to wire up mlz inspect in GitHub Actions, a pre-deploy hook, and a shell loop.

Terminal window showing mlz inspect running in GitHub Actions with all social preview checks passing — open_graph, twitter_card, favicon, load_time — and a deploy-unblocked badge

Why social preview checks belong in automation

Every time a page is deployed, the HTML that social platforms scrape can change. A template update, a CMS export, or a caching layer swap can silently remove the meta tags that control how links look on LinkedIn, Facebook, X, and Slack. By the time a campaign link goes out with a broken preview, the damage is already done: click-through rates drop, the paid campaign budget is spent, and the root cause is a missing <meta> tag that would have taken 30 seconds to fix.

Manual social preview checks — opening the Facebook Sharing Debugger, the LinkedIn Post Inspector, the Twitter Card Validator — require a browser session per platform per URL. They do not compose into CI pipelines. They do not run before every deploy. They do not produce structured output you can parse.

mlz inspect runs the same checks — OG tags, Twitter Card tags, viewport meta, favicon, page load time — as a single terminal command that returns JSON and exits non-zero on failure. That exit code is what makes it automatable. A CI step fails; a pre-deploy hook blocks; an agent workflow stops and reports. See landing page validation for social sharing for the full taxonomy of what these checks cover.

GitHub Actions: validate social previews before merge

The most reliable place to run social preview checks is as a required CI step that must pass before a pull request can merge. Here is a complete GitHub Actions workflow that runs mlz inspect against a staging URL whenever a pull request targets the main branch:

.github/workflows/social-preview-check.yml
name: Social Preview Check

on:
  pull_request:
    branches: [main]

jobs:
  inspect:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install MissingLinkz
        run: npm install -g missinglinkz

      - name: Inspect staging landing page
        env:
          MLZ_API_KEY: ${{ secrets.MLZ_API_KEY }}
        run: |
          mlz inspect https://staging.acme.com/landing --format json
          exit $?

The workflow installs missinglinkz globally, then runs mlz inspect against the staging URL. If any check fails (missing OG tags, Twitter Card absent, page not reachable), the command exits with a non-zero code and the CI step fails. The pull request cannot merge until the checks pass.

MLZ_API_KEY is optional for the inspect command — OG tag checks work without authentication. Set it in your repository secrets if you want results stored in your MissingLinkz account for later review.

Checking multiple URLs

If a campaign launches multiple landing pages, check them all in the same CI job:

Check multiple landing pages
run: |
  URLS=(
    "https://staging.acme.com/landing-a"
    "https://staging.acme.com/landing-b"
    "https://staging.acme.com/landing-c"
  )
  FAILED=0
  for URL in "${URLS[@]}"; do
    echo "Inspecting $URL"
    mlz inspect "$URL" --format json || FAILED=1
  done
  exit $FAILED

This pattern runs all inspections and collects failures rather than stopping at the first one, so you get a complete picture of what needs fixing.

Pre-deploy hook: block deploys on social preview failures

For teams using deployment scripts directly — without a full CI pipeline — a pre-deploy hook blocks the deploy if any social preview check fails. Add this to your deployment script before the deploy command runs:

deploy.sh — pre-deploy social preview check
#!/bin/bash
set -e

STAGING_URL="https://staging.acme.com/landing"

echo "Running social preview check before deploy..."

RESULT=$(mlz inspect "$STAGING_URL" --format json)
SUCCESS=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['success'])" 2>/dev/null)

if [ "$SUCCESS" != "True" ]; then
  echo "Social preview check failed. Blocking deploy."
  echo "$RESULT" | python3 -m json.tool
  exit 1
fi

echo "Social preview check passed. Deploying..."
# your deploy command here

The script runs mlz inspect, parses the success field from the JSON output, and exits with code 1 if the check failed. Because it uses set -e, any subsequent deploy command will not execute if this step fails.

What the inspect output looks like

Here is a real mlz inspect response showing what the script is parsing:

mlz inspect — JSON response
{
  "url": "https://acme.com/landing",
  "success": true,
  "checks": [
    { "check": "fetch", "status": "pass", "message": "Page fetched successfully (312ms)." },
    { "check": "open_graph", "status": "pass", "message": "Open Graph tags present: title, description, and image." },
    { "check": "twitter_card", "status": "pass", "message": "Twitter Card present (type: summary_large_image)." },
    { "check": "favicon", "status": "pass", "message": "Favicon found." },
    { "check": "load_time", "status": "pass", "message": "Page load time: 312ms." }
  ],
  "open_graph": {
    "title": "Acme — Q2 Product Launch",
    "description": "See what we shipped this quarter.",
    "image": "https://acme.com/og/q2-launch.png",
    "type": "website",
    "url": "https://acme.com/landing"
  },
  "twitter_card": {
    "card": "summary_large_image",
    "title": "Acme — Q2 Product Launch",
    "image": "https://acme.com/og/q2-launch.png"
  },
  "load_time_ms": 312
}

Every field is machine-readable. success: true is the top-level gate. The checks array gives per-check detail. The open_graph and twitter_card objects let you extract and log the actual tag values that were found.

Slack notification on failure

CI failures are easy to miss if the team is not actively watching the pipeline. Adding a Slack notification means the right people hear about a broken social preview immediately rather than discovering it when the campaign is already live. Here is how to extend the GitHub Actions workflow to send a notification when mlz inspect fails:

Add Slack alert on inspect failure
      - name: Inspect staging landing page
        id: inspect
        continue-on-error: true
        run: mlz inspect https://staging.acme.com/landing --format json

      - name: Notify Slack on failure
        if: steps.inspect.outcome == 'failure'
        uses: slackapi/[email protected]
        with:
          payload: |
            {
              "text": "Social preview check failed on PR #${{ github.event.number }}. OG tags or Twitter Card may be broken. Review before merging."
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
          SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

      - name: Fail job if inspect failed
        if: steps.inspect.outcome == 'failure'
        run: exit 1

The key pattern here is continue-on-error: true on the inspect step combined with a follow-up step that checks the outcome. This lets the Slack notification fire before the job fails, instead of the job aborting before it reaches the notification step.

Caching tips for fast runs

The main latency in a mlz inspect CI step is the npm install -g missinglinkz command. On a cold runner this takes 10–20 seconds. You can reduce this to under a second by caching the npm global install between runs:

Cache npm global install in GitHub Actions
      - name: Cache npm global
        uses: actions/cache@v4
        with:
          path: ~/.npm
          key: npm-global-missinglinkz-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            npm-global-missinglinkz-

      - name: Install MissingLinkz
        run: npm install -g missinglinkz

With caching, the install step typically resolves in 1–2 seconds on cache hit. The inspect command itself takes as long as the page takes to respond — usually under 500ms for a production URL, and up to 2 seconds if the staging environment is slow to start.

For teams running inspect on many URLs, consider running the checks in parallel using a matrix strategy rather than a shell loop. Matrix jobs run concurrently and the total CI time is bounded by the slowest URL, not by the total count.

FAQ

Does mlz inspect work on staging URLs that require authentication?
mlz inspect makes a standard HTTP GET request. If your staging environment is behind HTTP basic auth or requires a session cookie, the inspector will not be able to fetch the page metadata. The most common workaround is to create a dedicated preview URL that bypasses auth — a common pattern with tools like Vercel Preview Deployments or a staging environment with an allow-listed IP range for CI runners.
Can I run social preview checks without an API key?
Yes. mlz inspect runs without an MLZ_API_KEY. The command fetches the page directly and parses the meta tags. An API key is only needed if you want to store inspection results in your MissingLinkz account or access them later via the API. For CI-only use, no key is required.
What does mlz inspect check beyond OG tags?
The inspect command checks Open Graph tags (og:title, og:description, og:image), Twitter Card tags (twitter:card, twitter:title, twitter:image), the favicon, and page load time. For a complete picture of OG tag validation specific to campaign links, see OG tag validation for campaign links. For checking OG tags directly from the command line, see how to check OG tags from the command line.
Should I run social preview checks on every commit or just before merging?
Running on every pull request (as shown in the workflow above) is the right default. Running on every commit to a feature branch is usually too frequent — most commits touch code, not HTML meta tags. Running only on merge to main risks catching failures too late if your deploy pipeline is fast. The pull request gate is the right balance: it checks once per change, before the change lands in production.
How do I inspect a URL that is not yet live (e.g., a local dev server)?
If the URL is accessible from the CI runner, mlz inspect will check it. For local dev servers, this means the URL must be publicly accessible — which it is not by default. Use a tunneling tool like ngrok or set up an ephemeral staging environment as part of your CI pipeline. Both approaches are common in teams that want end-to-end preview validation before code merges.

Automate your next deploy

Add mlz inspect to your GitHub Actions workflow and catch broken social previews before they reach production. Install takes under a minute.

npm install -g missinglinkz
mlz inspect https://your-landing-page.com --format json

See the SKILL.md reference for all mlz inspect flags and JSON response fields.