
The Slack step runs after an iOS build, so it needs a Mac runner that can actually build and sign your app. For a typical Xcode 26 iOS pipeline, pick a Figment M-series Mac from $95/month (exact chip generation and RAM confirmed in the quote) at /mac-hosting/start, or Managed Build Mac at $249/month if you want Figment to set up CI, run backups and monitoring, and include 30 minutes of monthly support. The $29 Figment virtual Intel macOS runs Intel-compatible Xcode toolchains (Xcode 26 or earlier, per Apple's porting guide) and is a fine place to send notifications from Intel builds; choose M-series for Apple-silicon-only targets, GPU work, or higher RAM.
A useful notification carries four fields: status, branch, commit author, and artifact link. This guide delivers a reusable Fastlane lane built on the primary Fastlane slack action docs and a GitHub Actions–native alternative using Slack's documented incoming webhooks flow.
Before you wire this up, confirm the runner cost against your real term. Headline monthlies for M-class offers on 2026-11-10:
| Offer | Monthly | Annual term | Notes |
|---|---|---|---|
| Figment M-series (entry) | $95 | not published | exact chip/RAM in quote; physical supply request-based |
| Figment Managed Build Mac | $249 | not published | one M-series Mac, CI setup, backups, monitoring, 30 min support |
| CloudMac M4 / 16GB / 256GB | $99.99 | $999/year ≈ $83.25/month | 256GB row shows "Reserve now"; 512GB/1TB show "Fully reserved" |
| MacStadium M4.S 16GB/256GB | $149 | multi-year via sales | 99.99% uptime claim and included support per that page |
CloudMac's $999/year is $141 less than twelve months of Figment's $95 entry. That is a real difference on hardware CloudMac specifies as M4/16GB; Figment's $95 is an unspecified M-series entry, not confirmed as M4 or 16GB. If a specific chip and RAM matter, request a Figment quote and compare the quoted configuration. Figment's differentiator is the managed CI work in the $249 tier, not a lower entry sticker.
Dashboard-first feedback forces developers to notice a badge, click to the CI URL, then read the log. For a team shipping twenty or more builds a day, that adds up. A pushed notification reverses the flow: the result arrives in the channel the team already watches. Signal quality matters more than volume: Build failed · feature/payment-refactor · alex · IPA: not produced is actionable; a bare red X is not.
Slack incoming webhooks are per-app and post to a fixed channel chosen at install time. Per Slack's docs, you cannot override the destination channel, username, or icon at post time—create one webhook per channel you want to reach.
https://hooks.slack.com/services/T.../B.../XXX).Store it in your CI secret manager (GitHub Actions: Settings → Secrets and variables → Actions, name SLACK_WEBHOOK_URL). Never paste the raw URL into a committed workflow or Fastfile—Slack revokes leaked webhooks.
Map five fields to GitHub Actions context:
| Field | Value | Source |
|---|---|---|
| Status | success / failure | job.status |
| Branch | main, feature/... | github.ref_name |
| Commit author | GitHub username | github.actor |
| Commit SHA | short hash | github.sha (first 7) |
| Artifact link | workflow run URL | ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} |
For TestFlight uploads, prefer App Store Connect's TestFlight page URL for the app and include the workflow run URL as a fallback so the message is still useful if the upload step failed.
notify_slack Fastlane LaneAdd this lane to fastlane/Fastfile. It reads inputs from env vars so the same lane works in GitHub Actions, Bitrise, Buildkite, or Jenkins:
# fastlane/Fastfile (inside platform :ios do ... end)
lane :notify_slack do |options|
build_ok = options.key?(:success) ? options[:success] :
(ENV.fetch("FASTLANE_LANE_SUCCESS", "false") == "true")
branch = ENV.fetch("GITHUB_REF_NAME", "unknown-branch")
actor = ENV.fetch("GITHUB_ACTOR", "unknown")
run_url = ENV.fetch("WORKFLOW_RUN_URL", "")
sha_short = ENV.fetch("GITHUB_SHA", "")[0, 7]
status_emoji = build_ok ? ":white_check_mark:" : ":x:"
status_text = build_ok ? "Build succeeded" : "Build FAILED"
# Incoming webhooks post to the channel configured at install time;
# do not pass :channel — it is ignored per Slack docs.
slack(
slack_url: ENV.fetch("SLACK_WEBHOOK_URL"),
message: "#{status_emoji} *#{status_text}*",
success: build_ok,
payload: {
"Branch" => branch,
"Author" => actor,
"Commit" => sha_short,
"Run" => run_url
},
default_payloads: [],
fail_on_error: false
)
end
Call it from your main lane with an ensure block so the alert fires even when a prior step raises:
lane :beta do
build_ok = false
begin
match(type: "appstore", readonly: true)
build_app(workspace: "ios/YourApp.xcworkspace", scheme: "YourApp")
upload_to_testflight(skip_waiting_for_build_processing: true)
build_ok = true
ensure
notify_slack(success: build_ok)
end
end
Replace YourApp.xcworkspace and the scheme with your own project's values; do not run this against a scheme name you have not confirmed in xcodebuild -list.
- name: Notify Slack
if: always()
run: bundle exec fastlane notify_slack
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
GITHUB_REF_NAME: ${{ github.ref_name }}
GITHUB_ACTOR: ${{ github.actor }}
GITHUB_SHA: ${{ github.sha }}
WORKFLOW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
FASTLANE_LANE_SUCCESS: ${{ job.status == 'success' && 'true' || 'false' }}
if: always() runs the step on both success and failure. The lane reads FASTLANE_LANE_SUCCESS when not called directly from Ruby.
A red build on main is urgent; a red build on a feature branch usually is not. Gate the broadcast mention on both conditions:
lane :notify_slack do |options|
build_ok = options[:success]
branch = ENV.fetch("GITHUB_REF_NAME", "")
mention = (!build_ok && branch == "main") ? "<!channel> " : ""
slack(
slack_url: ENV.fetch("SLACK_WEBHOOK_URL"),
message: "#{mention}#{build_ok ? ':white_check_mark: Build succeeded' : ':x: Build FAILED'}",
success: build_ok,
default_payloads: [],
fail_on_error: false
)
end
<!channel> is Slack's documented webhook syntax for a channel-wide mention; a literal @channel in a webhook payload does not notify.
Teams without Fastlane can post directly with slackapi/slack-github-action:
- name: Notify Slack (native)
if: always()
uses: slackapi/slack-github-action@v2
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
{
"text": "${{ job.status == 'success' && ':white_check_mark: Build succeeded' || ':x: Build FAILED' }}",
"attachments": [{
"color": "${{ job.status == 'success' && 'good' || 'danger' }}",
"fields": [
{ "title": "Branch", "value": "${{ github.ref_name }}", "short": true },
{ "title": "Author", "value": "${{ github.actor }}", "short": true },
{ "title": "Run", "value": "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View>", "short": false }
]
}]
}
No Ruby on the runner. Use Fastlane when you already have a Fastfile, because ensure fires on exceptions in one place.
The Slack HTTPS call is an outbound request to hooks.slack.com:443. On a Figment M-series runner nothing extra is needed. If your runner sits behind a corporate egress proxy, allow that host in your own firewall. Update any allowlists you actually control—your artifact store, your CI provider's IP list, your proxy—rather than reaching for a third-party IP range that does not apply here.
Set up a Mac runner at Figment Mac hosting and start the order at /mac-hosting/start. Tax and any setup fees apply as stated in your signed offer.
Related guides: GitHub-hosted vs self-hosted Mac runner costs · Speed up CI builds with a RAM disk on Mac · Remote Xcode setup on Windows