Add Telnyx setup wizard, remove PROJECT.md from repo

- setup_telnyx.py: automates fax app creation, number purchase,
  and config generation via Telnyx API
- Gitignore PROJECT.md and CLAUDE.md to keep project files local

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sochen 2026-03-06 21:35:18 +00:00
parent aef5e5283a
commit 0748178355
4 changed files with 288 additions and 10 deletions

4
.gitignore vendored
View file

@ -1,3 +1,7 @@
# Project/session files
PROJECT.md
CLAUDE.md
# Credentials and config
.env

View file

@ -1,5 +0,0 @@
Basically, my health insurance claims to cover doulas, however requires a tedious process where one must either fax them or snail mail them the claim. I did both. The fax returned as failure and I haven't seen the claim added even after mailing it a month ago. My coworker has the same issue.
Build a script (can integrate with cron) to sent an electronic fax to them every hour on the hour, and safely collect the returned failed responses in a directory. I will show this to them or to my companies HR. Also find an electronic fax API to use with a service that is the cheapest for a single month. Assume the doula claim paperwork is about 5 black and white pages.
You've got a figure out directive, you can ask some clarifying questions in the beginning, but then I want you to go and build the thing. Preferred language is python.

View file

@ -24,24 +24,44 @@ build_windows.bat
This produces `dist/AutoFax.exe` -- a single self-contained file you can copy to a flash drive.
## Linux/Server Setup (CLI + cron)
## Telnyx Account Setup
### 1. Telnyx Account
### Automated (recommended)
The setup wizard handles everything -- creates a fax application, buys a number, and writes your config:
```bash
# Interactive
python3 setup_telnyx.py
# Or non-interactive
python3 setup_telnyx.py --api-key KEY_xxx --to +18019382102 --area-code 801
```
You just need a Telnyx API key:
1. Sign up at [portal.telnyx.com](https://portal.telnyx.com/)
2. Add a payment method
3. Go to **API Keys** and copy your key
The wizard creates both `.env` (for CLI) and `autofax_config.json` (for GUI).
### Manual
1. Sign up at [portal.telnyx.com](https://portal.telnyx.com/)
2. Add a payment method
3. Go to **Messaging > Fax** and create a Fax Application
3. Go to **Messaging > Fax** and create a Fax Application (note the Connection ID)
4. Go to **Numbers** and purchase a fax-enabled number (~$1/month)
5. Assign the number to your Fax Application
6. Go to **API Keys** and copy your API key
### 2. Configure
7. Configure:
```bash
cp .env.example .env
# Edit .env with your Telnyx credentials and the destination fax number
```
## Linux/Server Setup (CLI + cron)
### 3. Add Your Claim
Place your claim PDF in the `claims/` directory:

259
setup_telnyx.py Executable file
View file

@ -0,0 +1,259 @@
#!/usr/bin/env python3
"""
Telnyx setup wizard - automates account provisioning for AutoFax.
Run this once after signing up at portal.telnyx.com and getting an API key.
It will:
1. Create a Fax Application
2. Search for and purchase a fax-capable phone number
3. Assign the number to the Fax Application
4. Write the .env file (or autofax_config.json for the GUI)
Usage:
python setup_telnyx.py # interactive
python setup_telnyx.py --api-key KEY_xxx --to +18019382102 # non-interactive
"""
import argparse
import json
import sys
import time
from pathlib import Path
import requests
API = "https://api.telnyx.com/v2"
BASE_DIR = Path(__file__).parent
def api(method, path, token, **kwargs):
"""Make a Telnyx API call. Raises on HTTP error with details."""
resp = requests.request(
method,
f"{API}{path}",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
**kwargs,
)
if not resp.ok:
detail = resp.text[:500]
print(f" API error ({resp.status_code}): {detail}", file=sys.stderr)
resp.raise_for_status()
return resp.json()
def create_fax_application(token: str, name: str = "AutoFax") -> dict:
"""Create a Fax Application. Returns the app data including connection ID."""
print(f"Creating Fax Application '{name}'...")
data = api("POST", "/fax_applications", token, json={
"application_name": name,
"webhook_event_url": "https://example.com/fax-status",
"active": True,
})
app = data["data"]
print(f" Created: {app['application_name']} (ID: {app['id']})")
return app
def search_fax_numbers(token: str, area_code: str = None, limit: int = 5) -> list:
"""Search for available fax-capable phone numbers."""
print("Searching for fax-capable numbers...")
params = {
"filter[country_code]": "US",
"filter[features]": "fax",
"filter[phone_number_type]": "local",
"filter[limit]": limit,
}
if area_code:
params["filter[national_destination_code]"] = area_code
data = api("GET", "/available_phone_numbers", token, params=params)
numbers = data.get("data", [])
if not numbers:
print(" No numbers found. Try a different area code.")
return numbers
def order_number(token: str, phone_number: str, connection_id: str) -> dict:
"""Purchase a phone number and assign it to the fax application."""
print(f"Ordering {phone_number}...")
data = api("POST", "/number_orders", token, json={
"phone_numbers": [{"phone_number": phone_number}],
"connection_id": connection_id,
})
order = data["data"]
print(f" Order ID: {order['id']} | Status: {order.get('status', 'pending')}")
# Wait for order to complete
order_id = order["id"]
for attempt in range(12):
time.sleep(5)
check = api("GET", f"/number_orders/{order_id}", token)
status = check["data"].get("status", "pending")
if status == "success":
print(f" Number purchased successfully!")
return check["data"]
elif status in ("failed",):
print(f" Order failed: {check['data']}", file=sys.stderr)
sys.exit(1)
print(f" Waiting... ({status})")
print(" Order is still processing. Check the Telnyx portal.")
return order
def assign_number_to_app(token: str, phone_number_id: str, connection_id: str):
"""Assign a purchased number to the fax application."""
print(f"Assigning number to fax application...")
api("PATCH", f"/phone_numbers/{phone_number_id}", token, json={
"connection_id": connection_id,
})
print(" Done.")
def write_env(api_key: str, connection_id: str, from_number: str, to_number: str):
"""Write the .env file for the CLI workflow."""
env_path = BASE_DIR / ".env"
content = f"""# Generated by setup_telnyx.py
TELNYX_API_KEY={api_key}
TELNYX_CONNECTION_ID={connection_id}
TELNYX_FROM_NUMBER={from_number}
FAX_TO_NUMBER={to_number}
# ntfy notification URL (optional)
# NTFY_URL=https://nt.nevo.engineer/autofax
# NTFY_TOKEN=Bearer tk_your_token_here
"""
env_path.write_text(content)
print(f"\n Wrote {env_path}")
def write_gui_config(api_key: str, connection_id: str, from_number: str, to_number: str):
"""Write the GUI config file."""
config_path = BASE_DIR / "autofax_config.json"
config = {
"api_key": api_key,
"connection_id": connection_id,
"from_number": from_number,
"to_number": to_number,
"pdf_path": "",
"ntfy_url": "",
"ntfy_token": "",
}
config_path.write_text(json.dumps(config, indent=2))
print(f" Wrote {config_path}")
def pick_number(numbers: list) -> str:
"""Let the user pick from available numbers."""
print("\nAvailable numbers:")
for i, n in enumerate(numbers, 1):
pn = n.get("phone_number", "?")
region = n.get("region_information", [{}])
loc = ""
if region:
loc = f" ({region[0].get('region_name', '')}, {region[0].get('region_type', '')})"
cost = n.get("cost_information", {}).get("monthly_cost", "?")
print(f" [{i}] {pn}{loc} - ${cost}/mo")
while True:
try:
choice = input(f"\nSelect a number [1-{len(numbers)}]: ").strip()
idx = int(choice) - 1
if 0 <= idx < len(numbers):
return numbers[idx]["phone_number"]
except (ValueError, EOFError):
pass
print("Invalid choice, try again.")
def main():
parser = argparse.ArgumentParser(description="Set up Telnyx for AutoFax")
parser.add_argument("--api-key", help="Telnyx API key (starts with KEY_)")
parser.add_argument("--to", help="Destination fax number in E.164 format")
parser.add_argument("--area-code", help="Preferred area code for your fax number")
parser.add_argument("--app-name", default="AutoFax", help="Fax application name")
args = parser.parse_args()
print("=" * 50)
print(" AutoFax - Telnyx Setup Wizard")
print("=" * 50)
print()
# Get API key
api_key = args.api_key
if not api_key:
print("You need a Telnyx API key. Get one at:")
print(" https://portal.telnyx.com/ -> API Keys")
print()
api_key = input("Paste your API key: ").strip()
if not api_key.startswith("KEY"):
print("Warning: API key usually starts with 'KEY'. Continuing anyway...")
# Get destination number
to_number = args.to
if not to_number:
to_number = input("Destination fax number (E.164, e.g. +18019382102): ").strip()
# Verify API key works
print("\nVerifying API key...")
try:
api("GET", "/balance", api_key)
print(" API key is valid.")
except Exception:
print("ERROR: Invalid API key or account issue.", file=sys.stderr)
sys.exit(1)
# Step 1: Create fax application
print()
app = create_fax_application(api_key, args.app_name)
connection_id = app["id"]
# Step 2: Search for a number
print()
numbers = search_fax_numbers(api_key, area_code=args.area_code)
if not numbers:
# Try without area code filter
numbers = search_fax_numbers(api_key)
if not numbers:
print("ERROR: No fax numbers available. Try again later.", file=sys.stderr)
sys.exit(1)
# Step 3: Pick and order a number
if args.area_code and len(numbers) == 1:
chosen = numbers[0]["phone_number"]
print(f"\nAuto-selected: {chosen}")
else:
chosen = pick_number(numbers)
print()
order_number(api_key, chosen, connection_id)
# Step 4: Write config files
print()
print("Writing configuration...")
write_env(api_key, connection_id, chosen, to_number)
write_gui_config(api_key, connection_id, chosen, to_number)
print()
print("=" * 50)
print(" Setup complete!")
print("=" * 50)
print()
print(f" Fax Application: {app['application_name']}")
print(f" Connection ID: {connection_id}")
print(f" From Number: {chosen}")
print(f" To Number: {to_number}")
print()
print("Next steps:")
print(" 1. Place your claim PDF in the claims/ directory")
print(" 2. Linux: ./install.sh")
print(" 3. Windows: Double-click AutoFax.exe")
print()
if __name__ == "__main__":
main()