recipe-maintainer: public snapshot (secrets + deployment plans removed, single commit)
Sanitized single-commit public mirror of recipe-maintainer. - Removed test-ssh/.testenv (live creds); added test-ssh/.testenv.example placeholders. - Removed plans/ and planned-updates/ (deployment-planning docs) so no client/ deployment domains appear in the public repo. - All other secret stores were already gitignored. - docs.coopcloud.tech retained as a submodule (public upstream).
This commit is contained in:
81
scripts/mailgun_test.py
Executable file
81
scripts/mailgun_test.py
Executable file
@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test outgoing Mailgun credentials by sending a single email via the HTTP API.
|
||||
|
||||
Usage:
|
||||
MAILGUN_API_KEY='...' MAILGUN_FROM='noreply@example.com' \\
|
||||
./mailgun_test.py recipient@example.com
|
||||
|
||||
Env vars:
|
||||
MAILGUN_API_KEY (required) — Mailgun API key
|
||||
MAILGUN_FROM (required) — sender address (must be on a verified domain)
|
||||
MAILGUN_DOMAIN (optional) — defaults to the domain part of MAILGUN_FROM
|
||||
MAILGUN_BASE_URI (optional) — defaults to https://api.mailgun.net/v3
|
||||
use https://api.eu.mailgun.net/v3 for EU region
|
||||
|
||||
Prints HTTP status + Mailgun response body. Mailgun returns JSON with an
|
||||
"id" + "message" on success, or an error string on failure (e.g. bad domain,
|
||||
unverified sender, invalid key).
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from email.utils import parseaddr
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 2:
|
||||
print(f"usage: {sys.argv[0]} recipient@example.com", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
recipient = sys.argv[1]
|
||||
api_key = os.environ.get("MAILGUN_API_KEY")
|
||||
sender = os.environ.get("MAILGUN_FROM")
|
||||
if not api_key or not sender:
|
||||
print("error: MAILGUN_API_KEY and MAILGUN_FROM env vars are required",
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
|
||||
_, sender_addr = parseaddr(sender)
|
||||
domain = os.environ.get("MAILGUN_DOMAIN") or sender_addr.split("@", 1)[-1]
|
||||
base_uri = os.environ.get("MAILGUN_BASE_URI", "https://api.mailgun.net/v3")
|
||||
url = f"{base_uri.rstrip('/')}/{domain}/messages"
|
||||
|
||||
data = urllib.parse.urlencode({
|
||||
"from": sender,
|
||||
"to": recipient,
|
||||
"subject": "Mailgun test from mailgun_test.py",
|
||||
"text": f"This is a test message sent via {url} as {sender}.\n",
|
||||
}).encode()
|
||||
|
||||
auth = base64.b64encode(f"api:{api_key}".encode()).decode()
|
||||
req = urllib.request.Request(url, data=data, method="POST")
|
||||
req.add_header("Authorization", f"Basic {auth}")
|
||||
req.add_header("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
print(f"POST {url}")
|
||||
print(f" from={sender} to={recipient}")
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
body = resp.read().decode()
|
||||
print(f"\nHTTP {resp.status}")
|
||||
try:
|
||||
print(json.dumps(json.loads(body), indent=2))
|
||||
except json.JSONDecodeError:
|
||||
print(body)
|
||||
print(f"\nOK: Mailgun accepted the message")
|
||||
return 0
|
||||
except urllib.error.HTTPError as e:
|
||||
body = e.read().decode(errors="replace")
|
||||
print(f"\nHTTP {e.code} {e.reason}")
|
||||
print(body)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user