{"id":31626358,"url":"https://github.com/marcus-hooper/send-teams-notification","last_synced_at":"2026-04-19T07:33:36.358Z","repository":{"id":316661551,"uuid":"1064350096","full_name":"marcus-hooper/send-teams-notification","owner":"marcus-hooper","description":"Reusable GitHub Action to send Adaptive Card notifications to Microsoft Teams via webhook. PowerShell 7, emoji-safe, status styling, collapsible commits.","archived":false,"fork":false,"pushed_at":"2026-04-14T02:41:26.000Z","size":192,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-14T04:21:25.291Z","etag":null,"topics":["adaptive-cards","ci-cd","composite-action","deployment-notifications","devops","github-actions","microsoft","microsoft-teams","notifications","powershell","webhook"],"latest_commit_sha":null,"homepage":"","language":"PowerShell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/marcus-hooper.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-25T22:54:55.000Z","updated_at":"2026-04-14T02:41:30.000Z","dependencies_parsed_at":"2025-09-26T00:32:49.883Z","dependency_job_id":null,"html_url":"https://github.com/marcus-hooper/send-teams-notification","commit_stats":null,"previous_names":["marcus-hooper/send-teams-notification"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/marcus-hooper/send-teams-notification","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcus-hooper%2Fsend-teams-notification","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcus-hooper%2Fsend-teams-notification/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcus-hooper%2Fsend-teams-notification/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcus-hooper%2Fsend-teams-notification/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marcus-hooper","download_url":"https://codeload.github.com/marcus-hooper/send-teams-notification/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcus-hooper%2Fsend-teams-notification/sbom","scorecard":{"id":1242613,"data":{"date":"2026-01-28T15:32:40Z","repo":{"name":"github.com/marcus-hooper/send-teams-notification","commit":"b15606f0768a25d48fcb9d43f23fd3402bd0977b"},"scorecard":{"version":"v5.3.0","commit":"c22063e786c11f9dd714d777a687ff7c4599b600"},"score":7.9,"checks":[{"name":"Maintained","score":10,"reason":"24 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#binary-artifacts"}},{"name":"Dependency-Update-Tool","score":10,"reason":"update tool detected","details":["Info: detected update tool: Dependabot: .github/dependabot.yml:1"],"documentation":{"short":"Determines if the project uses a dependency update tool.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#dependency-update-tool"}},{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: SECURITY.md:1","Info: Found linked content: SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: SECURITY.md:1","Info: Found text in security policy: SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#security-policy"}},{"name":"Code-Review","score":0,"reason":"Found 0/25 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":10,"reason":"all dependencies are pinned","details":["Info:  24 out of  24 GitHub-owned GitHubAction dependencies pinned","Info:  24 out of  24 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#pinned-dependencies"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":10,"reason":"GitHub workflow tokens follow principle of least privilege","details":["Info: jobLevel 'contents' permission set to 'read': .github/workflows/ci.yml:112","Warn: jobLevel 'checks' permission set to 'write': .github/workflows/ci.yml:113","Info: jobLevel 'actions' permission set to 'read': .github/workflows/codeql.yml:38","Info: jobLevel 'contents' permission set to 'read': .github/workflows/codeql.yml:39","Warn: jobLevel 'contents' permission set to 'write': .github/workflows/dependabot-auto-merge.yml:21","Info: jobLevel 'contents' permission set to 'read': .github/workflows/labels.yml:24","Warn: jobLevel 'contents' permission set to 'write': .github/workflows/release.yml:21","Info: jobLevel 'contents' permission set to 'read': .github/workflows/schedule.yml:28","Info: jobLevel 'contents' permission set to 'read': .github/workflows/scorecard.yml:29","Info: jobLevel 'actions' permission set to 'read': .github/workflows/scorecard.yml:31","Info: jobLevel 'issues' permission set to 'read': .github/workflows/scorecard.yml:32","Info: jobLevel 'pull-requests' permission set to 'read': .github/workflows/scorecard.yml:33","Info: jobLevel 'checks' permission set to 'read': .github/workflows/scorecard.yml:34","Info: jobLevel 'actions' permission set to 'read': .github/workflows/scorecard.yml:82","Info: jobLevel 'contents' permission set to 'read': .github/workflows/scorecard.yml:80","Info: jobLevel 'contents' permission set to 'read': .github/workflows/security.yml:35","Info: jobLevel 'contents' permission set to 'read': .github/workflows/security.yml:64","Info: jobLevel 'contents' permission set to 'read': .github/workflows/security.yml:99","Info: jobLevel 'contents' permission set to 'read': .github/workflows/validate.yml:29","Info: topLevel 'contents' permission set to 'read': .github/workflows/ci.yml:21","Info: found token with 'none' permissions: .github/workflows/codeql.yml:1","Info: found token with 'none' permissions: .github/workflows/dependabot-auto-merge.yml:1","Info: found token with 'none' permissions: .github/workflows/labels.yml:1","Info: found token with 'none' permissions: .github/workflows/release.yml:1","Info: found token with 'none' permissions: .github/workflows/schedule.yml:1","Info: found token with 'none' permissions: .github/workflows/scorecard.yml:1","Info: found token with 'none' permissions: .github/workflows/security.yml:1","Info: found token with 'none' permissions: .github/workflows/validate.yml:1"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":7,"reason":"SAST tool detected but not run on all commits","details":["Info: SAST configuration detected: CodeQL","Warn: 1 commits out of 3 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#sast"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#signed-releases"}},{"name":"CI-Tests","score":10,"reason":"3 out of 3 merged PRs checked by a CI test -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project runs tests before pull requests are merged.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#ci-tests"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#branch-protection"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#fuzzing"}},{"name":"Contributors","score":3,"reason":"project has 1 contributing companies or organizations -- score normalized to 3","details":["Info: found contributions from: butegroup"],"documentation":{"short":"Determines if the project has a set of contributors from multiple organizations (e.g., companies).","url":"https://github.com/ossf/scorecard/blob/c22063e786c11f9dd714d777a687ff7c4599b600/docs/checks.md#contributors"}}]},"last_synced_at":"2026-01-29T06:59:56.594Z","repository_id":316661551,"created_at":"2026-01-29T06:59:56.595Z","updated_at":"2026-01-29T06:59:56.595Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31998901,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T20:23:30.271Z","status":"online","status_checked_at":"2026-04-19T02:00:07.110Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["adaptive-cards","ci-cd","composite-action","deployment-notifications","devops","github-actions","microsoft","microsoft-teams","notifications","powershell","webhook"],"created_at":"2025-10-06T19:51:05.586Z","updated_at":"2026-04-19T07:33:36.345Z","avatar_url":"https://github.com/marcus-hooper.png","language":"PowerShell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Send Teams Notification (Adaptive Cards)\n\n[![CI](https://github.com/marcus-hooper/send-teams-notification/actions/workflows/ci.yml/badge.svg)](https://github.com/marcus-hooper/send-teams-notification/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/marcus-hooper/send-teams-notification/graph/badge.svg)](https://codecov.io/gh/marcus-hooper/send-teams-notification)\n[![CodeQL](https://github.com/marcus-hooper/send-teams-notification/actions/workflows/codeql.yml/badge.svg)](https://github.com/marcus-hooper/send-teams-notification/actions/workflows/codeql.yml)\n[![Security](https://github.com/marcus-hooper/send-teams-notification/actions/workflows/security.yml/badge.svg)](https://github.com/marcus-hooper/send-teams-notification/actions/workflows/security.yml)\n[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/marcus-hooper/send-teams-notification/badge)](https://scorecard.dev/viewer/?uri=github.com/marcus-hooper/send-teams-notification)\n[![GitHub release](https://img.shields.io/github/v/release/marcus-hooper/send-teams-notification)](https://github.com/marcus-hooper/send-teams-notification/releases)\n[![PowerShell 7](https://img.shields.io/badge/PowerShell-7-blue.svg)](https://docs.microsoft.com/en-us/powershell/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nA composite GitHub Action that posts Adaptive Cards to Microsoft Teams via Incoming Webhooks.\n\n## Important Notice\n\n\u003e **Office 365 Connectors Deprecation**: Microsoft has announced that Office 365 Connectors, including Incoming Webhooks, are being retired. While this action continues to work with existing webhooks, Microsoft recommends migrating to [Workflows (Power Automate)](https://learn.microsoft.com/en-us/power-automate/teams/send-a-message-in-teams) for new integrations. See Microsoft's [retirement announcement](https://devblogs.microsoft.com/microsoft365dev/retirement-of-office-365-connectors-within-microsoft-teams/) for timeline and migration guidance.\n\n## Features\n\n- Sends rich Adaptive Card notifications to Microsoft Teams channels\n- Status styling with colors and emoji (success/failure/warning)\n- Displays repository, actor, and optional environment information\n- Optional collapsible commit history section\n- UTF-8-safe emoji handling (constructed from Unicode code points)\n- PowerShell 7 with no external dependencies\n- Works on Linux, Windows, and macOS runners\n\n## Quick Start\n\nMinimal usage (sends on success and failure):\n\n```yaml\njobs:\n  notify:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Send Teams notification\n        if: ${{ always() }}\n        uses: marcus-hooper/send-teams-notification@v1\n        with:\n          job_status: ${{ job.status }}\n          webhook_url: ${{ secrets.TEAMS_WEBHOOK_URL }}\n```\n\n\u003e **Note**: The `if: ${{ always() }}` condition ensures the notification step runs regardless of whether previous steps succeeded or failed. Without this, the notification would be skipped when the job fails—which is usually when you most want to be notified.\n\nWith environment and custom title:\n\n```yaml\n- name: Send Teams notification\n  if: ${{ always() }}\n  uses: marcus-hooper/send-teams-notification@v1\n  with:\n    job_status: ${{ job.status }}\n    environment: production\n    card_title: \"🔔 Deployment\"\n    webhook_url: ${{ secrets.TEAMS_WEBHOOK_URL }}\n```\n\nWith commit messages (collapsible section in card):\n\n```yaml\n- name: Get recent commits\n  id: commits\n  run: |\n    COMMITS=$(git log --pretty=format:'{\"title\":\"%h\",\"value\":\"[%s](https://github.com/${{ github.repository }}/commit/%H)\"}' -3 | jq -s '.')\n    echo \"json=$COMMITS\" \u003e\u003e $GITHUB_OUTPUT\n\n- name: Send Teams notification\n  if: ${{ always() }}\n  uses: marcus-hooper/send-teams-notification@v1\n  with:\n    job_status: ${{ job.status }}\n    commit_messages: ${{ steps.commits.outputs.json }}\n    webhook_url: ${{ secrets.TEAMS_WEBHOOK_URL }}\n```\n\n\u003e **Note**: The git log format above may produce invalid JSON if commit messages contain double quotes or backslashes. For repositories with complex commit messages, consider using `jq` to properly escape the values, or limit to commit SHAs only.\n\n### Complete Workflow Example\n\nHere's a complete deployment workflow with Teams notification:\n\n```yaml\nname: Deploy and Notify\n\non:\n  push:\n    branches: [main]\n\njobs:\n  deploy:\n    name: Deploy Application\n    runs-on: ubuntu-latest\n    environment: production\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 5\n\n      - name: Deploy to production\n        run: |\n          # Your deployment steps here\n          echo \"Deploying application...\"\n\n      - name: Get recent commits\n        id: commits\n        run: |\n          COMMITS=$(git log --pretty=format:'{\"title\":\"%h\",\"value\":\"[%s](https://github.com/${{ github.repository }}/commit/%H)\"}' -3 | jq -s '.')\n          echo \"json=$COMMITS\" \u003e\u003e $GITHUB_OUTPUT\n\n      - name: Send Teams notification\n        if: ${{ always() }}\n        uses: marcus-hooper/send-teams-notification@v1\n        with:\n          job_status: ${{ job.status }}\n          environment: ${{ github.environment }}\n          card_title: \"🚀 Production Deployment\"\n          commit_messages: ${{ steps.commits.outputs.json }}\n          webhook_url: ${{ secrets.TEAMS_WEBHOOK_URL }}\n```\n\n## Sample Card Output\n\nThe action sends an Adaptive Card to Teams with the following structure:\n\n```\n┌─────────────────────────────────────────────────┐\n│ 🚀 Production Deployment                        │\n├─────────────────────────────────────────────────┤\n│ ✅ Success                                      │\n│                                                 │\n│ Repository    owner/repo                        │\n│ Environment   production                        │\n│ Actor         username                          │\n│                                                 │\n│ ▶ Recent Commits (tap to expand)                │\n│   abc1234  Fix authentication bug               │\n│   def5678  Add new feature                      │\n│   ghi9012  Update dependencies                  │\n│                                                 │\n│ [View Run]                                      │\n└─────────────────────────────────────────────────┘\n```\n\n**Status Styling:**\n| Status | Color | Emoji |\n|--------|-------|-------|\n| Success | Green (#107C10) | ✅ |\n| Failure | Red (#D13438) | ❌ |\n| Cancelled/Other | Yellow (#F2C744) | ⚠️ |\n\n## Inputs\n\n| Input | Required | Default | Description |\n|-------|----------|---------|-------------|\n| `job_status` | Yes | | Status of the job: `success`, `failure`, or `cancelled` |\n| `webhook_url` | Yes | | Teams Incoming Webhook URL (store in secrets) |\n| `commit_messages` | No | `[]` | JSON array for FactSet, e.g. `[{\"title\":\"SHA\",\"value\":\"Message\"}]` |\n| `environment` | No | | Deployment environment label |\n| `card_title` | No | `🔔 GitHub Deployment` | Title for the card |\n| `repository` | No | `github.repository` | Repository name to display |\n| `actor` | No | `github.actor` | Actor name to display |\n| `run_id` | No | `github.run_id` | Workflow run ID for the \"View Run\" link |\n\n## Outputs\n\n| Output | Description |\n|--------|-------------|\n| `sent` | Whether a POST to Teams was attempted |\n| `payload_bytes` | Size of the JSON payload in bytes |\n| `run_url` | Link to the workflow run |\n\n## Requirements\n\n- Any GitHub Actions runner (GitHub-hosted or self-hosted) with PowerShell 7 available\n- Microsoft Teams Incoming Webhook connector configured for the target channel\n\n## Limitations\n\n| Limitation | Details |\n|------------|---------|\n| Teams webhook only | Does not support Bot Framework or Graph API delivery |\n| No retry logic | Single attempt to send; fails immediately on HTTP error |\n| Card size limit | Teams limits Adaptive Cards to ~28KB; large commit lists may be truncated |\n| Webhook rate limits | Teams may throttle frequent webhook calls |\n| No message updates | Cannot update or delete sent cards (webhook limitation) |\n| Encoding sensitivity | Requires UTF-8 without BOM; emoji via code points to avoid YAML issues |\n\n## Troubleshooting\n\n### Common Errors\n\n| Error | Cause | Solution |\n|-------|-------|----------|\n| 400/BadRequest | Teams rejects malformed or oversized cards | Keep payload \u003c ~28 KB |\n| Nothing appears in Teams | Webhook URL invalid or connector disabled | Verify the webhook URL and that the channel has the Incoming Webhook connector |\n| Emoji or characters look wrong | Encoding issues | Ensure your YAML files are UTF-8 without BOM |\n| No commits section | Invalid JSON | Ensure commit_messages is valid JSON (quote properly and avoid YAML mangling) |\n| 403/Forbidden | Webhook URL expired or revoked | Generate a new webhook URL in Teams |\n| Connector not available | Channel permissions restrict connectors | Ask a Teams admin to enable connectors for the channel or team |\n\n### Debug Tips\n\n1. **Check workflow logs** - Expand the \"Send Teams notification\" step for detailed output\n2. **Verify webhook URL** - Test the webhook URL manually with a simple curl/Invoke-RestMethod call\n3. **Check payload size** - The `payload_bytes` output shows the JSON size; keep it under 28KB\n4. **Validate commit JSON** - Add a step to echo `${{ steps.commits.outputs.json }}` to verify format\n5. **Test locally** - Run the PowerShell script directly with environment variables set (see Development section)\n\n## How It Works\n\n1. Builds an Adaptive Card 1.5 with status styling and optional collapsible commit list\n2. Sends via Teams Incoming Webhook as an attachment payload\n3. Derives repo/actor from GitHub context if not provided\n\n## Development\n\n### Requirements\n\n- PowerShell 7+\n- Pester 5.x (for tests)\n- PSScriptAnalyzer (for linting)\n\n### Local Testing\n\n```powershell\n# Run Pester tests\nInvoke-Pester ./tests -Output Detailed\n\n# Run tests with coverage\n$config = New-PesterConfiguration\n$config.Run.Path = './tests'\n$config.Output.Verbosity = 'Detailed'\n$config.CodeCoverage.Enabled = $true\n$config.CodeCoverage.Path = @('./scripts/send-teams.ps1', './scripts/Send-TeamsNotification.psm1')\nInvoke-Pester -Configuration $config\n```\n\n### Linting\n\n```powershell\n# Install PSScriptAnalyzer if needed\nInstall-Module -Name PSScriptAnalyzer -Force -Scope CurrentUser\n\n# Run linter\nInvoke-ScriptAnalyzer -Path ./scripts -Recurse -Settings PSGallery\n```\n\n### Manual Testing\n\nFor end-to-end testing with a real Teams channel:\n\n```powershell\n$env:INPUT_WEBHOOK_URL = 'https://outlook.office.com/webhook/...'\n$env:INPUT_JOB_STATUS = 'success'\n$env:INPUT_REPOSITORY = 'owner/repo'\n$env:INPUT_ACTOR = 'username'\n$env:INPUT_RUN_ID = '123456789'\n./scripts/send-teams.ps1\n```\n\n## Project Structure\n\n```\nsend-teams-notification/\n├── action.yml                  # GitHub Action definition (composite action)\n├── scripts/\n│   ├── send-teams.ps1          # Entry point script\n│   └── Send-TeamsNotification.psm1  # PowerShell module with testable functions\n├── tests/\n│   └── Send-TeamsNotification.Tests.ps1  # Pester unit tests\n├── .github/\n│   ├── dependabot.yml          # Dependabot configuration\n│   ├── labels.yml              # Repository label definitions\n│   ├── PULL_REQUEST_TEMPLATE.md\n│   ├── ISSUE_TEMPLATE/\n│   │   ├── bug_report.yml      # Bug report form\n│   │   ├── feature_request.yml # Feature request form\n│   │   └── config.yml          # Issue template chooser config\n│   └── workflows/\n│       ├── ci.yml              # CI workflow (lint, test, coverage)\n│       ├── codeql.yml          # CodeQL security analysis\n│       ├── dependabot-auto-merge.yml  # Auto-merge Dependabot PRs\n│       ├── labels.yml          # Label synchronization\n│       ├── release.yml         # Major version tag updates\n│       ├── schedule.yml        # Weekly health check\n│       ├── scorecard.yml       # OpenSSF Scorecard analysis\n│       ├── security.yml        # Security scanning\n│       └── validate.yml        # Action validation\n├── README.md                   # This file\n├── CHANGELOG.md                # Version history\n├── CONTRIBUTING.md             # Contribution guidelines\n├── SECURITY.md                 # Security policy\n├── LICENSE                     # MIT License\n└── .gitignore                  # Git ignore patterns\n```\n\n## Contributing\n\nContributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.\n\nQuick start:\n\n1. Check existing [issues](https://github.com/marcus-hooper/send-teams-notification/issues) or open a new one\n2. Fork the repository\n3. Create a feature branch (`git checkout -b feature/my-feature`)\n4. Make your changes and add tests if applicable\n5. Ensure CI passes (lint and test)\n6. Submit a pull request\n\nSee the issue templates for [bug reports](.github/ISSUE_TEMPLATE/bug_report.yml) and [feature requests](.github/ISSUE_TEMPLATE/feature_request.yml).\n\n## Security\n\n- Store `webhook_url` in GitHub Secrets (e.g., `TEAMS_WEBHOOK_URL`)\n- Do not echo or log the webhook URL\n- Review branch protections for workflows that can send notifications\n\nSee [SECURITY.md](SECURITY.md) for security policy and reporting vulnerabilities.\n\n## Related Projects\n\n- [deployment-notification-o365](https://github.com/marcus-hooper/deployment-notification-o365) - Email notifications via Microsoft Graph API\n- [Teams Incoming Webhooks](https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook) - Teams connector setup guide\n- [Adaptive Cards Designer](https://adaptivecards.io/designer/) - Visual card designer tool\n- [Adaptive Cards Schema](https://adaptivecards.io/explorer/) - Card element reference\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for version history.\n\n## License\n\nMIT License - see [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcus-hooper%2Fsend-teams-notification","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcus-hooper%2Fsend-teams-notification","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcus-hooper%2Fsend-teams-notification/lists"}