Skip to content

Commit 6f90ab5

Browse files
committed
feat(sponsors): add email functionality with template support
- Introduced `EmailArgs` struct for handling email command arguments. - Implemented email sending logic in `email.rs`, including template fetching and variable substitution. - Added interactive template selection for email composition. - Created `args.rs` for command-line argument parsing related to sponsors. - Developed `interactive.rs` for interactive sponsor listing and detail viewing. - Added `template.rs` for handling email template variable substitution and unresolved variable detection. - Created `email_template.rs` to define data structures for email templates and responses. - Updated `mod.rs` to include new modules and functionalities. - Enhanced `main.rs` to support the new email command. - Added end-to-end tests for email template fetching, variable substitution, and sending.
1 parent 93d8f13 commit 6f90ab5

13 files changed

Lines changed: 1195 additions & 74 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
99

1010
### Added
1111

12+
- `cnctl admin sponsors email <id>` — send templated emails to sponsors
13+
- Interactive fuzzy-search template picker, pre-sorted by relevance
14+
- `--template <slug>` for non-interactive template selection
15+
- `--message` for direct message (skip templates)
16+
- `--edit` to open `$EDITOR` before sending
17+
- `--dry-run` to preview without sending
18+
- `--json` for machine-readable output
19+
- Automatic variable substitution (`{{{SPONSOR_NAME}}}`, etc.)
20+
- Template variable engine (`template.rs`) with unresolved variable detection
1221
- Browser-based OAuth login (GitHub / LinkedIn) with conference selection
1322
- `cnctl login` / `cnctl logout` / `cnctl status` commands
1423
- `cnctl admin proposals list` — list all talk proposals

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ open = "5"
1919
reqwest = { version = "0.13", features = ["json"] }
2020
serde = { version = "1", features = ["derive"] }
2121
serde_json = "1"
22+
tempfile = "3"
2223
terminal_size = "0.4"
2324
tiny_http = "0.12"
2425
tokio = { version = "1", features = ["full"] }

README.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ The organizer CLI for [Cloud Native Days Norway](https://cloudnativedays.no). Re
1111
- 🔐 **Browser-based login** — authenticate with GitHub or LinkedIn OAuth, no API keys to manage
1212
- 📋 **Interactive proposal review** — fuzzy-search, filter by status/format, sort by rating, and scroll through details with vim-style keybindings
1313
- 💰 **Sponsor pipeline** — track sponsors from prospect to paid, with contacts, tiers, and contract status
14+
- 📧 **Sponsor emails** — send templated emails to sponsors with interactive template picker, `$EDITOR` integration, and variable substitution
1415
- 🎨 **Color-coded output** — status badges at a glance (green = confirmed, yellow = submitted, red = rejected, …)
1516
- 📊 **JSON output** — pipe to `jq` or feed into scripts with `--json`
1617
- 🖥️ **Cross-platform** — prebuilt binaries for macOS, Linux, and Windows
@@ -97,6 +98,11 @@ cnctl admin sponsors list
9798
cnctl admin sponsors list --status negotiating,closedWon
9899
cnctl admin sponsors get <id>
99100

101+
cnctl admin sponsors email <id> # interactive template picker
102+
cnctl admin sponsors email <id> --template <slug> # use specific template
103+
cnctl admin sponsors email <id> --message "Hello" # send direct message
104+
cnctl admin sponsors email <id> --dry-run # preview without sending
105+
100106
cnctl logout # clear credentials
101107
```
102108

@@ -230,6 +236,52 @@ Dive into a specific sponsor for contacts, billing details, and notes:
230236
cnctl admin sponsors get <sponsor-id>
231237
```
232238

239+
### Sponsor Emails
240+
241+
Send templated emails to sponsor contacts directly from the terminal. Templates are managed in the web UI and delivered with full conference branding via Resend.
242+
243+
**Interactive mode** (default) — shows a fuzzy-search template picker, pre-sorted by relevance for the sponsor's status and language:
244+
245+
```sh
246+
cnctl admin sponsors email <sponsor-id>
247+
```
248+
249+
**Select a specific template:**
250+
251+
```sh
252+
cnctl admin sponsors email <sponsor-id> --template cold-outreach-en
253+
```
254+
255+
**Send a direct message** (skip templates):
256+
257+
```sh
258+
cnctl admin sponsors email <sponsor-id> --message "Quick follow-up on our call."
259+
```
260+
261+
**Preview without sending:**
262+
263+
```sh
264+
cnctl admin sponsors email <sponsor-id> --dry-run
265+
cnctl admin sponsors email <sponsor-id> --dry-run --json
266+
```
267+
268+
**Edit in your editor before sending:**
269+
270+
```sh
271+
cnctl admin sponsors email <sponsor-id> --edit
272+
```
273+
274+
| Flag | Description |
275+
| -------------- | ----------------------------------------------------- |
276+
| `--template` | Template slug (skip interactive picker) |
277+
| `--subject` | Override the email subject |
278+
| `--message` | Use this body directly (skip template selection) |
279+
| `--edit` | Open `$EDITOR` to edit the message before sending |
280+
| `--dry-run` | Preview the email without sending |
281+
| `--json` | Output as JSON |
282+
283+
Template variables like `{{{SPONSOR_NAME}}}`, `{{{CONTACT_NAMES}}}`, and `{{{CONFERENCE_TITLE}}}` are automatically resolved from the sponsor and conference context.
284+
233285
## 🛠️ Development
234286

235287
### Prerequisites
@@ -259,9 +311,10 @@ src/
259311
auth.rs — browser-based OAuth flow with local callback server
260312
client.rs — tRPC HTTP client
261313
config.rs — TOML config read/write (~/.config/cnctl/)
314+
template.rs — {{{VAR}}} template variable substitution
262315
commands/ — command orchestration
263316
proposals/ — proposal list, detail, review, filters, interactive mode
264-
sponsors.rs — sponsor list and detail
317+
sponsors/ — sponsor list, detail, email sending with template picker
265318
display/ — terminal output formatting (colors, layout, truncation)
266319
types/ — API response types with typed enums (serde)
267320
ui/ — reusable TUI components (pager, spinner, terminal helpers)

src/commands/sponsors/args.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use clap::Args;
2+
3+
use crate::types::SponsorStatus;
4+
5+
#[derive(Args)]
6+
pub struct ListArgs {
7+
/// Filter by status (comma-separated)
8+
#[arg(long, value_delimiter = ',', value_enum)]
9+
pub status: Option<Vec<SponsorStatus>>,
10+
11+
/// Output as JSON
12+
#[arg(long)]
13+
pub json: bool,
14+
}
15+
16+
#[derive(Args)]
17+
pub struct EmailArgs {
18+
/// Sponsor-for-conference ID
19+
pub id: String,
20+
21+
/// Template slug to use (interactive picker if omitted)
22+
#[arg(long)]
23+
pub template: Option<String>,
24+
25+
/// Override the email subject
26+
#[arg(long)]
27+
pub subject: Option<String>,
28+
29+
/// Use this message body directly (skip template selection)
30+
#[arg(long)]
31+
pub message: Option<String>,
32+
33+
/// Open $EDITOR to edit the message before sending
34+
#[arg(long)]
35+
pub edit: bool,
36+
37+
/// Preview the email without sending
38+
#[arg(long)]
39+
pub dry_run: bool,
40+
41+
/// Output as JSON
42+
#[arg(long)]
43+
pub json: bool,
44+
}

0 commit comments

Comments
 (0)