GitHub Actions can do more than run tests and deploy software: you can use it to send email from GitHub Actions when a repository event occurs. The direct approach is a workflow step that runs curl against Volanea’s REST API—there is no Volanea GitHub Actions marketplace app to install, and none is required.
What this integration actually does
GitHub Actions is a workflow automation system, not a no-code records database or form builder. Its native triggers are repository and workflow events: a push, pull request activity, release publication, issue update, scheduled run, manual dispatch, and similar events.
For this guide, the concrete trigger is a push event to the main branch. When someone pushes a commit to main—including when a pull request is merged—GitHub starts a workflow run. That workflow runs on a GitHub-hosted runner, reads the push-event payload available to the runner, builds an email request, and sends it to Volanea over HTTPS.
The flow is:
- A commit is pushed to
main. - GitHub Actions starts the workflow defined in
.github/workflows/send-deploy-email.yml. - The runner reads event details such as the repository name, commit SHA, commit message, branch, and actor.
- The runner sends a
POSTrequest tohttps://api.volanea.com/v1/send. - Volanea accepts the transactional email request, applies the sending-domain and suppression checks, and dispatches the message.
This is a direct server-to-server integration. GitHub Actions has outbound HTTP capability because workflow steps can run shell commands and scripts. You do not need Zapier, Make, a custom marketplace action, or an intermediate webhook endpoint merely to send an email after a GitHub event.
That distinction matters. GitHub Actions does not automatically emit a generic outbound HTTP payload to Volanea in the way a traditional webhook configuration does. Instead, GitHub starts a runner and exposes the event data to the workflow through contexts and the event JSON file. Your run step is what turns that event data into a Volanea API request.
Choose the GitHub event that should send the email
A successful integration begins with the right event boundary. Emailing on every low-level event can create noisy, unhelpful notifications. Emailing only after a meaningful repository state change creates a message recipients can act on.
A push to main is a good default for deployment or merge notifications because it reflects a change that has reached the branch your team treats as the primary integration branch. GitHub Actions workflow syntax uses the on key to define the event that creates a workflow run.
on:
push:
branches:
- main
That syntax means the workflow runs for pushes targeting main. A direct push, a merge commit, a squash merge, and a rebase-and-merge can all produce a push event, so do not assume that every notification corresponds to a pull request with the same shape or metadata.
Useful trigger choices
Use the event that matches the message you want to send:
push: deployment notices, changelog summaries, release-branch updates, or post-merge notifications.pull_request: review requests, approval alerts, or notifications when a pull request is opened, synchronized, or closed.release: customer-facing release announcements after a release is published.workflow_dispatch: a manually initiated email, such as a release manager’s approval notice.schedule: a daily CI health summary or an upcoming certificate-expiry reminder.workflow_run: a notification after another workflow completes, such as a production deployment workflow.
For transactional operational email, avoid treating GitHub Actions like a campaign engine. One event should represent one meaningful operational message: “production deployment completed,” “release v2.4.0 published,” or “the nightly backup verification failed.” If you need bulk newsletters, audience segmentation, and marketing automation, use the campaign and workflow capabilities described in the Volanea API reference and setup guides rather than creating a repository run for every recipient.
Understand the GitHub Actions payload before mapping it
A GitHub Actions workflow can access the triggering event through the github context and through the file path in GITHUB_EVENT_PATH. For a push event, GitHub provides repository metadata and push details. The exact event object can vary depending on what happened, which is why a production workflow should tolerate missing optional fields.
Conceptually, a push-event payload includes fields like these:
{
"ref": "refs/heads/main",
"before": "a1b2c3d4",
"after": "e5f6g7h8",
"repository": {
"full_name": "acme/widget-service",
"html_url": "https://github.com/acme/widget-service"
},
"head_commit": {
"id": "e5f6g7h8",
"message": "Deploy version 2.4.0",
"url": "https://github.com/acme/widget-service/commit/e5f6g7h8"
},
"pusher": {
"name": "octocat"
},
"sender": {
"login": "octocat"
}
}
In a workflow, you do not need to copy that entire payload into an email. Instead, select the stable, useful fields. In this example, the email maps GitHub data to Volanea fields as follows:
| GitHub Actions data | Volanea email field | Why it is included |
|---|---|---|
repository.full_name | subject and html | Identifies the repository that changed. |
ref | html | Shows the branch that triggered the workflow. |
after or github.sha | html | Identifies the revision associated with the notification. |
head_commit.message | html and text | Gives recipients a readable summary of the change. |
sender.login | html | Identifies the GitHub user associated with the event. |
github.server_url + repository + commit SHA | html | Creates a direct commit link. |
github.run_id | Idempotency-Key request header | Prevents an email duplicate when the same workflow run is reattempted. |
The runner receives the raw event JSON at GITHUB_EVENT_PATH. Reading that file with jq is safer than injecting arbitrary event text directly into a shell command. Commit messages, branch names, pull request titles, issue bodies, and other repository-controlled values can contain quotes, newlines, shell-looking characters, or HTML-looking characters. Treat every event field as untrusted input, even if your team usually controls the repository.
Create the Volanea credentials and sender first
Before the GitHub workflow can deliver email, create a Volanea secret API key and configure a verified sender address. The single-message endpoint is POST /v1/send at the https://api.volanea.com base URL. A normal production key uses the sk_ prefix; test-mode keys can exercise the request pipeline without sending to real recipients.
Your from address must use a verified sending domain unless you are working in test mode. That prerequisite is not cosmetic: domain verification and email authentication protect deliverability and prevent a build system from impersonating an address it does not control.
For this guide, assume you have:
- A verified sender such as
deployments@updates.example.com. - A Volanea secret key such as
sk_.... - A team destination mailbox such as
engineering@example.com.
The recipient can be an individual mailbox, a shared operations inbox, or an email-routing alias. Keep it in a GitHub secret rather than in the workflow file if it may change between environments or if the repository is public. Storing it as a secret also makes the workflow easier to reuse across a staging and production repository.
Store the Volanea API key in GitHub Actions secrets
Never place a Volanea secret API key in JavaScript shipped to a browser, a mobile app bundle, a checked-in YAML file, a public issue, or a repository variable meant for non-sensitive configuration. A secret key can send mail under your account, consume sending capacity, and expose operational access that should be limited to trusted automation.
On GitHub, add the key as an Actions secret:
- Open the repository’s Settings page.
- Go to Secrets and variables, then Actions.
- Open the Secrets tab.
- Create a repository secret named
VOLANEA_API_KEY. - Paste the Volanea secret key as its value.
- Add
VOLANEA_EMAIL_TOif you also want the notification recipient to be configurable without editing YAML.
For a production deployment workflow, consider an environment secret instead of a broad repository secret. An environment can put approval rules and branch restrictions in front of jobs that use the production sending credential. This makes a GitHub Actions email integration easier to audit: only jobs assigned to the protected environment can access the key.
A GitHub Actions secret is injected into a workflow only when you explicitly reference it. It is generally redacted from logs, but redaction is not a reason to print credentials or transformed variants of credentials. Do not run env, set -x, or debugging commands that could expose sensitive request headers.
The key belongs on the GitHub Actions side because the workflow runner is the trusted server-side execution environment making the outbound request. It must not sit in client-visible configuration because anyone who can inspect client code, browser network traffic, or a public build artifact could extract it and send email as your domain.
Add a complete workflow that sends the email
Create .github/workflows/send-deploy-email.yml in the repository. The workflow below runs when code is pushed to main, safely extracts fields from the event JSON, constructs a Volanea request body with jq, and makes the REST call with curl.
name: Send deployment email
on:
push:
branches:
- main
permissions:
contents: read
jobs:
notify:
runs-on: ubuntu-latest
env:
VOLANEA_API_KEY: ${{ secrets.VOLANEA_API_KEY }}
VOLANEA_EMAIL_TO: ${{ secrets.VOLANEA_EMAIL_TO }}
VOLANEA_FROM: deployments@updates.example.com
steps:
- name: Send push notification through Volanea
shell: bash
run: |
set -euo pipefail
repository=$(jq -r '.repository.full_name // "unknown repository"' "$GITHUB_EVENT_PATH")
branch=$(jq -r '.ref // "unknown ref" | sub("^refs/heads/"; "")' "$GITHUB_EVENT_PATH")
commit_sha=$(jq -r '.after // .head_commit.id // "unknown"' "$GITHUB_EVENT_PATH")
commit_message=$(jq -r '.head_commit.message // "No commit message was provided"' "$GITHUB_EVENT_PATH")
actor=$(jq -r '.sender.login // .pusher.name // "unknown"' "$GITHUB_EVENT_PATH")
commit_url="${{ github.server_url }}/${repository}/commit/${commit_sha}"
subject="Deployment update: ${repository} (${branch})"
payload=$(jq -n \
--arg to "$VOLANEA_EMAIL_TO" \
--arg from "$VOLANEA_FROM" \
--arg subject "$subject" \
--arg repository "$repository" \
--arg branch "$branch" \
--arg sha "$commit_sha" \
--arg message "$commit_message" \
--arg actor "$actor" \
--arg url "$commit_url" \
'{
to: $to,
from: $from,
subject: $subject,
html: (
"<h1>Deployment update</h1>" +
"<p><strong>Repository:</strong> " + $repository + "</p>" +
"<p><strong>Branch:</strong> " + $branch + "</p>" +
"<p><strong>Commit:</strong> <a href=\"" + $url + "\">" + $sha + "</a></p>" +
"<p><strong>Triggered by:</strong> " + $actor + "</p>" +
"<p><strong>Message:</strong> " + $message + "</p>"
),
text: (
"Deployment update\n\n" +
"Repository: " + $repository + "\n" +
"Branch: " + $branch + "\n" +
"Commit: " + $sha + "\n" +
"URL: " + $url + "\n" +
"Triggered by: " + $actor + "\n" +
"Message: " + $message
)
}')
curl --fail-with-body --silent --show-error \
--request POST \
--url "https://api.volanea.com/v1/send" \
--header "Authorization: Bearer ${VOLANEA_API_KEY}" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: github-actions-${{ github.run_id }}" \
--data "$payload"
This request uses the Volanea fields to, from, subject, html, and text. Supplying both HTML and plain-text content is a practical default: recipients with HTML-capable clients receive the formatted version, while text-only clients and automated systems still receive a useful message.
The Authorization: Bearer header carries the secret key. The Idempotency-Key identifies the logical email send associated with the workflow run. Volanea supports that header for safe retries, which is essential when a runner cannot know whether a network failure happened before or after the API accepted the request.
Why this workflow does not check out repository code
This example only reads GitHub’s event file and makes an HTTPS request. It does not need actions/checkout, and omitting it reduces the amount of code and trust involved in the notification job.
That is especially useful for an email-sending workflow. If a job checks out and executes code contributed by an untrusted pull request while also receiving a production email key, the workflow can accidentally expose that key. Keeping notification code in the trusted workflow file and avoiding unnecessary checkout narrows the attack surface.
Make the message useful without leaking sensitive data
Deployment and CI emails are most useful when they answer a small set of questions quickly:
- What repository changed?
- Which branch or environment changed?
- Which revision caused the notification?
- Who or what initiated the event?
- Where can the recipient inspect the relevant GitHub run or commit?
The sample subject uses the repository and branch, while the body adds the short operational context. You can extend it with build status, release version, environment, pull request number, or a link to a deployment dashboard—provided those values are present for the selected event.
Do not automatically include secrets, build logs, environment-variable dumps, customer records, access tokens, or raw issue and pull request bodies. Email is frequently forwarded, archived, and indexed. A CI notification should link authorized recipients to the source system for sensitive details rather than reproduce sensitive data in the message itself.
Also be careful with HTML. The example demonstrates field mapping, but event-derived values can contain characters meaningful in HTML. If your commit messages or issue titles can be authored by untrusted users, encode untrusted text before embedding it in an HTML email, or keep the email body text-only. Building the JSON with jq protects JSON syntax and shell argument handling; it does not automatically make arbitrary strings safe HTML markup.
Prevent duplicates with an idempotency key
Email sends are side effects. If the email API receives the request successfully but the runner loses the response because of a network interruption, the workflow cannot safely infer that no email was sent. Retrying blindly can produce duplicate deployment alerts.
That is why the workflow sets:
Idempotency-Key: github-actions-${{ github.run_id }}
A GitHub Actions workflow run has a run ID. Reusing that stable value for the same logical send gives Volanea a way to recognize an attempt to repeat the request. The key should identify the event you want to deduplicate, not merely the current shell invocation.
Choose the right deduplication boundary
The correct key depends on the notification’s meaning:
- Use the workflow run ID when one email should be associated with one workflow run.
- Use a release tag when one announcement should be associated with one published release.
- Use a commit SHA plus environment when one deployment email should be sent once per revision per environment.
- Use a pull request number plus event action when an email should be sent once when a pull request is opened, approved, or merged.
Do not generate a fresh random UUID each time the step runs if your goal is deduplication. A new random key makes every retry look like a new message.
Conversely, do not reuse a permanent key such as github-actions-notification. That would incorrectly collapse unrelated notifications. An idempotency key must be unique enough to distinguish separate events while stable enough to survive a retry of the same event.
When this breaks
The GitHub Actions-to-Volanea hop is simple, but it is still a distributed system boundary: GitHub starts a runner, the runner builds a request, the network carries it, and Volanea processes it. Failures can happen at every stage.
A rerun or retry produces duplicate sends
GitHub Actions does not guarantee that a human will never rerun a workflow, and a workflow can be reattempted after a failed job. In addition, a future edit might add curl --retry, a retry wrapper, or a reusable action that retries the request.
Without an idempotency key, a rerun after an ambiguous timeout can send another copy. Keep the key deterministic for the logical event, and do not use the run attempt number in the key if a reattempt should represent the same message.
If you intentionally want a new email on a manual rerun, change the deduplication boundary deliberately—for example, include a workflow-dispatch input such as a release announcement ID. Do not make accidental duplication the mechanism for sending a follow-up.
The request times out after Volanea receives it
A timeout is not proof that the send failed. The runner may lose the response after Volanea has already accepted the request. This is the classic case for retrying with the same Idempotency-Key, not a newly generated one.
Use curl --fail-with-body so non-success HTTP responses fail the step and leave useful response information in the log. Avoid logging the Authorization header or full secret-bearing environment. If you add a retry policy, limit retries to transient connection failures and maintain the same payload and idempotency key for every attempt.
Event fields are absent or different from what you expect
A push event does not always have every field your ideal notification wants. For example, head_commit can be absent in edge cases, and a push payload does not inherently provide a pull request number. Different trigger types also expose different event objects.
The example uses // fallback values in jq for this reason. If your notification requires a pull request title, use a pull_request event rather than trying to infer it from a push. If it requires deployment-state information, use the appropriate deployment or workflow-completion event. Match the event to the data model instead of forcing unrelated payloads into the same template.
Secrets are unavailable to a pull request workflow
GitHub intentionally restricts secrets for workflows triggered by pull requests from forks. This is a security protection, not a configuration bug. A contributor should not be able to submit a pull request that runs arbitrary code and reads your email-sending credential.
Do not work around that restriction by casually switching to pull_request_target and checking out or executing untrusted fork code. If you need to send a notification for external pull requests, design a workflow that uses only trusted workflow logic and safe metadata, or send the email after trusted review and merge activity instead.
The sender domain is not verified
A request can be structurally correct yet fail because from is not on a verified Volanea sending domain. Treat sender setup as part of infrastructure configuration. Use a domain or subdomain dedicated to operational mail, complete the required DNS records, and test with a Volanea test key before notifying real recipients.
The workflow succeeds but the email is not useful
An accepted API request is not the same as a useful operational alert. The message may go to the wrong mailbox, arrive after the deployment has rolled back, lack a link to the run, or create too much volume to be read.
Test the workflow with a controlled commit, inspect the resulting email, and adjust the trigger and recipient strategy. For high-signal production notices, send only after the deployment workflow reports success rather than immediately after every push to main.
Use workflow status when deployment success matters
A push to main only says code reached the branch. It does not prove that a deployment succeeded. If your actual requirement is “email the team after production is live,” put the send step at the end of the deployment job or trigger a separate notification workflow after a deployment workflow completes successfully.
A common structure is:
- A push to
mainstarts tests and deployment. - The deployment job performs the release.
- A final step runs only when deployment succeeds.
- That final step calls Volanea.
In that model, the email reflects a completed business or operational event, not merely a source-control event. It also lets you include the deployment URL, environment, release version, and status in the payload.
For example, a deployment job can use an if: success() condition around the notification step. Keep the API call close to the operation it reports so the workflow author can see the event, deployment decision, and email notification in one place.
- name: Notify after production deployment
if: success()
run: |
# Build the Volanea payload and POST /v1/send here.
# Reuse a deterministic key tied to the deployment or release.
echo "Deployment completed; send the notification request."
If your workflow has multiple jobs, remember that a job can only use outputs explicitly passed from another job. Do not assume that a shell variable created during deployment automatically exists in a later notification job. Write important values to GITHUB_OUTPUT, expose them as job outputs, and consume them through the needs context.
Direct API call versus a marketplace action or middleware
There is no native Volanea GitHub Actions app, listing, or marketplace plugin to install for this use case. A direct REST request is usually preferable because it makes the credentials, payload, retry boundary, and message content explicit in a small YAML file.
A direct call is a strong choice when:
- The trigger is already a GitHub repository event.
- The email content comes from the GitHub event payload.
- Your team is comfortable maintaining a short shell or JavaScript step.
- You need deterministic idempotency behavior.
- You want no extra automation platform between the runner and the email API.
Middleware such as Zapier or Make can still be appropriate if the email must combine GitHub activity with a CRM record, spreadsheet row, support ticket, approval system, or other non-GitHub source. In that architecture, GitHub can call a middleware webhook endpoint from a workflow step, and the middleware can build the Volanea request.
That extra hop has tradeoffs. It may simplify no-code routing, but it introduces another secret store, another retry mechanism, another payload transformation, another timeout boundary, and another place where duplicate sends can occur. If the only required action is “send a build or deployment email,” calling Volanea directly from the runner is simpler to reason about.
Test safely before notifying real recipients
Start with a Volanea test key where possible. Test-mode requests can validate that the workflow has access to its secret, the request is properly formed, and the selected fields are present without mailing real people.
Then test the workflow in layers:
- Trigger it from a non-production branch or use a temporary branch filter.
- Send to an internal test mailbox or alias.
- Confirm the sender domain is verified.
- Confirm the subject and body contain the repository, branch, actor, commit, and link you expect.
- Rerun the same workflow and confirm the idempotency behavior matches your intended boundary.
- Test a commit message containing quotes, an ampersand, angle brackets, and multiple lines.
- Test a case where
head_commitis not present or use fallback logic deliberately.
Once the message is correct, switch the branch filter to main or connect the step to the successful production deployment job. Review the GitHub Actions logs and Volanea send records together during the first few real runs.
For an address that may come from a user-controlled source rather than a fixed operations mailbox, validate it before sending. Volanea provides a free email address verification tool that can help catch malformed or risky recipient addresses before an automated workflow creates unnecessary bounces.
Operational checklist
Before treating this integration as production-ready, verify the following:
- The trigger represents the real event recipients care about.
- The workflow file is stored in
.github/workflowsand is reviewed like application code. VOLANEA_API_KEYis an Actions secret, not a committed value or client-side configuration value.- The sender address uses a verified Volanea domain.
- The destination mailbox is intentional and monitored.
- The request uses
Authorization: BearerandContent-Type: application/json. - The request body includes
to,from,subject,html, andtext. - The request has a stable, event-scoped
Idempotency-Key. - Event fields have fallbacks where the chosen event may omit them.
- The workflow does not check out or execute untrusted code in a job that can access the Volanea secret.
- The email does not expose logs, tokens, customer data, or other sensitive internal details.
- A failed email request makes the workflow visibly fail or creates an actionable alert.
Conclusion
To send email from GitHub Actions with Volanea, use a repository event such as push, run a normal workflow step on a GitHub Actions runner, and call POST /v1/send with curl. The runner constructs the outgoing API payload from GitHub’s event context; GitHub Actions is not sending a prebuilt Volanea webhook for you.
Keep the Volanea key in GitHub Actions secrets, use a verified from address, send both HTML and text content, and use an idempotency key based on the event you are notifying about. Those details turn a basic build notification into a safer operational integration that remains understandable when retries, reruns, missing payload fields, and delivery failures happen.
FAQ
Can GitHub Actions send an HTTP request directly?
Yes. A GitHub Actions workflow can run shell commands or scripts on a runner, including curl requests to an external HTTPS API such as Volanea. You do not need a marketplace plugin for a direct REST integration.
Does Volanea have a native GitHub Actions marketplace action?
No. This integration uses GitHub Actions’ standard scripting capability and Volanea’s REST API. The workflow explicitly builds the JSON email payload and sends it with curl.
Where should I store the Volanea API key?
Store it as a GitHub Actions repository, organization, or environment secret, such as VOLANEA_API_KEY. Do not commit it to the workflow file or expose it in browser-accessible client configuration.
How do I avoid duplicate emails when a workflow is rerun?
Send a stable Idempotency-Key header based on the logical event, such as the GitHub Actions run ID, release tag, or commit SHA plus deployment environment. Reuse that same key when retrying the same request.
Should I email on every push to main?
Only if a push itself is the operational event recipients need to know about. For production notifications, it is often better to send after the deployment job completes successfully so the email reports a confirmed deployment rather than only a repository change.