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).
82 lines
2.7 KiB
Python
Executable File
82 lines
2.7 KiB
Python
Executable File
#!/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())
|