Skip to content

Commit 7ccc4b3

Browse files
authored
Generalize Google Voice sender config (#31)
1 parent 95f87fe commit 7ccc4b3

9 files changed

Lines changed: 69 additions & 33 deletions

.env.example

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@ FIRSTRADE_STRATEGY_PLUGIN_MOUNTS_JSON=
2626

2727
# Optional Google Voice/SMS channel for escalated strategy plugin alerts.
2828
CRISIS_ALERT_GOOGLE_VOICE_RECIPIENTS=
29-
CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER=
30-
CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD=
29+
CRISIS_ALERT_GOOGLE_VOICE_SENDER_EMAIL=
30+
CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD=
31+
CRISIS_ALERT_GOOGLE_VOICE_SMTP_HOST=
32+
CRISIS_ALERT_GOOGLE_VOICE_SMTP_PORT=
33+
CRISIS_ALERT_GOOGLE_VOICE_SMTP_SECURITY=
3134

3235
# Runtime safety controls.
3336
FIRSTRADE_COOKIE_DIR=.runtime/firstrade-cookies

.github/workflows/sync-cloud-run-env.yml

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,11 @@ jobs:
6262
FIRSTRADE_STRATEGY_CONFIG_PATH: ${{ vars.FIRSTRADE_STRATEGY_CONFIG_PATH }}
6363
FIRSTRADE_STRATEGY_PLUGIN_MOUNTS_JSON: ${{ vars.FIRSTRADE_STRATEGY_PLUGIN_MOUNTS_JSON }}
6464
CRISIS_ALERT_GOOGLE_VOICE_RECIPIENTS: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_RECIPIENTS }}
65-
CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER }}
66-
CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME }}
65+
CRISIS_ALERT_GOOGLE_VOICE_SENDER_EMAIL: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_SENDER_EMAIL }}
66+
CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD_SECRET_NAME: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD_SECRET_NAME }}
67+
CRISIS_ALERT_GOOGLE_VOICE_SMTP_HOST: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_SMTP_HOST }}
68+
CRISIS_ALERT_GOOGLE_VOICE_SMTP_PORT: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_SMTP_PORT }}
69+
CRISIS_ALERT_GOOGLE_VOICE_SMTP_SECURITY: ${{ vars.CRISIS_ALERT_GOOGLE_VOICE_SMTP_SECURITY }}
6770
FIRSTRADE_RUNTIME_EXECUTION_WINDOW_TRADING_DAYS: ${{ vars.FIRSTRADE_RUNTIME_EXECUTION_WINDOW_TRADING_DAYS }}
6871
FIRSTRADE_TECH_RUNTIME_EXECUTION_WINDOW_TRADING_DAYS: ${{ vars.FIRSTRADE_TECH_RUNTIME_EXECUTION_WINDOW_TRADING_DAYS }}
6972
INCOME_THRESHOLD_USD: ${{ vars.INCOME_THRESHOLD_USD }}
@@ -72,7 +75,7 @@ jobs:
7275
GLOBAL_TELEGRAM_CHAT_ID: ${{ vars.GLOBAL_TELEGRAM_CHAT_ID }}
7376
NOTIFY_LANG: ${{ vars.NOTIFY_LANG }}
7477
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
75-
CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD: ${{ secrets.CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD }}
78+
CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD: ${{ secrets.CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD }}
7679
FIRSTRADE_USERNAME: ${{ secrets.FIRSTRADE_USERNAME }}
7780
FIRSTRADE_PASSWORD: ${{ secrets.FIRSTRADE_PASSWORD }}
7881
FIRSTRADE_MFA_SECRET: ${{ secrets.FIRSTRADE_MFA_SECRET }}
@@ -372,6 +375,8 @@ jobs:
372375
"TELEGRAM_CHAT_ID"
373376
"CRISIS_ALERT_GOOGLE_VOICE_TO"
374377
"CRISIS_ALERT_GOOGLE_VOICE_GATEWAY"
378+
"CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER"
379+
"CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD"
375380
"CRISIS_ALERT_SMTP_FROM"
376381
"CRISIS_ALERT_SMTP_HOST"
377382
"CRISIS_ALERT_SMTP_PORT"
@@ -380,7 +385,7 @@ jobs:
380385
"CRISIS_ALERT_SMTP_STARTTLS"
381386
"CRISIS_ALERT_SMTP_SSL"
382387
)
383-
remove_secret_vars=("CRISIS_ALERT_SMTP_PASSWORD")
388+
remove_secret_vars=("CRISIS_ALERT_SMTP_PASSWORD" "CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD")
384389
385390
add_optional_env() {
386391
local name="$1"
@@ -438,7 +443,10 @@ jobs:
438443
add_optional_env FIRSTRADE_STRATEGY_CONFIG_PATH
439444
add_optional_env FIRSTRADE_STRATEGY_PLUGIN_MOUNTS_JSON
440445
add_optional_env CRISIS_ALERT_GOOGLE_VOICE_RECIPIENTS
441-
add_optional_env CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER
446+
add_optional_env CRISIS_ALERT_GOOGLE_VOICE_SENDER_EMAIL
447+
add_optional_env CRISIS_ALERT_GOOGLE_VOICE_SMTP_HOST
448+
add_optional_env CRISIS_ALERT_GOOGLE_VOICE_SMTP_PORT
449+
add_optional_env CRISIS_ALERT_GOOGLE_VOICE_SMTP_SECURITY
442450
add_optional_env FIRSTRADE_RUNTIME_EXECUTION_WINDOW_TRADING_DAYS
443451
add_optional_env FIRSTRADE_TECH_RUNTIME_EXECUTION_WINDOW_TRADING_DAYS
444452
add_optional_env INCOME_THRESHOLD_USD
@@ -448,7 +456,7 @@ jobs:
448456
add_optional_env NOTIFY_LANG
449457
450458
add_optional_secret TELEGRAM_TOKEN TELEGRAM_TOKEN_SECRET_NAME TELEGRAM_TOKEN
451-
add_optional_secret CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD
459+
add_optional_secret CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD_SECRET_NAME CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD
452460
add_optional_secret FIRSTRADE_USERNAME FIRSTRADE_USERNAME_SECRET_NAME FIRSTRADE_USERNAME
453461
add_optional_secret FIRSTRADE_PASSWORD FIRSTRADE_PASSWORD_SECRET_NAME FIRSTRADE_PASSWORD
454462
add_optional_secret FIRSTRADE_MFA_SECRET FIRSTRADE_MFA_SECRET_SECRET_NAME FIRSTRADE_MFA_SECRET

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,11 @@ commit credentials.
8686
| `GLOBAL_TELEGRAM_CHAT_ID` | Optional | Telegram chat ID for strategy-cycle summaries |
8787
| `FIRSTRADE_STRATEGY_PLUGIN_MOUNTS_JSON` | Optional | JSON sidecar plugin mount config. Overrides global `STRATEGY_PLUGIN_MOUNTS_JSON` for this platform |
8888
| `CRISIS_ALERT_GOOGLE_VOICE_RECIPIENTS` | Optional | Email-form recipients. Use a normal mailbox for email-only delivery, or a Google Voice mailbox/address to also trigger GV prompts |
89-
| `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER` | Optional | Gmail address used as the sender for Google Voice notification mail |
90-
| `CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD` | Optional | Gmail App Password for the sender account, preferably supplied from Secret Manager in Cloud Run |
89+
| `CRISIS_ALERT_GOOGLE_VOICE_SENDER_EMAIL` | Optional | Sender email address used for Google Voice notification mail. Gmail is the default transport, but the sender naming is provider-neutral |
90+
| `CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD` | Optional | Sender SMTP password or app password, preferably supplied from Secret Manager in Cloud Run |
91+
| `CRISIS_ALERT_GOOGLE_VOICE_SMTP_HOST` | Optional | SMTP host override. Defaults to Gmail SMTP when unset |
92+
| `CRISIS_ALERT_GOOGLE_VOICE_SMTP_PORT` | Optional | SMTP port override. Defaults to `465` when unset |
93+
| `CRISIS_ALERT_GOOGLE_VOICE_SMTP_SECURITY` | Optional | SMTP security override: `ssl`, `starttls`, or `none`. Defaults to `ssl` when unset |
9194
| `FIRSTRADE_COOKIE_DIR` | Optional | Cookie cache directory, default `.runtime/firstrade-cookies` |
9295
| `FIRSTRADE_ENABLE_LIVE_TRADING` | Optional | Must be `true` before any live order can be submitted |
9396
| `FIRSTRADE_RUN_SMOKE_ON_HTTP` | Optional | Must be `true` before `/smoke` performs a real login/quote |

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ authors = [
1414
]
1515
dependencies = [
1616
"firstrade==0.0.38",
17-
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@705667fbb88b743eb83e858b1cc42fe9ebc3a87a",
18-
"us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@aca4e1449e9e87e0222b8a1a5e0dd84f822d516c",
17+
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@43b2ecb9dc7b1a70e52fe038ce321d79f5f7987a",
18+
"us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@a3f1899079d8d6c11601dddc40cb6f3020b6fc82",
1919
"google-cloud-storage",
2020
"requests",
2121
]

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
flask
22
gunicorn
33
firstrade==0.0.38
4-
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@705667fbb88b743eb83e858b1cc42fe9ebc3a87a
5-
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@aca4e1449e9e87e0222b8a1a5e0dd84f822d516c
4+
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@43b2ecb9dc7b1a70e52fe038ce321d79f5f7987a
5+
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@a3f1899079d8d6c11601dddc40cb6f3020b6fc82
66
google-cloud-storage
77
requests
88
pytest

runtime_config_support.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,11 @@ class PlatformRuntimeSettings:
5959
strategy_config_source: str | None = None
6060
strategy_plugin_mounts_json: str | None = None
6161
crisis_alert_google_voice_recipients: tuple[str, ...] = ()
62-
crisis_alert_google_voice_gmail_user: str | None = None
63-
crisis_alert_google_voice_gmail_app_password: str | None = None
62+
crisis_alert_google_voice_sender_email: str | None = None
63+
crisis_alert_google_voice_sender_password: str | None = None
64+
crisis_alert_google_voice_smtp_host: str | None = None
65+
crisis_alert_google_voice_smtp_port: str | None = None
66+
crisis_alert_google_voice_smtp_security: str | None = None
6467
runtime_target: RuntimeTarget | None = None
6568

6669

@@ -149,9 +152,14 @@ def load_platform_runtime_settings(
149152
or os.getenv("STRATEGY_PLUGIN_MOUNTS_JSON")
150153
),
151154
crisis_alert_google_voice_recipients=_split_env_list(os.getenv("CRISIS_ALERT_GOOGLE_VOICE_RECIPIENTS")),
152-
crisis_alert_google_voice_gmail_user=_first_non_empty(os.getenv("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER")),
153-
crisis_alert_google_voice_gmail_app_password=_first_non_empty(
154-
os.getenv("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD")
155+
crisis_alert_google_voice_sender_email=_first_non_empty(os.getenv("CRISIS_ALERT_GOOGLE_VOICE_SENDER_EMAIL")),
156+
crisis_alert_google_voice_sender_password=_first_non_empty(
157+
os.getenv("CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD")
158+
),
159+
crisis_alert_google_voice_smtp_host=_first_non_empty(os.getenv("CRISIS_ALERT_GOOGLE_VOICE_SMTP_HOST")),
160+
crisis_alert_google_voice_smtp_port=_first_non_empty(os.getenv("CRISIS_ALERT_GOOGLE_VOICE_SMTP_PORT")),
161+
crisis_alert_google_voice_smtp_security=_first_non_empty(
162+
os.getenv("CRISIS_ALERT_GOOGLE_VOICE_SMTP_SECURITY")
155163
),
156164
runtime_target=runtime_target,
157165
)

tests/test_rebalance_service.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,8 @@ def test_run_strategy_cycle_loads_strategy_plugin_report_and_sends_email(
229229
settings = _runtime_settings_with_persistence(
230230
strategy_plugin_mounts_json=mount_config,
231231
crisis_alert_google_voice_recipients=("voice@example.com",),
232-
crisis_alert_google_voice_gmail_user="bot@example.com",
233-
crisis_alert_google_voice_gmail_app_password="app-password",
232+
crisis_alert_google_voice_sender_email="bot@example.com",
233+
crisis_alert_google_voice_sender_password="app-password",
234234
)
235235
messages = []
236236
observed_alerts = []

tests/test_runtime_config_support.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,11 @@ def test_reserved_cash_policy_defaults_to_zero(monkeypatch):
5454
assert settings.reserved_cash_floor_usd == 0.0
5555
assert settings.reserved_cash_ratio == 0.0
5656
assert settings.crisis_alert_google_voice_recipients == ()
57-
assert settings.crisis_alert_google_voice_gmail_user is None
58-
assert settings.crisis_alert_google_voice_gmail_app_password is None
57+
assert settings.crisis_alert_google_voice_sender_email is None
58+
assert settings.crisis_alert_google_voice_sender_password is None
59+
assert settings.crisis_alert_google_voice_smtp_host is None
60+
assert settings.crisis_alert_google_voice_smtp_port is None
61+
assert settings.crisis_alert_google_voice_smtp_security is None
5962

6063

6164
def test_reserved_cash_policy_loads_from_env(monkeypatch):
@@ -72,14 +75,20 @@ def test_reserved_cash_policy_loads_from_env(monkeypatch):
7275
def test_crisis_alert_google_voice_settings_load_from_env(monkeypatch):
7376
monkeypatch.setenv("RUNTIME_TARGET_JSON", _target_json())
7477
monkeypatch.setenv("CRISIS_ALERT_GOOGLE_VOICE_RECIPIENTS", "alerts@example.com; voice@example.com")
75-
monkeypatch.setenv("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER", "sender@gmail.com")
76-
monkeypatch.setenv("CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD", "secret")
78+
monkeypatch.setenv("CRISIS_ALERT_GOOGLE_VOICE_SENDER_EMAIL", "sender@example.com")
79+
monkeypatch.setenv("CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD", "secret")
80+
monkeypatch.setenv("CRISIS_ALERT_GOOGLE_VOICE_SMTP_HOST", "smtp.example.com")
81+
monkeypatch.setenv("CRISIS_ALERT_GOOGLE_VOICE_SMTP_PORT", "587")
82+
monkeypatch.setenv("CRISIS_ALERT_GOOGLE_VOICE_SMTP_SECURITY", "starttls")
7783

7884
settings = load_platform_runtime_settings(project_id_resolver=lambda: "project-1")
7985

8086
assert settings.crisis_alert_google_voice_recipients == ("alerts@example.com", "voice@example.com")
81-
assert settings.crisis_alert_google_voice_gmail_user == "sender@gmail.com"
82-
assert settings.crisis_alert_google_voice_gmail_app_password == "secret"
87+
assert settings.crisis_alert_google_voice_sender_email == "sender@example.com"
88+
assert settings.crisis_alert_google_voice_sender_password == "secret"
89+
assert settings.crisis_alert_google_voice_smtp_host == "smtp.example.com"
90+
assert settings.crisis_alert_google_voice_smtp_port == "587"
91+
assert settings.crisis_alert_google_voice_smtp_security == "starttls"
8392

8493

8594
def test_reserved_cash_ratio_rejects_invalid_env(monkeypatch):

tests/test_sync_cloud_run_env_workflow.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,25 @@ def test_sync_cloud_run_env_workflow_syncs_crisis_alert_settings():
99

1010
for name in (
1111
"CRISIS_ALERT_GOOGLE_VOICE_RECIPIENTS",
12-
"CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER",
12+
"CRISIS_ALERT_GOOGLE_VOICE_SENDER_EMAIL",
13+
"CRISIS_ALERT_GOOGLE_VOICE_SMTP_HOST",
14+
"CRISIS_ALERT_GOOGLE_VOICE_SMTP_PORT",
15+
"CRISIS_ALERT_GOOGLE_VOICE_SMTP_SECURITY",
1316
):
1417
assert f"{name}: ${{{{ vars.{name} }}}}" in workflow
1518
assert f"add_optional_env {name}" in workflow
1619

1720
assert (
18-
"CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME: "
19-
"${{ vars.CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME }}"
21+
"CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD_SECRET_NAME: "
22+
"${{ vars.CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD_SECRET_NAME }}"
2023
) in workflow
21-
assert "CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD: ${{ secrets.CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD }}" in workflow
24+
assert "CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD: ${{ secrets.CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD }}" in workflow
2225
assert (
23-
"add_optional_secret CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD "
24-
"CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD_SECRET_NAME CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD"
26+
"add_optional_secret CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD "
27+
"CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD_SECRET_NAME CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD"
2528
) in workflow
2629
assert '"CRISIS_ALERT_GOOGLE_VOICE_TO"' in workflow
2730
assert '"CRISIS_ALERT_GOOGLE_VOICE_GATEWAY"' in workflow
31+
assert '"CRISIS_ALERT_GOOGLE_VOICE_GMAIL_USER"' in workflow
32+
assert '"CRISIS_ALERT_GOOGLE_VOICE_GMAIL_APP_PASSWORD"' in workflow
2833
assert '"CRISIS_ALERT_SMTP_HOST"' in workflow

0 commit comments

Comments
 (0)