GitHub push email replacement — configuration example, not live-delivery proof

Scope
- The legacy repository push email feature was retired on 2026-10-15.
- This example targets GitHub-hosted ubuntu-24.04 and one consented test recipient.
- It submits one metadata-only email via STARTTLS SMTP, default port 587.
- The provider must support username/password SMTP authentication and the sender.
- Implicit TLS on 465, OAuth-only SMTP and provider HTTP APIs need different code.
- Python standard library only. No mail Action or Python package is installed.

Copy into the target repository, preserving paths
.github/workflows/push-email.yml
scripts/send_push_email.py
tests/test_send_push_email.py (optional local tests)

Keep the workflow under the target repository's real .github/workflows directory.
This folder is a downloadable example; it does not activate this blog's Actions.
Review existing files before copying. The example filters main; change it to your
branch if necessary. It does not notify on tags and skips branch deletion jobs.
No paths filter is set. Push indicates a repository event, not deployment success.

Repository Actions secrets (Settings > Secrets and variables > Actions)
SMTP_HOST / SMTP_USERNAME / SMTP_PASSWORD / MAIL_FROM / MAIL_TO
SMTP_PORT is the non-secret value 587 in the YAML. MAIL_TO accepts one plain ASCII
address. The account must be allowed to send as MAIL_FROM. Use provider-issued SMTP
credentials; confirm current authentication rules, usage limits and billing there.

Run local synthetic tests from this extracted folder
python3 -m unittest discover -s tests -v

Tests replace SMTP with an in-memory fake and block socket.create_connection.
They do not exercise TLS negotiation, GitHub events, a provider or a mailbox.
The actual test environment is recorded in the article verification report.

Live verification (not performed by the editor)
1. Prepare a test repository, a permitted SMTP sender and one consented address.
2. Review code, branch protections, organization Actions policy and secret access.
3. Commit these files and push to the allowed branch. Avoid skip directives.
4. Record the commit SHA, run ID and attempt. Inspect this notification job's step.
5. smtp_accepted means the SMTP submission completed, not inbox delivery.
6. Find the same Message-ID in provider acceptance/delivery/bounce records and
   sanitized received message headers. A provider ID can differ from Message-ID.
7. Compare an excluded branch and a provider-authentication failure only in a
   controlled test setup. Document observed results; the article does not claim them.

If no workflow runs, check branch/tag/path conditions, disabled Actions, commit skip
directives and whether the push was created with GITHUB_TOKEN (recursion prevention).
Workflow push files can exist on the pushed branch; the default-branch prerequisite
for workflow_dispatch is a different rule. This example has no manual trigger.

Output
configuration_error: exit 2; required configuration is missing or invalid.
smtp_authentication_failed: exit 1; SMTP authentication failed.
smtp_recipient_refused: exit 1; recipient was refused.
smtp_error: exit 1; inspect provider connectivity/TLS/submission conditions.
smtp_accepted: exit 0; includes SHA, run ID, attempt and client Message-ID.

No raw provider exception, password, recipient address or commit contents are
printed. The Message-ID contains the sender domain and should be redacted for
public evidence when needed. Do not enable SMTP debug logging with real secrets.
No automatic retry or exactly-once delivery is implemented. A timeout or session
failure may happen after server acceptance. Check provider records before rerunning
to avoid duplicate mail. Re-running an Actions run increments its attempt number.

checkout is pinned to v7.0.1 commit 3d3c42e5aac5ba805825da76410c181273ba90b1,
verified against the official release. Review and update it as needed; pinning does
not certify the Action's safety. Self-hosted runner requirements are out of scope.
