Skip to content

VitePress Blog Instant Indexing: Bing IndexNow + GitHub Actions Automation in Practice ​

VitePress IndexNow Automation in Practice

Recently, I have been consulting Bing Webmaster Tools While in the background, the system pushed an optimization suggestion to me:

Learn how IndexNow can improve your website's visibility in search engines with a simple 5-minute setup

Looking at the backend data, although total impressions were 5.6K, clicks were only 202, and the data curve fluctuates greatly. This indicates that the website's new content has not been crawled by search engines in a timely manner. Because my blog is based on VitePress The pure static site I built did not have a ready-made plugin ecosystem like WordPress, so I decided to do it myself and implement automatic indexing through API pushes and a CI workflow.

Today, let's get hands-on with how to integrate into your VitePress blog.IndexNow, delivering a silky-smooth experience where content is indexed as soon as it is published.

What is IndexNow? ​

In the traditional SEO process, after we publish an article, we can only passively wait for search engine “spider” crawlers to periodically come and crawl it (which may take days to weeks).

IndexNow It is an open-source protocol jointly launched by Microsoft Bing and Yandex. Its core philosophy is:Active push When your website content is updated, you immediately notify search engines through the API, and after receiving the notification, the search engines will process it as soon as possible.

Simply put:turned "waiting for the courier" into "sending via SF Express Priority".

Step 1: Generate and deploy API Key ​

The first step is always to obtain a dedicated Key in Bing Webmaster Tools and verify website ownership.

  • Login Bing Webmaster Tools.

  • Find and click in the left menu IndexNow.

  • The system will generate an API Key string for you (for example:74c02e84c5fe401bb1768e0eae41deb6).

  • Core verification steps (VitePress hands-on):

  • Your Key needs to be directly accessible from the public internet:https://fishfinal.com/74c02e84c5fe401bb1768e0eae41deb6.txt.

  • Since the output generated after VitePress build is in .vitepress/dist, you only need to 74c02e84c5fe401bb1768e0eae41deb6.txt Put the file into your docs/public/ In the directory.

  • The file content only needs to contain this Key string itself (note: do not include a line break).

  • Rebuild and deploy your VitePress site, and confirm in the browser that the link is accessible; this means verification has passed.

Step 2: Basic solution (create a new single-file Action) ​

If you just want to "get it running quickly" and don't have a complex deployment pipeline (such as automatic hosting via Vercel/Netlify), you can create a separate indexnow.yml.

1. Write a basic push script ​

at scripts/indexnow.mjs Write the following code in it. This version is simple enough: get the changed files and push directly:

javascript
// ====== Configuration Section ======
const HOST = 'fishfinal.com'
const KEY = '74c02e84c5fe401bb1768e0eae41deb6' // Replace with your API Key
const KEY_LOCATION = `https://${HOST}/${KEY}.txt`
const ENDPOINT = 'https://api.indexnow.org/indexnow'
// ====================

const changedFiles = process.env.CHANGED_FILES
  ? process.env.CHANGED_FILES.split(' ').filter(Boolean)
  : []

// Filter out the .md files under the docs/posts directory and convert them to URLs.
const urlList = changedFiles
  .filter(file => file.startsWith('docs/posts/') && file.endsWith('.md'))
  .map(file => {
    let urlPath = file.replace(/^docs/, '').replace(/\.md$/, '.html')
    return `https://${HOST}${urlPath}`
  })

const uniqueUrls = [...new Set(urlList)]

if (uniqueUrls.length === 0) {
  console.log('ℹ️ No new articles, skipping push.')
  process.exit(0)
}

console.log('📤 Preparing to push to IndexNow:', uniqueUrls)

const payload = {
  host: HOST,
  key: KEY,
  keyLocation: KEY_LOCATION,
  urlList: uniqueUrls,
}

const response = await fetch(ENDPOINT, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json; charset=utf-8' },
  body: JSON.stringify(payload),
})

if ([200, 202].includes(response.status)) {
  console.log(`✅ [${response.status}] Push successful!`)
} else {
  console.error(`❌ Push failed, status code: ${response.status}`)
  process.exit(1)
}

2. Configure basic Action ​

New .github/workflows/indexnow.yml, monitoring push Event direct execution:

yaml
name: Push to IndexNow

on:
  push:
    branches: [ master ]

jobs:
  indexnow:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - name: Get changed files
        run: |
          FILES=$(git diff --name-only HEAD^ HEAD --diff-filter=AM | tr '\n' ' ')
          echo "CHANGED_FILES=$FILES" >> $GITHUB_ENV

      - name: Push to IndexNow
        run: node scripts/indexnow.mjs
        env:
          CHANGED_FILES: ${{ env.CHANGED_FILES }}

⚠️ Drawbacks: If your server/CDN hasn't been updated at this time, the Bing crawler will be turned away (404) when it arrives, severely damaging your SEO reputation.

Step 3: Advanced Solution (Integrate into the Existing Deployment Pipeline) ​

Real Homelab or enterprise deployments often have their own pipelines (such as your existing release-bump.yaml). Must Wait until the deployment is completely successful and the site is accessible online, then notify Bing..

1. Upgrade the ultimate version script (add real liveness probing and status output) ​

rewrite your scripts/indexnow.mjs, adding three major moats:Strict filtering, real HEAD liveness probing, and output data for email invocation:

javascript
// ====== Configuration Section ======
const HOST = 'fishfinal.com'
const KEY = '74c02e84c5fe401bb1768e0eae41deb6' // Replace with your API Key
const KEY_LOCATION = `https://${HOST}/${KEY}.txt`
const ENDPOINT = 'https://api.indexnow.org/indexnow'
// Strictly limit to only pushing articles under the posts directory.
const POSTS_PREFIX = 'docs/posts/'
// ====================

import fs from 'node:fs'

function writeOutput(key, value) {
  if (!process.env.GITHUB_OUTPUT) return
  fs.appendFileSync(process.env.GITHUB_OUTPUT, `${key}=${value}\n`)
}

function writeUrlsToOutput(urls) {
  const html = urls.map(u => `<li><a href="${u}">${u}</a></li>`).join('')
  writeOutput('pushed_urls_html', html)
  writeOutput('pushed_count', String(urls.length))
}

function writeErrorToOutput(msg) {
  writeOutput('error_msg', msg.replace(/\n/g, '<br>'))
}

// ====== 1. Parse filter file ======
const changedFiles = process.env.CHANGED_FILES
  ? process.env.CHANGED_FILES.split(' ').filter(Boolean)
  : []

// Keep only the .md files under docs/posts/, and filter out configuration files, scripts, etc.
const mdFiles = changedFiles.filter(
  file => file.startsWith(POSTS_PREFIX) && file.endsWith('.md')
)

if (mdFiles.length === 0) {
  console.log('ℹ️ No new or modified Markdown articles under docs/posts/, skipping IndexNow push.')
  writeOutput('pushed_count', '0')
  process.exit(0)
}

// ====== 2. Convert to full URL ======
let urlList = mdFiles.map(file => {
  // docs/posts/seo/xxx.md -> /posts/seo/xxx
  let urlPath = file.replace(/^docs/, '').replace(/\.md$/, '.html')
  urlPath = urlPath.replace(/\/index$/, '/')
  if (urlPath === '') urlPath = '/'
  return `https://${HOST}${urlPath}`
})

// Filter out 404 and deduplicate
urlList = [...new Set(urlList.filter(url => !url.includes('/404')))]

// ====== 3. Real availability check (to prevent pushing 404 to Bing) ======
async function verifyUrls(urls) {
  const valid = []
  console.log('\n🔍 Starting to verify whether the URL is actually accessible (HEAD request):')
  for (const url of urls) {
    try {
      const res = await fetch(url, { method: 'HEAD' })
      if (res.ok) {
        console.log(`  ✅ [${res.status}] ${url}`)
        valid.push(url)
      } else {
        console.warn(`  ❌ [${res.status}] ${url} (skipped push)`)
      }
    } catch (e) {
      console.warn(`  ❌ Request failed ${url} (skipped push)`)
    }
  }
  return valid
}

// ====== 4. Send request ======
async function main() {
  const verifiedUrls = await verifyUrls(urlList)

  if (verifiedUrls.length === 0) {
    console.log('\n⚠️ All URL validations failed (the website may not be fully deployed yet or there may be CDN caching), terminating push.')
    writeErrorToOutput('URL validation failed, cannot access the actual page')
    process.exit(1)
  }

  const payload = {
    host: HOST,
    key: KEY,
    keyLocation: KEY_LOCATION,
    urlList: verifiedUrls,
  }

  // Print the complete Request Body you requested
  console.log('\n📤 Full request body about to be pushed to IndexNow:')
  console.log('─────────────────────────────────────────')
  console.log(JSON.stringify(payload, null, 2))
  console.log('─────────────────────────────────────────')

  try {
    const response = await fetch(ENDPOINT, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json; charset=utf-8' },
      body: JSON.stringify(payload),
    })

    const responseText = await response.text()
    console.log(`\n📨 Response status code: ${response.status}`)
    if (responseText) console.log(`📨 Response content: ${responseText}`)

    // Strictly follow the official documentation: only 200 is success.
    if (response.status === 200) {
      console.log('\n✅ [200 OK] URL submitted successfully!')
      writeUrlsToOutput(verifiedUrls)
      process.exit(0)
    } else if (response.status === 202) {
      // Special notes for 202
      console.log('\n⚠️ [202 Accepted] Request has been received, queued for processing (Bing will usually complete the crawl asynchronously later).')
      writeUrlsToOutput(verifiedUrls)
      process.exit(0) // Still considered a successful process.
    }

    // failure branch
    let errMsg = ''
    switch (response.status) {
      case 400: errMsg = '[400 Bad Request] Invalid request format'; break;
      case 403: errMsg = `[403 Forbidden] Invalid key. Please check:\n  1. Is the file accessible via ${KEY_LOCATION}?\n  2. Does the file content exactly match the KEY?`; break;
      case 422: errMsg = '[422 Unprocessable Entity] URL does not belong to this host, or Key does not match the protocol schema'; break;
      case 429: errMsg = '[429 Too Many Requests] Pushing too frequently'; break;
      default: errMsg = `[${response.status}] Unknown status code, response: ${responseText}`;
    }
    console.error(`\n❌ ${errMsg}`)
    writeErrorToOutput(errMsg)
    process.exit(1)
  } catch (error) {
    const errMsg = `Network request exception: ${error.message}`
    console.error(`\n❌ ${errMsg}`)
    writeErrorToOutput(errMsg)
    process.exit(1)
  }
}

main()

2. Integrate into the existing release pipeline ​

Do not create a new Action file., directly in your .github/workflows/release-bump.yaml in, find the steps for a successful deployment (such as Execute Remote Deployment), in it after Insert the following steps:

yaml
      # ============================================
      # 13.5 IndexNow Push (push only after successful deployment to ensure the live site is already on the new version)
      # ============================================
      - name: Push to IndexNow
        id: indexnow
        if: success()
        continue-on-error: true
        env:
           CHANGED_FILES: ${{ env.CHANGED_FILES }}
        run: |
           if [ -z "$CHANGED_FILES" ]; then
             echo "ℹ️ No Markdown changes in this release, skipping IndexNow push"
             echo "pushed_count=0" >> $GITHUB_OUTPUT
             exit 0
           fi
           node scripts/indexnow.mjs

      # ============================================
      # 13.6 IndexNow push success email (lists all submitted URLs)
      # ============================================
      - name: Send IndexNow success email
        if: steps.indexnow.outcome == 'success' && steps.indexnow.outputs.pushed_count != '0'
        uses: dawidd6/action-send-mail@v3
        with:
           # ... your SMTP configuration ...
           html_body: |
              <h2>🚀 IndexNow Push Successful</h2>
              <ul>
                ${{ steps.indexnow.outputs.pushed_urls_html }}
              </ul>

FAQs and Pitfall Avoidance Guide ​

  • Why does it keep returning 403 Forbidden? Go to the browser and visit your https://域名/Key.txt, ensure the file is in the root directory, and inside it Absolutely must not contain any extra spaces or line breaks..

  • Does the URL include it or not?.html? This depends on your VitePress configuration. If it is not enabled cleanUrls, must be added in the conversion script .html A failed liveness probe usually means the URL was misspelled.

  • Does receiving 202 count as success? Calculate.200 indicates that the synchronization processing was successful,202 Indicates it has been received and entered the asynchronous queue; both are fine.

Summary: SEO is a long-distance race, but the starting line must be automated. ​

After configuration is complete, you can view the push history in the IndexNow menu in the Bing backend. Now, every time your VitePress blog is published, Bing can learn about it within minutes.

⚠️Important reminder: Fast indexing ≠ good rankings. IndexNow can only help you solve the problem of “being seen” (accelerating indexing), while “ranking higher” (improving rankings) still depends on high-quality content and quality backlink building.

Hurry up and give it a try!

Appendix: Complete release-bump.yaml ​

For ease of comparison, here is the complete set actually used in my blog.release-bump.yaml. It includes version number extraction, build, compression, SCP, remote Nginx deployment, and IndexNow push.

yaml
name: Auto Bump Version on Release

on:
  release:
    types: [created]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
      # 1. Check out the code
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.release.target_commitish }}
          fetch-depth: 0

      # 2. Set up the Node.js environment
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      # 3. Install dependencies
      - name: Install dependencies
        run: npm ci

      # 4. Extract the version number
      - name: Extract Version
        id: extract-version
        run: |
          TAG=${GITHUB_REF#refs/tags/}
          VERSION=$(echo "$TAG" | sed 's/^v//')
          echo "version=$VERSION" >> $GITHUB_OUTPUT

      # 5. Extract project name
      - name: Extract Project Name
        id: project-name
        run: |
          PROJECT_NAME=$(node -p "require('./package.json').name")
          echo "project_name=$PROJECT_NAME" >> $GITHUB_OUTPUT

      # 6. Calculate the Markdown changes involved in this release (for IndexNow)
      - name: Get changed md files since last release
        id: changed-files
        run: |
          # Disable path escaping to prevent Chinese filenames from being converted to octal.
          git config core.quotepath false
          CURRENT_TAG=${GITHUB_REF#refs/tags/}
          # Get the nearest tag before the current tag (excluding the current tag itself)
          PREV_TAG=$(git describe --tags --abbrev=0 "${CURRENT_TAG}^" 2>/dev/null || echo "")
          if [ -z "$PREV_TAG" ]; then
            echo "ℹ️ First release, unable to compare with the previous tag, skipping IndexNow change calculation"
            echo "CHANGED_FILES=" >> $GITHUB_ENV
          else
            echo "📌 Comparison range: ${PREV_TAG} .. ${CURRENT_TAG}"
            FILES=$(git diff --name-only "${PREV_TAG}" "${CURRENT_TAG}" --diff-filter=AM | tr '\n' ' ')
            echo "Changed files: ${FILES}"
            echo "CHANGED_FILES=${FILES}" >> $GITHUB_ENV
          fi

      # 7. Update the package.json version number
      - name: Update Version
        run: |
          npm version ${{ steps.extract-version.outputs.version }} --no-git-tag-version
          cat package.json | grep version

      # 8. Build a VitePress project
      - name: Build VitePress
        env:
          NODE_OPTIONS: "--max-old-space-size=4096"
        run: npm run docs:build

      # 9. Create a compressed archive
      - name: Create Distribution Archive
        id: create-archive
        run: |
          if [ ! -d "docs/.vitepress/dist" ]; then
            echo "Error: ./docs/.vitepress/dist/ directory not found!"
            exit 1
          fi

          echo "Contents of ./docs/.vitepress/dist/ directory:"
          ls -la ./docs/.vitepress/dist/

          PROJECT_NAME="${{ steps.project-name.outputs.project_name }}"
          VERSION="${{ steps.extract-version.outputs.version }}"
          ARCHIVE_NAME="${PROJECT_NAME}-${VERSION}.tar.gz"

          tar -czf ${ARCHIVE_NAME} -C ./docs/.vitepress/dist/ .

          echo "Archive created successfully: ${ARCHIVE_NAME}"
          echo "archive_name=${ARCHIVE_NAME}" >> $GITHUB_OUTPUT
          ls -la *.tar.gz

      # 10. Upload to GitHub Release
      - name: Upload Release Assets
        uses: ncipollo/release-action@v1
        with:
          artifacts: "./${{ steps.create-archive.outputs.archive_name }}"
          allowUpdates: true
          omitBody: true
          omitName: true
          token: ${{ secrets.ACCESS_TOKEN }}
          makeLatest: "true"

      # 11. Commit version number change
      - name: Commit Changes
        run: |
          git config user.name "GitHub Actions"
          git config user.email "[email protected]"
          git add package.json package-lock.json
          git commit -m "chore(release): bump to ${{ steps.extract-version.outputs.version }} [skip ci]"
          git push origin HEAD:${{ github.event.release.target_commitish }}

      # ============================================
      # Build success email
      # ============================================
      - name: Send build success email
        if: success()
        uses: dawidd6/action-send-mail@v3
        with:
          server_address: smtp.gmail.com
          server_port: 587
          username: ${{ vars.GMAIL_SMTP_USER }}
          password: ${{ secrets.GMAIL_SMTP_PASSWORD }}
          subject: "✅ VitePress Build Successful - ${{ github.repository }}"
          to: ${{ vars.NOTIFY_EMAILS }}
          from: "GitHub Actions <${{ vars.GMAIL_SMTP_USER }}>"
          body: |
            🎉 VitePress project built successfully!

            Repository: ${{ github.repository }}
            Version: ${{ steps.extract-version.outputs.version }}
            Triggered by: ${{ github.actor }}
            Time: ${{ github.event.repository.updated_at }}
            Status: ✅ Success

            Details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}

      # ============================================
      # Build failure email
      # ============================================
      - name: Send build failure email
        if: failure()
        uses: dawidd6/action-send-mail@v3
        with:
          server_address: smtp.gmail.com
          server_port: 587
          username: ${{ vars.GMAIL_SMTP_USER }}
          password: ${{ secrets.GMAIL_SMTP_PASSWORD }}
          ❌ VitePress Build Failed - ${{ github.repository }}
          to: ${{ vars.NOTIFY_EMAILS }}
          from: "GitHub Actions <${{ vars.GMAIL_SMTP_USER }}>"
          body: |
            🚨 VitePress project build failed, please check immediately!

            Repository: ${{ github.repository }}
            Version: ${{ steps.extract-version.outputs.version }}
            Triggered by: ${{ github.actor }}
            Time: ${{ github.event.repository.updated_at }}
            Status: ❌ Failed

            Details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
            Please click the link above to view detailed error information.

      # ============================================
      # Deployment phase begins
      # ============================================

      # 12. SCP transfer to the server
      - name: Deploy to Server (SCP)
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.DEPLOY_SSH_KEY }}
          port: ${{ secrets.DEPLOY_PORT || 22 }}
          source: "./${{ steps.create-archive.outputs.archive_name }}"
          target: "/tmp/${{ steps.project-name.outputs.project_name }}-build-${{ steps.extract-version.outputs.version }}/"
          strip_components: 1

      # 13. SSH Execute Remote Deployment
      - name: Execute Remote Deployment
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.DEPLOY_SSH_KEY }}
          port: ${{ secrets.DEPLOY_PORT || 22 }}
          script: |
            set -e

            PROJECT_NAME="${{ steps.project-name.outputs.project_name }}"
            VERSION="${{ steps.extract-version.outputs.version }}"
            ARCHIVE_NAME="${PROJECT_NAME}-${VERSION}.tar.gz"
            DEPLOY_DIR="/var/www/${PROJECT_NAME}"
            BUILD_DIR="/tmp/${PROJECT_NAME}-build-${VERSION}"

            echo "=========================================="
            echo "Deploying ${PROJECT_NAME} version ${VERSION}"
            echo "=========================================="

            # 1. Create a temporary directory and extract
            mkdir -p ${BUILD_DIR}
            echo "📦 Extracting ${ARCHIVE_NAME}..."
            tar -xzf "/tmp/${PROJECT_NAME}-build-${VERSION}/${ARCHIVE_NAME}" -C ${BUILD_DIR}

            # 2. Back up the current version
            if [ -d "$DEPLOY_DIR" ]; then
              BACKUP_DIR="/var/www/${PROJECT_NAME}-backup-$(date +%Y%m%d_%H%M%S)"
              echo "📦 Creating backup: ${BACKUP_DIR}"
              cp -r "$DEPLOY_DIR" "$BACKUP_DIR"
              # Keep the last 5 backups
              ls -t /var/www/${PROJECT_NAME}-backup-* 2>/dev/null | tail -n +6 | xargs rm -rf || true
            else
              📁 Creating deployment directory: ${DEPLOY_DIR}
              mkdir -p "$DEPLOY_DIR"
            fi

            # 3. Deploy new files
            echo "📤 Deploying files..."
            rm -rf ${DEPLOY_DIR}/*
            cp -r ${BUILD_DIR}/* ${DEPLOY_DIR}/

            # 4. Set permissions
            chown -R www-data:www-data ${DEPLOY_DIR} || chown -R $(whoami):$(whoami) ${DEPLOY_DIR}
            chmod -R 755 ${DEPLOY_DIR}

            # 5. Restart the NGINX service
            echo "🔄 Reloading nginx service..."
            if command -v nginx &> /dev/null; then
              nginx -s reload || systemctl reload nginx || echo "⚠️ Nginx reload skipped (service not found)"
            else
              echo "⚠️ Nginx not found, skipping reload"
            fi

            # 6. Clean up temporary files
            echo "🧹 Cleaning up..."
            rm -rf ${BUILD_DIR}
            rm -rf "/tmp/${PROJECT_NAME}-build-${VERSION}"

            # 7. Display deployment results
            echo "=========================================="
            echo "✅ Deployment completed successfully!"
            📍 Deployed to: ${DEPLOY_DIR}
            echo "📌 Version: ${VERSION}"
            echo "=========================================="

      # ============================================
      # 13.5 IndexNow Push (push only after successful deployment to ensure the live site is already on the new version)
      # ============================================
      - name: Push to IndexNow
        id: indexnow
        if: success()
        continue-on-error: true
        env:
          CHANGED_FILES: ${{ env.CHANGED_FILES }}
        run: |
          if [ -z "$CHANGED_FILES" ]; then
            echo "ℹ️ No Markdown changes in this release, skipping IndexNow push"
            echo "pushed_count=0" >> $GITHUB_OUTPUT
            exit 0
          fi
          node scripts/indexnow.mjs

      # ============================================
      # 13.6 IndexNow push success email (lists all submitted URLs)
      # ============================================
      - name: Send IndexNow success email
        if: steps.indexnow.outcome == 'success' && steps.indexnow.outputs.pushed_count != '0'
        uses: dawidd6/action-send-mail@v3
        with:
          server_address: smtp.gmail.com
          server_port: 587
          username: ${{ vars.GMAIL_SMTP_USER }}
          password: ${{ secrets.GMAIL_SMTP_PASSWORD }}
          subject: 🚀 IndexNow push successful (${{ steps.extract-version.outputs.version }}) - ${{ steps.indexnow.outputs.pushed_count }} URLs
          to: ${{ vars.NOTIFY_EMAILS }}
          from: "GitHub Actions <${{ vars.GMAIL_SMTP_USER }}>"
          html_body: |
            <h2>🚀 IndexNow Push Successful</h2>
            <p>The following <strong>${{ steps.indexnow.outputs.pushed_count }}</strong> URLs have been successfully submitted to Bing and are awaiting crawling:</p>
            <ul>
              ${{ steps.indexnow.outputs.pushed_urls_html }}
            </ul>
            <hr>
            <p><strong>Repository:</strong>${{ github.repository }}</p>
            <p><strong>Version:</strong>${{ steps.extract-version.outputs.version }}</p>
            <p><strong>Triggered by:</strong>${{ github.actor }}</p>
            <p><strong>Details:</strong><a href="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}">View Action run record</a></p>

      # ============================================
      # 13.7 IndexNow Push Failure Email (with error reasons and troubleshooting tips)
      # ============================================
      - name: Send IndexNow failure email
        if: steps.indexnow.outcome == 'failure'
        uses: dawidd6/action-send-mail@v3
        with:
          server_address: smtp.gmail.com
          server_port: 587
          username: ${{ vars.GMAIL_SMTP_USER }}
          password: ${{ secrets.GMAIL_SMTP_PASSWORD }}
          ⚠️ IndexNow push failed (${{ steps.extract-version.outputs.version }}) - needs investigation
          to: ${{ vars.NOTIFY_EMAILS }}
          from: "GitHub Actions <${{ vars.GMAIL_SMTP_USER }}>"
          html_body: |
            <h2>⚠️ IndexNow Push Failed</h2>
            <p>This release has been <strong>successfully deployed</strong>, but the IndexNow push failed. Bing will not receive new content notifications for now, and you need to manually investigate.</p>
            <h3>Error Message</h3>
            <pre style="background:#f5f5f5;padding:12px;border-radius:4px;">${{ steps.indexnow.outputs.error_msg }}</pre>
            <h3>Common troubleshooting directions</h3>
            <ul>
              <li><strong>403</strong>: Check whether <code>docs/public/74c02e84c5fe401bb1768e0eae41deb6.txt</code> is publicly accessible over the internet and whether its content exactly matches the Key (no trailing newline)</li>
              <li><strong>422</strong>: Check whether the URL generated by the script belongs to the <code>fishfinal.com</code> domain</li>
              <li><strong>429</strong>: Push too frequent, manually rerun later</li>
              <li><strong>Network error</strong>: Check the GitHub Actions egress network, or rerun later</li>
            </ul>
            <hr>
            <p><strong>Repository:</strong>${{ github.repository }}</p>
            <p><strong>Version:</strong>${{ steps.extract-version.outputs.version }}</p>
            <p><strong>Triggered by:</strong>${{ github.actor }}</p>
            <p><strong>Details:</strong><a href="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}">View Action run record</a></p>

      # 14. Clean up local compressed packages
      - name: Cleanup Local Archive
        if: always()
        run: |
          rm -f ${{ steps.create-archive.outputs.archive_name }} 2>/dev/null || true
          echo "🧹 Local archive cleaned up"

      # ============================================
      # Deployment successful email
      # ============================================
      - name: Send deploy success email
        if: success()
        uses: dawidd6/action-send-mail@v3
        with:
          server_address: smtp.gmail.com
          server_port: 587
          username: ${{ vars.GMAIL_SMTP_USER }}
          password: ${{ secrets.GMAIL_SMTP_PASSWORD }}
          subject: ✅ VitePress Deployment Successful - ${{ github.repository }}
          to: ${{ vars.NOTIFY_EMAILS }}
          from: "GitHub Actions <${{ vars.GMAIL_SMTP_USER }}>"
          body: |
            🎉 VitePress project deployed successfully!

            Repository: ${{ github.repository }}
            Version: ${{ steps.extract-version.outputs.version }}
            Triggered by: ${{ github.actor }}
            Time: ${{ github.event.repository.updated_at }}
            Status: ✅ Success

            🌐 Visit URL: ${{ vars.DEPLOY_WEBSITE_DOMAIN }}
            Details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}

      # ============================================
      # Deployment failure email
      # ============================================
      - name: Send deploy failure email
        if: failure()
        uses: dawidd6/action-send-mail@v3
        with:
          server_address: smtp.gmail.com
          server_port: 587
          username: ${{ vars.GMAIL_SMTP_USER }}
          password: ${{ secrets.GMAIL_SMTP_PASSWORD }}
          subject: "❌ VitePress deployment failed - ${{ github.repository }}"
          to: ${{ vars.NOTIFY_EMAILS }}
          from: "GitHub Actions <${{ vars.GMAIL_SMTP_USER }}>"
          body: |
            🚨 VitePress project deployment failed, please check immediately!

            Repository: ${{ github.repository }}
            Version: ${{ steps.extract-version.outputs.version }}
            Triggered by: ${{ github.actor }}
            Time: ${{ github.event.repository.updated_at }}
            Status: ❌ Failed

            Details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
            Please click the link above to view detailed error information.
最后更新2026/11/07 14:16
如果你觉得这篇文章有帮助,或者想聊聊技术、工作,欢迎通过下面方式联系我:
contact fishfinal