You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过GitHub Actions循环发送含变量的邮件通知

问题描述

需要将Client-Credentials-Watchdog.yaml配置为每日定时Cron任务,实现以下功能:

  1. 获取所有应用的凭证详情
  2. 筛选出有效期不足30天的凭证
  3. 向每个应用的所有者(对应Get App by ID响应中的lastModifiedBy字段)发送包含凭证信息的邮件
  4. 遍历所有符合条件的应用,逐个发送通知

当前实现的问题:GitHub Actions工作流仅在末尾向硬编码邮箱发送单封邮件,无法动态循环给不同所有者发送对应通知。


解决方案

步骤1:修改Groovy脚本,收集待通知数据

修改Client-Credentials-Watchdog-script.groovy,在遍历过程中收集所有需要发送通知的条目,输出为JSON格式文件供后续Actions步骤读取:

#!/usr/bin/env groovy
import groovy.json.JsonSlurper
import groovy.json.JsonBuilder
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter

def host = "example.com"
def USER = System.getenv("SECRET_API_KEY")
def jsonSlurper = new JsonSlurper()
def notifications = []

// 获取应用列表
def getAppsListResponse = ["bash", "-c", "curl -k -s -w '\n%{response_code}' --header 'Authorization:Basic $USER' --header 'Content-Type: application/json' --request GET https://${host}/v1/organizations/org/apps"].execute().text
println "*** GET Apps List Response = $getAppsListResponse ***"

if (getAppsListResponse.contains('200')){
    println "*** Success ***"
    def appsArray = getAppsListResponse.find(/\[.*\]/)
    def appIds = appsArray.replaceAll(/\[|\]/, '').split(',').collect { it.trim().replaceAll('"', '') }
    println "*** App IDs: $appIds ***"
    
    for(String appId : appIds){
        println "*** Processing App ID: ${appId} ***"
        // 获取单个应用详情
        def getAppByIDResponse = ["bash", "-c", "curl -k -s -w '\n%{response_code}' --header 'Authorization:Basic $USER' --header 'Content-Type: application/json' --request GET https://${host}/v1/organizations/org/apps/${appId}"].execute().text
        
        if(getAppByIDResponse.contains('200')){
            println "*** App found ***"
            def splitStatusCode = getAppByIDResponse.split('} 200')
            def appDetails = jsonSlurper.parseText(splitStatusCode[0] + '}')

            def ownerEmail = appDetails.lastModifiedBy
            def appDisplayName = appDetails.attributes.find { it.name == "DisplayName" }?.value ?: appDetails.name
            def thirtyDaysInMs = 30 * 24 * 60 * 60 * 1000
            def currentUnixTime = Instant.now().toEpochMilli()
            def dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault())

            // 遍历凭证,筛选即将过期的条目
            for (def credential : appDetails.credentials){
                def expiresAtUnix = credential.expiresAt
                if((expiresAtUnix - currentUnixTime) <= thirtyDaysInMs){
                    def expiresAtFormatted = dateFormatter.format(Instant.ofEpochMilli(expiresAtUnix))
                    notifications.add([
                        to: ownerEmail,
                        appName: appDisplayName,
                        expiresAt: expiresAtFormatted,
                        consumerKey: credential.consumerKey
                    ])
                    println "*** Added notification for ${ownerEmail} - ${appDisplayName} ***"
                } else {
                    println "*** Skip sending notification ***"
                }
            }
        } else {
            println "*** App not found ***"
            System.exit(1)
        }
    }
    
    // 将通知列表写入JSON文件
    def jsonBuilder = new JsonBuilder(notifications)
    new File("notifications.json").write(jsonBuilder.toPrettyString())
    println "*** Generated notifications.json with ${notifications.size} entries ***"
} else {
    println "*** Failed to fetch apps list ***"
    System.exit(1)
}

步骤2:修改GitHub Actions工作流,实现循环发送邮件

修改Client-Credentials-Watchdog.yaml,添加定时Cron触发,读取Groovy生成的notifications.json,通过jq解析后循环调用独立邮件工作流:

name: Client-Credentials-Watchdog
on:
  workflow_dispatch:
  schedule:
    # 每日UTC时间0点执行(可根据时区调整,如北京时间16点改为'0 8 * * *')
    - cron: '0 0 * * *'
jobs:
  build:
    runs-on: [abc]
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3
      
      - name: Install dependencies
        run: |
          sudo apt-get update && sudo apt-get install -y groovy jq
      
      - name: Generate notification list
        env:
          SECRET_API_KEY: ${{ secrets.USER }}
        run: groovy Client-Credentials-Watchdog-script.groovy
      
      - name: Send notifications
        run: |
          # 遍历JSON中的每个通知条目
          jq -c '.[]' notifications.json | while read -r item; do
            TO_EMAIL=$(echo "$item" | jq -r '.to')
            APP_NAME=$(echo "$item" | jq -r '.appName')
            EXPIRES_AT=$(echo "$item" | jq -r '.expiresAt')
            CONSUMER_KEY=$(echo "$item" | jq -r '.consumerKey')
            
            # 调用独立邮件工作流发送通知
            gh workflow run send-single-email.yml \
              -f to-email="$TO_EMAIL" \
              -f subject="Action Required: Renew credentials for $APP_NAME" \
              -f body="Please renew the credentials for app <b>$APP_NAME</b> (Consumer Key: $CONSUMER_KEY) which will expire on $EXPIRES_AT."
          done

步骤3:创建独立邮件发送工作流

新建send-single-email.yml文件,用于处理单封邮件的发送逻辑:

name: Send Single Email
on:
  workflow_dispatch:
    inputs:
      to-email:
        description: 'Recipient email'
        required: true
      subject:
        description: 'Email subject'
        required: true
      body:
        description: 'Email body (HTML)'
        required: true

jobs:
  send-email:
    runs-on: [abc]
    steps:
      - name: Send Email
        uses: xyz/action-send-email@v4
        with:
          from-email: 'noreply-github@example.com'
          to-email: ${{ github.event.inputs.to-email }}
          subject: ${{ github.event.inputs.subject }}
          email-format: 'html'
          body: ${{ github.event.inputs.body }}

关键说明

  1. 定时任务:通过schedule字段的Cron表达式设置每日执行时间,可根据业务需求调整时区。
  2. 数据传递:Groovy脚本生成结构化JSON文件,统一存储所有待发送通知的核心信息,避免重复调用API。
  3. 循环发送:使用jq解析JSON条目,通过gh workflow run触发独立邮件工作流,实现动态遍历发送。
  4. 可读性优化:将Unix时间戳转换为可读的日期格式,提升邮件内容的友好性。

内容的提问来源于stack exchange,提问作者Reddy Rohit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 12:54:50