emailr-cli 1.5.4 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +2643 -112
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import {Command}from'commander';import {Emailr}from'emailr';import E from'fs';import G from'os';import U from'path';import ie from'cli-table3';import v from'chalk';import qe from'http';import {URL}from'url';import ct from'crypto';import {spawn,execSync,exec}from'child_process';var _e=[U.join(G.homedir(),".emailrrc"),U.join(G.homedir(),".config","emailr","config.json")];function m(){if(process.env.EMAILR_API_KEY)return {apiKey:process.env.EMAILR_API_KEY,baseUrl:process.env.EMAILR_BASE_URL,format:process.env.EMAILR_FORMAT||"table"};for(let n of _e)if(E.existsSync(n))try{let t=E.readFileSync(n,"utf-8"),e=JSON.parse(t);if(e.apiKey)return {apiKey:e.apiKey,baseUrl:e.baseUrl,format:e.format||"table"}}catch{}throw new Error(`No API key configured.
2
+ import {Command}from'commander';import {Emailr}from'emailr';import v,{readFileSync}from'fs';import W from'os';import N from'path';import re from'cli-table3';import S from'chalk';import Je from'http';import {URL}from'url';import ct from'crypto';import {spawn,execSync,exec}from'child_process';var Re=[N.join(W.homedir(),".emailrrc"),N.join(W.homedir(),".config","emailr","config.json")];function m(){if(process.env.EMAILR_API_KEY)return {apiKey:process.env.EMAILR_API_KEY,baseUrl:process.env.EMAILR_BASE_URL,format:process.env.EMAILR_FORMAT||"table"};for(let i of Re)if(v.existsSync(i))try{let t=v.readFileSync(i,"utf-8"),e=JSON.parse(t);if(e.apiKey)return {apiKey:e.apiKey,baseUrl:e.baseUrl,format:e.format||"table"}}catch{}throw new Error(`No API key configured.
3
3
 
4
4
  Set the EMAILR_API_KEY environment variable:
5
5
  export EMAILR_API_KEY=your-api-key
@@ -7,9 +7,419 @@ Set the EMAILR_API_KEY environment variable:
7
7
  Or create a config file at ~/.emailrrc:
8
8
  { "apiKey": "your-api-key" }
9
9
 
10
- Or run: emailr config set api-key <your-api-key>`)}function j(){let n=U.join(G.homedir(),".config","emailr");return U.join(n,"config.json")}function A(n){let t=j(),e=U.dirname(t);E.existsSync(e)||E.mkdirSync(e,{recursive:true});let o={};if(E.existsSync(t))try{o=JSON.parse(E.readFileSync(t,"utf-8"));}catch{}let i={...o,...n};E.writeFileSync(t,JSON.stringify(i,null,2)+`
11
- `);}function re(n){try{return m()[n]?.toString()}catch{return}}function c(n,t="table"){t==="json"?console.log(JSON.stringify(n,null,2)):Le(n);}function Le(n){Array.isArray(n)?Re(n):typeof n=="object"&&n!==null?Ne(n):console.log(n);}function Re(n){if(n.length===0){console.log(v.gray("No results"));return}let t=n[0];if(typeof t!="object"||t===null){n.forEach(i=>console.log(i));return}let e=Object.keys(t),o=new ie({head:e.map(i=>v.cyan(i)),style:{head:[],border:[]}});for(let i of n){let a=e.map(r=>{let s=i[r];return se(s)});o.push(a);}console.log(o.toString());}function Ne(n){let t=new ie({style:{head:[],border:[]}});for(let[e,o]of Object.entries(n))t.push([v.cyan(e),se(o)]);console.log(t.toString());}function se(n){return n==null?v.gray("-"):typeof n=="boolean"?n?v.green("\u2713"):v.red("\u2717"):typeof n=="object"?JSON.stringify(n):String(n)}function d(n){console.log(v.green("\u2713"),n);}function l(n){console.error(v.red("\u2717"),n);}function k(n){console.warn(v.yellow("\u26A0"),n);}function p(n){console.log(v.blue("\u2139"),n);}function ce(){return new Command("send").description("Send an email").requiredOption("--to <email>","Recipient email address (comma-separated for multiple)").option("--from <email>","Sender email address").option("--subject <subject>","Email subject").option("--html <html>","HTML content").option("--text <text>","Plain text content").option("--template <id>","Template ID to use").option("--template-data <json>","Template data as JSON").option("--cc <emails>","CC recipients (comma-separated)").option("--bcc <emails>","BCC recipients (comma-separated)").option("--reply-to <email>","Reply-to email address").option("--schedule <datetime>","Schedule send time (ISO 8601)").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),i=t.to.split(",").map(s=>s.trim()),a={to:i.length===1?i[0]:i};if(t.from&&(a.from=t.from),t.subject&&(a.subject=t.subject),t.html&&(a.html=t.html),t.text&&(a.text=t.text),t.template&&(a.template_id=t.template),t.templateData)try{a.template_data=JSON.parse(t.templateData);}catch{l("Invalid JSON for --template-data"),process.exit(1);}if(t.cc){let s=t.cc.split(",").map(f=>f.trim());a.cc=s.length===1?s[0]:s;}if(t.bcc){let s=t.bcc.split(",").map(f=>f.trim());a.bcc=s.length===1?s[0]:s;}t.replyTo&&(a.reply_to_email=t.replyTo),t.schedule&&(a.scheduled_at=t.schedule);let r=await o.emails.send(a);t.format==="json"?c(r,"json"):(d("Email sent successfully!"),c({"Message ID":r.message_id,Recipients:r.recipients,Status:r.status,...r.scheduled_at&&{"Scheduled At":r.scheduled_at}},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to send email"),process.exit(1);}})}function le(){let n=new Command("contacts").description("Manage contacts");return n.command("list").description("List all contacts").option("--limit <number>","Number of contacts to return","20").option("--offset <number>","Offset for pagination","0").option("--subscribed","Only show subscribed contacts").option("--unsubscribed","Only show unsubscribed contacts").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),i={limit:parseInt(t.limit,10),offset:parseInt(t.offset,10)};t.subscribed&&(i.subscribed=!0),t.unsubscribed&&(i.subscribed=!1);let a=await o.contacts.list(i);if(t.format==="json")c(a,"json");else {let r=a.contacts.map(s=>({ID:s.id,Email:s.email,Name:[s.first_name,s.last_name].filter(Boolean).join(" ")||"-",Subscribed:s.subscribed,Created:s.created_at}));c(r,"table"),console.log(`
12
- Total: ${a.total}`);}}catch(e){l(e instanceof Error?e.message:"Failed to list contacts"),process.exit(1);}}),n.command("get <id>").description("Get a contact by ID").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).contacts.get(t);c(a,e.format);}catch(o){l(o instanceof Error?o.message:"Failed to get contact"),process.exit(1);}}),n.command("create").description("Create a new contact").requiredOption("--email <email>","Contact email address").option("--first-name <name>","First name").option("--last-name <name>","Last name").option("--subscribed","Mark as subscribed (default: true)").option("--metadata <json>","Metadata as JSON").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),i={email:t.email};if(t.firstName&&(i.first_name=t.firstName),t.lastName&&(i.last_name=t.lastName),t.subscribed!==void 0&&(i.subscribed=t.subscribed),t.metadata)try{i.metadata=JSON.parse(t.metadata);}catch{l("Invalid JSON for --metadata"),process.exit(1);}let a=await o.contacts.create(i);t.format==="json"?c(a,"json"):(d(`Contact created: ${a.id}`),c(a,"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create contact"),process.exit(1);}}),n.command("update <id>").description("Update a contact").option("--email <email>","New email address").option("--first-name <name>","First name").option("--last-name <name>","Last name").option("--subscribed","Mark as subscribed").option("--unsubscribed","Mark as unsubscribed").option("--metadata <json>","Metadata as JSON").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),i=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={};if(e.email&&(a.email=e.email),e.firstName&&(a.first_name=e.firstName),e.lastName&&(a.last_name=e.lastName),e.subscribed&&(a.subscribed=!0),e.unsubscribed&&(a.subscribed=!1),e.metadata)try{a.metadata=JSON.parse(e.metadata);}catch{l("Invalid JSON for --metadata"),process.exit(1);}let r=await i.contacts.update(t,a);e.format==="json"?c(r,"json"):(d(`Contact updated: ${r.id}`),c(r,"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update contact"),process.exit(1);}}),n.command("delete <id>").description("Delete a contact").action(async t=>{try{let e=m();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).contacts.delete(t),d(`Contact deleted: ${t}`);}catch(e){l(e instanceof Error?e.message:"Failed to delete contact"),process.exit(1);}}),n}function de(){return U.join(G.homedir(),".config","emailr","templates")}function He(){let n=de();E.existsSync(n)||E.mkdirSync(n,{recursive:true});}function J(n){return U.join(de(),`${n}.html`)}function z(n,t){He();let e=J(n);E.writeFileSync(e,t,"utf-8");}function pe(n){let t=J(n);return E.existsSync(t)?E.readFileSync(t,"utf-8"):null}function fe(n){let t=J(n);return E.existsSync(t)}var y=null,T=null,H=false;function ge(n){return n.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}function Be(n){return n===null||n.trim()===""}function We(n){return `<!DOCTYPE html>
10
+ Or run: emailr config set api-key <your-api-key>`)}function I(){let i=N.join(W.homedir(),".config","emailr");return N.join(i,"config.json")}function G(i){let t=I(),e=N.dirname(t);v.existsSync(e)||v.mkdirSync(e,{recursive:true});let a={};if(v.existsSync(t))try{a=JSON.parse(v.readFileSync(t,"utf-8"));}catch{}let r={...a,...i};v.writeFileSync(t,JSON.stringify(r,null,2)+`
11
+ `);}function ne(i){try{return m()[i]?.toString()}catch{return}}function c(i,t="table"){t==="json"?console.log(JSON.stringify(i,null,2)):Me(i);}function Me(i){Array.isArray(i)?Le(i):typeof i=="object"&&i!==null?De(i):console.log(i);}function Le(i){if(i.length===0){console.log(S.gray("No results"));return}let t=i[0];if(typeof t!="object"||t===null){i.forEach(r=>console.log(r));return}let e=Object.keys(t),a=new re({head:e.map(r=>S.cyan(r)),style:{head:[],border:[]}});for(let r of i){let o=e.map(n=>{let s=r[n];return se(s)});a.push(o);}console.log(a.toString());}function De(i){let t=new re({style:{head:[],border:[]}});for(let[e,a]of Object.entries(i))t.push([S.cyan(e),se(a)]);console.log(t.toString());}function se(i){return i==null?S.gray("-"):typeof i=="boolean"?i?S.green("\u2713"):S.red("\u2717"):typeof i=="object"?JSON.stringify(i):String(i)}function d(i){console.log(S.green("\u2713"),i);}function l(i){console.error(S.red("\u2717"),i);}function k(i){console.warn(S.yellow("\u26A0"),i);}function u(i){console.log(S.blue("\u2139"),i);}function ce(){return new Command("send").description(`Send an email
12
+
13
+ USAGE
14
+ emailr send --to <email_address> [options]
15
+
16
+ DESCRIPTION
17
+ Send a transactional email to one or more recipients. You can provide
18
+ email content directly via options, load content from files, or use
19
+ a pre-defined template with variable substitution.
20
+
21
+ OPTIONS
22
+ --to <email_address> Recipient email address (required)
23
+ Use comma-separated values for multiple recipients
24
+ --from <email_address> Sender email address (uses default if not specified)
25
+ --subject <subject_line> Email subject line
26
+
27
+ Content Options (choose one approach):
28
+ --html <html_content> Inline HTML content for the email body
29
+ --text <text_content> Inline plain text content for the email body
30
+ --html-file <file_path> Read HTML content from a file
31
+ --text-file <file_path> Read plain text content from a file
32
+ --template <template_id> Use a pre-defined template by ID
33
+ --template-data <json> JSON object with template variable values
34
+
35
+ Additional Recipients:
36
+ --cc <email_addresses> CC recipients (comma-separated)
37
+ --bcc <email_addresses> BCC recipients (comma-separated)
38
+ --reply-to <email_address> Reply-to email address
39
+
40
+ Scheduling:
41
+ --schedule <datetime> Schedule send time in ISO 8601 format
42
+ Example: 2024-12-25T09:00:00Z
43
+
44
+ Output:
45
+ --format <format> Output format: json | table (default: table)
46
+
47
+ CONTENT OPTIONS EXPLAINED
48
+ There are three ways to provide email content:
49
+
50
+ 1. Inline Content (--html / --text):
51
+ Provide content directly on the command line. Best for short content
52
+ or when content is generated by a script.
53
+
54
+ --html "<h1>Hello</h1><p>Welcome!</p>"
55
+ --text "Hello, Welcome to our service!"
56
+
57
+ 2. File Content (--html-file / --text-file):
58
+ Read content from local files. Best for longer emails or when you
59
+ want to edit content in your preferred editor.
60
+
61
+ --html-file ./email.html
62
+ --text-file ./email.txt
63
+
64
+ 3. Template (--template / --template-data):
65
+ Use a pre-defined template with variable substitution. Best for
66
+ consistent branding and reusable email designs.
67
+
68
+ --template tmpl_abc123 --template-data '{"name": "John"}'
69
+
70
+ You can combine --html/--html-file with --text/--text-file to provide
71
+ both HTML and plain text versions. The plain text version is used as
72
+ a fallback for email clients that don't support HTML.
73
+
74
+ TEMPLATE DATA FORMAT
75
+ The --template-data option accepts a JSON object where keys match
76
+ the variable names defined in your template.
77
+
78
+ Template with variables {{name}} and {{order_id}}:
79
+ --template-data '{"name": "John Doe", "order_id": "ORD-12345"}'
80
+
81
+ Complex data with nested objects:
82
+ --template-data '{"user": {"name": "John", "email": "john@example.com"}, "items": ["Item 1", "Item 2"]}'
83
+
84
+ Using a JSON file (via shell):
85
+ --template-data "$(cat data.json)"
86
+
87
+ OUTPUT FORMATS
88
+ --format json Machine-readable JSON with message_id, recipients, status
89
+ --format table Human-readable table output (default)
90
+
91
+ EXAMPLES
92
+ # Simple email with inline HTML
93
+ emailr send --to "user@example.com" \\
94
+ --subject "Welcome!" \\
95
+ --html "<h1>Welcome</h1><p>Thanks for signing up!</p>"
96
+
97
+ # Email with plain text fallback
98
+ emailr send --to "user@example.com" \\
99
+ --subject "Hello" \\
100
+ --html "<h1>Hello</h1>" \\
101
+ --text "Hello (plain text version)"
102
+
103
+ # Email from HTML file
104
+ emailr send --to "user@example.com" \\
105
+ --subject "Newsletter" \\
106
+ --html-file ./newsletter.html \\
107
+ --text-file ./newsletter.txt
108
+
109
+ # Multiple recipients
110
+ emailr send --to "user1@example.com,user2@example.com,user3@example.com" \\
111
+ --subject "Team Update" \\
112
+ --html-file ./update.html
113
+
114
+ # Using a template with variables
115
+ emailr send --to "customer@example.com" \\
116
+ --template tmpl_welcome123 \\
117
+ --template-data '{"name": "John", "company": "Acme Inc"}'
118
+
119
+ # Template email with complex data
120
+ emailr send --to "customer@example.com" \\
121
+ --template tmpl_order_confirm \\
122
+ --template-data '{"customer_name": "Jane", "order_id": "ORD-789", "total": "$99.99", "items": [{"name": "Widget", "qty": 2}]}'
123
+
124
+ # Email with CC and BCC
125
+ emailr send --to "primary@example.com" \\
126
+ --cc "manager@example.com,team@example.com" \\
127
+ --bcc "archive@example.com" \\
128
+ --subject "Project Update" \\
129
+ --html-file ./update.html
130
+
131
+ # Scheduled email
132
+ emailr send --to "user@example.com" \\
133
+ --subject "Reminder" \\
134
+ --html "<p>Don't forget your appointment tomorrow!</p>" \\
135
+ --schedule "2024-12-25T09:00:00Z"
136
+
137
+ # Custom sender and reply-to
138
+ emailr send --to "customer@example.com" \\
139
+ --from "support@mycompany.com" \\
140
+ --reply-to "help@mycompany.com" \\
141
+ --subject "Support Response" \\
142
+ --html-file ./response.html
143
+
144
+ # Get JSON output for scripting
145
+ emailr send --to "user@example.com" \\
146
+ --subject "Test" \\
147
+ --text "Test email" \\
148
+ --format json
149
+
150
+ # Send and capture message ID
151
+ emailr send --to "user@example.com" \\
152
+ --subject "Test" \\
153
+ --text "Hello" \\
154
+ --format json | jq -r '.message_id'
155
+
156
+ SEE ALSO
157
+ emailr templates Manage email templates
158
+ emailr contacts Manage contacts
159
+ emailr broadcasts Send bulk emails to segments`).requiredOption("--to <email>","Recipient email address (comma-separated for multiple)").option("--from <email>","Sender email address").option("--subject <subject>","Email subject").option("--html <html>","HTML content (inline)").option("--text <text>","Plain text content (inline)").option("--html-file <path>","Read HTML content from file").option("--text-file <path>","Read plain text content from file").option("--template <id>","Template ID to use").option("--template-data <json>","Template data as JSON").option("--cc <emails>","CC recipients (comma-separated)").option("--bcc <emails>","BCC recipients (comma-separated)").option("--reply-to <email>","Reply-to email address").option("--schedule <datetime>","Schedule send time (ISO 8601)").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),a=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r=t.to.split(",").map(s=>s.trim()),o={to:r.length===1?r[0]:r};if(t.from&&(o.from=t.from),t.subject&&(o.subject=t.subject),t.htmlFile)try{o.html=readFileSync(t.htmlFile,"utf-8");}catch{l(`Failed to read HTML file: ${t.htmlFile}`),process.exit(1);}else t.html&&(o.html=t.html);if(t.textFile)try{o.text=readFileSync(t.textFile,"utf-8");}catch{l(`Failed to read text file: ${t.textFile}`),process.exit(1);}else t.text&&(o.text=t.text);if(t.template&&(o.template_id=t.template),t.templateData)try{o.template_data=JSON.parse(t.templateData);}catch{l("Invalid JSON for --template-data"),process.exit(1);}if(t.cc){let s=t.cc.split(",").map(p=>p.trim());o.cc=s.length===1?s[0]:s;}if(t.bcc){let s=t.bcc.split(",").map(p=>p.trim());o.bcc=s.length===1?s[0]:s;}t.replyTo&&(o.reply_to_email=t.replyTo),t.schedule&&(o.scheduled_at=t.schedule);let n=await a.emails.send(o);t.format==="json"?c(n,"json"):(d("Email sent successfully!"),c({"Message ID":n.message_id,Recipients:n.recipients,Status:n.status,...n.scheduled_at&&{"Scheduled At":n.scheduled_at}},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to send email"),process.exit(1);}})}function me(){let i=new Command("contacts").description(`Manage contacts
160
+
161
+ USAGE
162
+ emailr contacts <subcommand> [options]
163
+
164
+ DESCRIPTION
165
+ Create, update, list, and manage email contacts. Contacts are the recipients
166
+ of your email campaigns and can be organized into segments for targeted
167
+ broadcasts.
168
+
169
+ SUBCOMMANDS
170
+ list List all contacts with pagination and filtering
171
+ get <id> Get a contact by ID
172
+ create Create a new contact
173
+ update <id> Update an existing contact
174
+ delete <id> Delete a contact
175
+
176
+ OPTIONS
177
+ --format <format> Output format: json | table (default: table)
178
+
179
+ OUTPUT FORMATS
180
+ --format json Machine-readable JSON output
181
+ --format table Human-readable table output (default)
182
+
183
+ PAGINATION OPTIONS
184
+ --limit <count> Number of contacts to return (default: 20)
185
+ --offset <count> Number of contacts to skip (default: 0)
186
+
187
+ FILTER OPTIONS
188
+ --subscribed Only show subscribed contacts
189
+ --unsubscribed Only show unsubscribed contacts
190
+
191
+ METADATA OPTION
192
+ --metadata <json> Custom metadata as JSON object
193
+
194
+ The metadata option accepts a JSON object with custom key-value pairs.
195
+ This allows you to store additional information about contacts.
196
+
197
+ JSON Format Examples:
198
+ '{"plan": "premium", "source": "website"}'
199
+ '{"company": "Acme Inc", "role": "developer"}'
200
+ '{"tags": ["vip", "early-adopter"], "score": 100}'
201
+
202
+ EXAMPLES
203
+ # List all contacts
204
+ emailr contacts list
205
+
206
+ # List with pagination
207
+ emailr contacts list --limit 50 --offset 100
208
+
209
+ # List only subscribed contacts
210
+ emailr contacts list --subscribed
211
+
212
+ # List only unsubscribed contacts
213
+ emailr contacts list --unsubscribed
214
+
215
+ # Get a specific contact
216
+ emailr contacts get con_abc123
217
+
218
+ # Create a new contact
219
+ emailr contacts create --email "user@example.com" --first-name "John" --last-name "Doe"
220
+
221
+ # Create contact with metadata
222
+ emailr contacts create --email "user@example.com" --metadata '{"plan": "premium"}'
223
+
224
+ # Update a contact
225
+ emailr contacts update con_abc123 --first-name "Jane"
226
+
227
+ # Update contact subscription status
228
+ emailr contacts update con_abc123 --unsubscribed
229
+
230
+ # Update contact metadata
231
+ emailr contacts update con_abc123 --metadata '{"plan": "enterprise", "upgraded": true}'
232
+
233
+ # Delete a contact
234
+ emailr contacts delete con_abc123
235
+
236
+ # Get JSON output for scripting
237
+ emailr contacts list --format json
238
+
239
+ SEE ALSO
240
+ emailr segments Manage contact segments
241
+ emailr broadcasts Send emails to contacts`);return i.command("list").description(`List all contacts
242
+
243
+ USAGE
244
+ emailr contacts list [options]
245
+
246
+ DESCRIPTION
247
+ Retrieves a paginated list of all contacts in your account. Supports
248
+ filtering by subscription status and pagination for large contact lists.
249
+
250
+ OPTIONS
251
+ --limit <count> Number of contacts to return (default: 20)
252
+ --offset <count> Number of contacts to skip for pagination (default: 0)
253
+ --subscribed Only show subscribed contacts
254
+ --unsubscribed Only show unsubscribed contacts
255
+ --format <format> Output format: json | table (default: table)
256
+
257
+ OUTPUT FORMATS
258
+ --format json Machine-readable JSON with contacts array and total count
259
+ --format table Human-readable table with columns: ID, Email, Name, Subscribed, Created
260
+
261
+ PAGINATION
262
+ Use --limit and --offset together for pagination:
263
+ - Page 1: --limit 20 --offset 0 (default)
264
+ - Page 2: --limit 20 --offset 20
265
+ - Page 3: --limit 20 --offset 40
266
+
267
+ EXAMPLES
268
+ # List first 20 contacts (default)
269
+ emailr contacts list
270
+
271
+ # List 50 contacts
272
+ emailr contacts list --limit 50
273
+
274
+ # Get page 2 of results (20 per page)
275
+ emailr contacts list --limit 20 --offset 20
276
+
277
+ # List only subscribed contacts
278
+ emailr contacts list --subscribed
279
+
280
+ # List only unsubscribed contacts
281
+ emailr contacts list --unsubscribed
282
+
283
+ # Get JSON output for scripting
284
+ emailr contacts list --format json
285
+
286
+ # Combine filters with pagination
287
+ emailr contacts list --subscribed --limit 100 --offset 0`).option("--limit <number>","Number of contacts to return","20").option("--offset <number>","Offset for pagination","0").option("--subscribed","Only show subscribed contacts").option("--unsubscribed","Only show unsubscribed contacts").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),a=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r={limit:parseInt(t.limit,10),offset:parseInt(t.offset,10)};t.subscribed&&(r.subscribed=!0),t.unsubscribed&&(r.subscribed=!1);let o=await a.contacts.list(r);if(t.format==="json")c(o,"json");else {let n=o.contacts.map(s=>({ID:s.id,Email:s.email,Name:[s.first_name,s.last_name].filter(Boolean).join(" ")||"-",Subscribed:s.subscribed,Created:s.created_at}));c(n,"table"),console.log(`
288
+ Total: ${o.total}`);}}catch(e){l(e instanceof Error?e.message:"Failed to list contacts"),process.exit(1);}}),i.command("get <contact_id>").description(`Get a contact by ID
289
+
290
+ USAGE
291
+ emailr contacts get <contact_id> [options]
292
+
293
+ DESCRIPTION
294
+ Retrieves detailed information about a specific contact including
295
+ email, name, subscription status, metadata, and timestamps.
296
+
297
+ ARGUMENTS
298
+ <contact_id> The unique contact identifier (e.g., con_abc123)
299
+
300
+ OPTIONS
301
+ --format <format> Output format: json | table (default: table)
302
+
303
+ OUTPUT FORMATS
304
+ --format json Full contact object including all fields and metadata
305
+ --format table Summary table with key contact information
306
+
307
+ EXAMPLES
308
+ # Get contact details
309
+ emailr contacts get con_abc123
310
+
311
+ # Get full contact data as JSON
312
+ emailr contacts get con_abc123 --format json
313
+
314
+ # Pipe JSON to jq for processing
315
+ emailr contacts get con_abc123 --format json | jq '.metadata'`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).contacts.get(t);c(o,e.format);}catch(a){l(a instanceof Error?a.message:"Failed to get contact"),process.exit(1);}}),i.command("create").description(`Create a new contact
316
+
317
+ USAGE
318
+ emailr contacts create --email <email_address> [options]
319
+
320
+ DESCRIPTION
321
+ Creates a new contact with the specified email address and optional
322
+ details. Contacts are subscribed by default unless --subscribed is
323
+ explicitly set to false.
324
+
325
+ OPTIONS
326
+ --email <email_address> Contact email address (required)
327
+ --first-name <name> First name
328
+ --last-name <name> Last name
329
+ --subscribed Mark as subscribed (default: true)
330
+ --metadata <json> Custom metadata as JSON object
331
+ --format <format> Output format: json | table (default: table)
332
+
333
+ METADATA FORMAT
334
+ The --metadata option accepts a JSON object with custom key-value pairs:
335
+ '{"plan": "premium", "source": "website"}'
336
+ '{"company": "Acme Inc", "role": "developer"}'
337
+ '{"tags": ["vip", "early-adopter"], "score": 100}'
338
+
339
+ OUTPUT FORMATS
340
+ --format json Full contact object with all fields
341
+ --format table Summary table with ID, Email, Name, Subscribed
342
+
343
+ EXAMPLES
344
+ # Create contact with email only
345
+ emailr contacts create --email "user@example.com"
346
+
347
+ # Create contact with full name
348
+ emailr contacts create --email "user@example.com" --first-name "John" --last-name "Doe"
349
+
350
+ # Create contact with metadata
351
+ emailr contacts create --email "user@example.com" --metadata '{"plan": "premium", "source": "signup"}'
352
+
353
+ # Create unsubscribed contact (for import purposes)
354
+ emailr contacts create --email "user@example.com" --subscribed false
355
+
356
+ # Get JSON output for scripting
357
+ emailr contacts create --email "user@example.com" --format json`).requiredOption("--email <email>","Contact email address").option("--first-name <name>","First name").option("--last-name <name>","Last name").option("--subscribed","Mark as subscribed (default: true)").option("--metadata <json>","Metadata as JSON").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),a=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r={email:t.email};if(t.firstName&&(r.first_name=t.firstName),t.lastName&&(r.last_name=t.lastName),t.subscribed!==void 0&&(r.subscribed=t.subscribed),t.metadata)try{r.metadata=JSON.parse(t.metadata);}catch{l("Invalid JSON for --metadata"),process.exit(1);}let o=await a.contacts.create(r);t.format==="json"?c(o,"json"):(d(`Contact created: ${o.id}`),c(o,"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create contact"),process.exit(1);}}),i.command("update <contact_id>").description(`Update a contact
358
+
359
+ USAGE
360
+ emailr contacts update <contact_id> [options]
361
+
362
+ DESCRIPTION
363
+ Updates an existing contact with new values. Only specified fields
364
+ are updated; omitted fields remain unchanged.
365
+
366
+ ARGUMENTS
367
+ <contact_id> The unique contact identifier (e.g., con_abc123)
368
+
369
+ OPTIONS
370
+ --email <email_address> New email address
371
+ --first-name <name> New first name
372
+ --last-name <name> New last name
373
+ --subscribed Mark as subscribed
374
+ --unsubscribed Mark as unsubscribed
375
+ --metadata <json> New metadata as JSON object (replaces existing)
376
+ --format <format> Output format: json | table (default: table)
377
+
378
+ METADATA FORMAT
379
+ The --metadata option accepts a JSON object. Note that this replaces
380
+ the entire metadata object, not individual fields:
381
+ '{"plan": "enterprise", "upgraded": true}'
382
+ '{"tags": ["vip"], "notes": "Important customer"}'
383
+
384
+ OUTPUT FORMATS
385
+ --format json Full updated contact object
386
+ --format table Summary table with updated contact information
387
+
388
+ EXAMPLES
389
+ # Update contact name
390
+ emailr contacts update con_abc123 --first-name "Jane" --last-name "Smith"
391
+
392
+ # Update email address
393
+ emailr contacts update con_abc123 --email "newemail@example.com"
394
+
395
+ # Unsubscribe a contact
396
+ emailr contacts update con_abc123 --unsubscribed
397
+
398
+ # Resubscribe a contact
399
+ emailr contacts update con_abc123 --subscribed
400
+
401
+ # Update metadata
402
+ emailr contacts update con_abc123 --metadata '{"plan": "enterprise", "upgraded": true}'
403
+
404
+ # Get JSON output
405
+ emailr contacts update con_abc123 --first-name "Jane" --format json`).option("--email <email>","New email address").option("--first-name <name>","First name").option("--last-name <name>","Last name").option("--subscribed","Mark as subscribed").option("--unsubscribed","Mark as unsubscribed").option("--metadata <json>","Metadata as JSON").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),r=new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}),o={};if(e.email&&(o.email=e.email),e.firstName&&(o.first_name=e.firstName),e.lastName&&(o.last_name=e.lastName),e.subscribed&&(o.subscribed=!0),e.unsubscribed&&(o.subscribed=!1),e.metadata)try{o.metadata=JSON.parse(e.metadata);}catch{l("Invalid JSON for --metadata"),process.exit(1);}let n=await r.contacts.update(t,o);e.format==="json"?c(n,"json"):(d(`Contact updated: ${n.id}`),c(n,"table"));}catch(a){l(a instanceof Error?a.message:"Failed to update contact"),process.exit(1);}}),i.command("delete <contact_id>").description(`Delete a contact
406
+
407
+ USAGE
408
+ emailr contacts delete <contact_id>
409
+
410
+ DESCRIPTION
411
+ Permanently deletes a contact. This action cannot be undone.
412
+ The contact will be removed from all segments.
413
+
414
+ ARGUMENTS
415
+ <contact_id> The unique contact identifier (e.g., con_abc123)
416
+
417
+ EXAMPLES
418
+ # Delete a contact
419
+ emailr contacts delete con_abc123
420
+
421
+ WARNING
422
+ This action is permanent and cannot be undone.`).action(async t=>{try{let e=m();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).contacts.delete(t),d(`Contact deleted: ${t}`);}catch(e){l(e instanceof Error?e.message:"Failed to delete contact"),process.exit(1);}}),i}function pe(){return N.join(W.homedir(),".config","emailr","templates")}function qe(){let i=pe();v.existsSync(i)||v.mkdirSync(i,{recursive:true});}function B(i){return N.join(pe(),`${i}.html`)}function X(i,t){qe();let e=B(i);v.writeFileSync(e,t,"utf-8");}function ue(i){let t=B(i);return v.existsSync(t)?v.readFileSync(t,"utf-8"):null}function fe(i){let t=B(i);return v.existsSync(t)}var w=null,C=null,H=false;function he(i){return i.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}function $e(i){return i===null||i.trim()===""}function We(i){return `<!DOCTYPE html>
13
423
  <html lang="en">
14
424
  <head>
15
425
  <meta charset="UTF-8">
@@ -75,14 +485,14 @@ Total: ${a.total}`);}}catch(e){l(e instanceof Error?e.message:"Failed to list co
75
485
  <div class="icon">\u{1F4ED}</div>
76
486
  <h1>No Content Available</h1>
77
487
  <p>This template exists but has no HTML content to display.</p>
78
- <div class="template-id">${ge(n)}</div>
488
+ <div class="template-id">${he(i)}</div>
79
489
  <div class="hint">
80
490
  <p><strong>To add content:</strong></p>
81
491
  <p>Update the template using the CLI with the <code>--html</code> option or provide HTML content when creating the template.</p>
82
492
  </div>
83
493
  </div>
84
494
  </body>
85
- </html>`}function ue(n){return `<!DOCTYPE html>
495
+ </html>`}function be(i){return `<!DOCTYPE html>
86
496
  <html lang="en">
87
497
  <head>
88
498
  <meta charset="UTF-8">
@@ -134,11 +544,11 @@ Total: ${a.total}`);}}catch(e){l(e instanceof Error?e.message:"Failed to list co
134
544
  <div class="icon">404</div>
135
545
  <h1>Template Not Found</h1>
136
546
  <p>The requested template could not be found in local storage.</p>
137
- <div class="template-id">${ge(n)}</div>
547
+ <div class="template-id">${he(i)}</div>
138
548
  <p style="margin-top: 1rem;">Try creating or retrieving the template first using the CLI.</p>
139
549
  </div>
140
550
  </body>
141
- </html>`}function Ge(n){let t=n.match(/^\/preview\/([^/]+)$/);return t?t[1]:null}function Je(){return (n,t)=>{if(n.method!=="GET"){t.writeHead(405,{"Content-Type":"text/plain"}),t.end("Method Not Allowed");return}let e=n.url||"/",o=Ge(e);if(!o){t.writeHead(404,{"Content-Type":"text/plain"}),t.end("Not Found");return}if(!fe(o)){t.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),t.end(ue(o));return}let i=pe(o);if(i===null){t.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),t.end(ue(o));return}if(Be(i)){t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(We(o));return}t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(i);}}var be={async start(){return H&&T!==null?T:new Promise((n,t)=>{y=qe.createServer(Je()),y.listen(0,"127.0.0.1",()=>{let e=y.address();e&&typeof e=="object"?(T=e.port,H=true,y.unref(),n(T)):t(new Error("Failed to get server address"));}),y.on("error",e=>{H=false,T=null,y=null,t(new Error(`Failed to start preview server: ${e.message}`));});})},getPort(){return T},isRunning(){return H},async stop(){if(y)return new Promise(n=>{y.close(()=>{y=null,T=null,H=false,n();});})}};function he(){return be}async function V(n){try{return `http://127.0.0.1:${await be.start()}/preview/${n}`}catch{return null}}function ye(){y&&y.ref();}var P=null,Y=null,B=null,L=[],ve=`
551
+ </html>`}function Be(i){let t=i.match(/^\/preview\/([^/]+)$/);return t?t[1]:null}function Xe(){return (i,t)=>{if(i.method!=="GET"){t.writeHead(405,{"Content-Type":"text/plain"}),t.end("Method Not Allowed");return}let e=i.url||"/",a=Be(e);if(!a){t.writeHead(404,{"Content-Type":"text/plain"}),t.end("Not Found");return}if(!fe(a)){t.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),t.end(be(a));return}let r=ue(a);if(r===null){t.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),t.end(be(a));return}if($e(r)){t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(We(a));return}t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(r);}}var ge={async start(){return H&&C!==null?C:new Promise((i,t)=>{w=Je.createServer(Xe()),w.listen(0,"127.0.0.1",()=>{let e=w.address();e&&typeof e=="object"?(C=e.port,H=true,w.unref(),i(C)):t(new Error("Failed to get server address"));}),w.on("error",e=>{H=false,C=null,w=null,t(new Error(`Failed to start preview server: ${e.message}`));});})},getPort(){return C},isRunning(){return H},async stop(){if(w)return new Promise(i=>{w.close(()=>{w=null,C=null,H=false,i();});})}};function we(){return ge}async function V(i){try{return `http://127.0.0.1:${await ge.start()}/preview/${i}`}catch{return null}}function ye(){w&&w.ref();}var A=null,z=null,J=null,R=[],ve=`
142
552
  <script>
143
553
  (function() {
144
554
  const evtSource = new EventSource('/__live-reload');
@@ -152,31 +562,369 @@ Total: ${a.total}`);}}catch(e){l(e instanceof Error?e.message:"Failed to list co
152
562
  };
153
563
  })();
154
564
  </script>
155
- `;function Ye(n){return n.includes("</body>")?n.replace("</body>",`${ve}</body>`):n+ve}function Qe(){L.forEach(n=>{try{n.write(`data: reload
565
+ `;function Ye(i){return i.includes("</body>")?i.replace("</body>",`${ve}</body>`):i+ve}function Ze(){R.forEach(i=>{try{i.write(`data: reload
566
+
567
+ `);}catch{}});}async function Y(i,t){let e=N.resolve(i);return new Promise((a,r)=>{A=Je.createServer((o,n)=>{if(o.url==="/__live-reload"){n.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive","Access-Control-Allow-Origin":"*"}),n.write(`data: connected
568
+
569
+ `),R.push(n),o.on("close",()=>{R=R.filter(s=>s!==n);});return}if(o.method==="GET"){try{let s=v.readFileSync(e,"utf-8"),p=Ye(s);n.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),n.end(p);}catch(s){n.writeHead(500,{"Content-Type":"text/plain"}),n.end(`Error reading file: ${s instanceof Error?s.message:String(s)}`);}return}n.writeHead(405,{"Content-Type":"text/plain"}),n.end("Method Not Allowed");}),A.listen(0,"127.0.0.1",()=>{let o=A.address();if(o&&typeof o=="object"){z=o.port;let n=null;J=v.watch(e,s=>{s==="change"&&(n&&clearTimeout(n),n=setTimeout(()=>{Ze(),t?.();},100));}),a(z);}else r(new Error("Failed to get server address"));}),A.on("error",o=>{r(new Error(`Failed to start server: ${o.message}`));});})}async function Z(){if(J&&(J.close(),J=null),R.forEach(i=>{try{i.end();}catch{}}),R=[],A)return new Promise(i=>{A.close(()=>{A=null,z=null,i();});})}async function Te(i){try{let t=i.html_content??"";X(i.id,t);}catch(t){return k(`Could not save template for preview: ${t instanceof Error?t.message:String(t)}`),null}try{let t=await V(i.id);return t===null?(k("Could not start preview server"),null):t}catch(t){return k(`Could not generate preview URL: ${t instanceof Error?t.message:String(t)}`),null}}function Oe(){let i=new Command("templates").description(`Manage email templates
570
+
571
+ USAGE
572
+ emailr templates <subcommand> [options]
573
+
574
+ DESCRIPTION
575
+ Create, edit, preview, and publish email templates. Supports both local
576
+ live-preview editing and server-side preview URLs for sharing with AI agents.
577
+
578
+ SUBCOMMANDS
579
+ list List all templates
580
+ get <id> Get a template by ID
581
+ create Create a new template
582
+ update <id> Update an existing template
583
+ delete <id> Delete a template
584
+ fetch <id> Download template HTML to file or stdout
585
+ push-preview <id> Upload HTML to template's preview for sharing
586
+ preview <id> Preview a template in the browser
587
+ edit <id> Start live editing session with hot-reload
588
+ draft Start live drafting session for new template
589
+ stop-preview Stop any running background preview server
590
+
591
+ OUTPUT FORMATS
592
+ --format json Machine-readable JSON output
593
+ --format table Human-readable table output (default)
594
+
595
+ EXAMPLES
596
+ # List all templates
597
+ emailr templates list
598
+
599
+ # Get a specific template
600
+ emailr templates get tpl_abc123
601
+
602
+ # Create a template from file
603
+ emailr templates create --name "Welcome" --subject "Welcome!" --html-file welcome.html
604
+
605
+ # Update a template
606
+ emailr templates update tpl_abc123 --subject "New Subject"
607
+
608
+ # Download template HTML to a file
609
+ emailr templates fetch tpl_abc123 --output template.html
610
+
611
+ # Push preview for AI agent review
612
+ emailr templates push-preview tpl_abc123 --html-file template.html
613
+
614
+ # Start live editing with hot-reload
615
+ emailr templates edit tpl_abc123 --file ./template.html
616
+
617
+ # Draft a new template with live preview
618
+ emailr templates draft --file ./new-template.html
619
+
620
+ AGENTIC WORKFLOW
621
+ For collaborating with AI agents on template design:
622
+
623
+ 1. Fetch current template:
624
+ emailr templates fetch <id> --output template.html
625
+
626
+ 2. Edit locally or with AI assistance
627
+
628
+ 3. Push preview for AI review:
629
+ emailr templates push-preview <id> --html-file template.html
630
+
631
+ 4. Share the preview URL with your AI agent for feedback
632
+
633
+ 5. Repeat steps 2-4 until satisfied
634
+
635
+ 6. Publish the final version:
636
+ emailr templates update <id> --html-file template.html
637
+
638
+ SEE ALSO
639
+ emailr send Send emails using templates
640
+ emailr broadcasts Send bulk emails to segments`);return i.command("list").description(`List all templates
641
+
642
+ USAGE
643
+ emailr templates list [options]
644
+
645
+ DESCRIPTION
646
+ Retrieves a paginated list of all email templates in your account.
647
+ Returns template ID, name, subject, variables, and creation date.
648
+
649
+ OPTIONS
650
+ --limit <count> Number of templates to return (default: 20)
651
+ --page <page_number> Page number for pagination (default: 1)
652
+ --format <format> Output format: json | table (default: table)
653
+
654
+ OUTPUT FORMATS
655
+ --format json Machine-readable JSON array of template objects
656
+ --format table Human-readable table with columns: ID, Name, Subject, Variables, Created
156
657
 
157
- `);}catch{}});}async function Q(n,t){let e=U.resolve(n);return new Promise((o,i)=>{P=qe.createServer((a,r)=>{if(a.url==="/__live-reload"){r.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive","Access-Control-Allow-Origin":"*"}),r.write(`data: connected
658
+ EXAMPLES
659
+ # List first 20 templates
660
+ emailr templates list
158
661
 
159
- `),L.push(r),a.on("close",()=>{L=L.filter(s=>s!==r);});return}if(a.method==="GET"){try{let s=E.readFileSync(e,"utf-8"),f=Ye(s);r.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),r.end(f);}catch(s){r.writeHead(500,{"Content-Type":"text/plain"}),r.end(`Error reading file: ${s instanceof Error?s.message:String(s)}`);}return}r.writeHead(405,{"Content-Type":"text/plain"}),r.end("Method Not Allowed");}),P.listen(0,"127.0.0.1",()=>{let a=P.address();if(a&&typeof a=="object"){Y=a.port;let r=null;B=E.watch(e,s=>{s==="change"&&(r&&clearTimeout(r),r=setTimeout(()=>{Qe(),t?.();},100));}),o(Y);}else i(new Error("Failed to get server address"));}),P.on("error",a=>{i(new Error(`Failed to start server: ${a.message}`));});})}async function X(){if(B&&(B.close(),B=null),L.forEach(n=>{try{n.end();}catch{}}),L=[],P)return new Promise(n=>{P.close(()=>{P=null,Y=null,n();});})}async function xe(n){try{let t=n.html_content??"";z(n.id,t);}catch(t){return k(`Could not save template for preview: ${t instanceof Error?t.message:String(t)}`),null}try{let t=await V(n.id);return t===null?(k("Could not start preview server"),null):t}catch(t){return k(`Could not generate preview URL: ${t instanceof Error?t.message:String(t)}`),null}}function Se(){let n=new Command("templates").description(`Manage email templates
662
+ # List 50 templates
663
+ emailr templates list --limit 50
160
664
 
161
- AGENTIC LIVE EDITING:
162
- draft Create new template with live preview
163
- edit <id> Edit existing template with live preview
665
+ # Get page 2 of results
666
+ emailr templates list --page 2 --limit 10
164
667
 
165
- Both commands save HTML to a local file and start a hot-reload server.
166
- Edit the file and see changes instantly in the browser.`);return n.command("list").description("List all templates").option("--limit <number>","Number of templates to return","20").option("--page <number>","Page number","1").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),i=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).templates.list({limit:parseInt(t.limit,10),page:parseInt(t.page,10)});if(t.format==="json")c(i,"json");else {let a=i.map(r=>({ID:r.id,Name:r.name,Subject:r.subject,Variables:r.variables?.join(", ")||"-",Created:r.created_at}));c(a,"table"),console.log(`
167
- Total: ${i.length}`);}}catch(e){l(e instanceof Error?e.message:"Failed to list templates"),process.exit(1);}}),n.command("get <id>").description("Get a template by ID").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).templates.get(t),r=await xe({id:a.id,html_content:a.html_content??void 0});if(e.format==="json")c({...a,preview_url:r},"json");else {let s={ID:a.id,Name:a.name,Subject:a.subject,Variables:a.variables?.join(", ")||"-",Created:a.created_at};r&&(s["Preview URL"]=r),c(s,"table");}}catch(o){l(o instanceof Error?o.message:"Failed to get template"),process.exit(1);}}),n.command("create").description(`Create a new template
668
+ # Get JSON output for scripting
669
+ emailr templates list --format json`).option("--limit <count>","Number of templates to return","20").option("--page <page_number>","Page number for pagination","1").option("--format <format>","Output format: json | table","table").action(async t=>{try{let e=m(),r=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).templates.list({limit:parseInt(t.limit,10),page:parseInt(t.page,10)});if(t.format==="json")c(r,"json");else {let o=r.map(n=>({ID:n.id,Name:n.name,Subject:n.subject,Variables:n.variables?.join(", ")||"-",Created:n.created_at}));c(o,"table"),console.log(`
670
+ Total: ${r.length}`);}}catch(e){l(e instanceof Error?e.message:"Failed to list templates"),process.exit(1);}}),i.command("get <template_id>").description(`Get a template by ID
168
671
 
169
- TIP: For live preview while building, use "emailr templates draft" first.
170
- It creates a local file with hot-reload preview, then use this command to save.`).requiredOption("--name <name>","Template name").requiredOption("--subject <subject>","Email subject").option("--html <html>","HTML content").option("--text <text>","Plain text content").option("--html-file <path>","Read HTML content from file").option("--text-file <path>","Read text content from file").option("--from <email>","Default from email").option("--reply-to <email>","Default reply-to email").option("--preview-text <text>","Preview text").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),i={name:t.name,subject:t.subject};if(t.htmlFile){let s=await import('fs');i.html_content=s.readFileSync(t.htmlFile,"utf-8");}else t.html&&(i.html_content=t.html);if(t.textFile){let s=await import('fs');i.text_content=s.readFileSync(t.textFile,"utf-8");}else t.text&&(i.text_content=t.text);t.from&&(i.from_email=t.from),t.replyTo&&(i.reply_to=t.replyTo),t.previewText&&(i.preview_text=t.previewText);let a=await o.templates.create(i),r=await xe({id:a.id,html_content:a.html_content??void 0});if(t.format==="json")c({...a,preview_url:r},"json");else {d(`Template created: ${a.id}`);let s={ID:a.id,Name:a.name,Subject:a.subject,Variables:a.variables?.join(", ")||"-"};r&&(s["Preview URL"]=r),c(s,"table"),r&&console.log(`
171
- Open the Preview URL in your browser to view the rendered template.`);}}catch(e){l(e instanceof Error?e.message:"Failed to create template"),process.exit(1);}}),n.command("update <id>").description(`Update a template
672
+ USAGE
673
+ emailr templates get <template_id> [options]
172
674
 
173
- TIP: For live preview while editing, use "emailr templates edit <id>" first.
174
- It downloads the template with hot-reload preview, then use this command to save.`).option("--name <name>","Template name").option("--subject <subject>","Email subject").option("--html <html>","HTML content").option("--text <text>","Plain text content").option("--html-file <path>","Read HTML content from file").option("--text-file <path>","Read text content from file").option("--from <email>","Default from email").option("--reply-to <email>","Default reply-to email").option("--preview-text <text>","Preview text").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),i=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={};if(e.name&&(a.name=e.name),e.subject&&(a.subject=e.subject),e.htmlFile){let s=await import('fs');a.html_content=s.readFileSync(e.htmlFile,"utf-8");}else e.html&&(a.html_content=e.html);if(e.textFile){let s=await import('fs');a.text_content=s.readFileSync(e.textFile,"utf-8");}else e.text&&(a.text_content=e.text);e.from&&(a.from_email=e.from),e.replyTo&&(a.reply_to=e.replyTo),e.previewText&&(a.preview_text=e.previewText);let r=await i.templates.update(t,a);if(e.format==="json")c(r,"json");else {d(`Template updated: ${r.id}`);let s={ID:r.id,Name:r.name,Subject:r.subject,Variables:r.variables?.join(", ")||"-",Updated:r.updated_at};c(s,"table");}}catch(o){l(o instanceof Error?o.message:"Failed to update template"),process.exit(1);}}),n.command("delete <id>").description("Delete a template").action(async t=>{try{let e=m();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).templates.delete(t),d(`Template deleted: ${t}`);}catch(e){l(e instanceof Error?e.message:"Failed to delete template"),process.exit(1);}}),n.command("preview <id>").description("Preview a template in the browser").option("--no-open","Do not automatically open browser").option("--foreground","Keep process running in foreground (blocks terminal)").action(async(t,e)=>{try{let o=m(),i=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl});console.log(`Fetching template ${t}...`);let a=await i.templates.get(t),r=a.html_content??"";if(z(a.id,r),e.foreground){let u=await V(a.id);if(u||(l("Failed to start preview server"),process.exit(1)),ye(),console.log(`
175
- Template: ${a.name}`),console.log(`Preview URL: ${u}`),e.open!==!1)try{await(await import('open')).default(u),console.log(`
675
+ DESCRIPTION
676
+ Retrieves detailed information about a specific template including
677
+ its HTML content, subject, variables, and preview URL if available.
678
+ Also starts a local preview server for viewing the template.
679
+
680
+ ARGUMENTS
681
+ <template_id> The unique template identifier (e.g., tpl_abc123)
682
+
683
+ OPTIONS
684
+ --format <format> Output format: json | table (default: table)
685
+
686
+ OUTPUT FORMATS
687
+ --format json Full template object including html_content and preview_url
688
+ --format table Summary table with ID, Name, Subject, Variables, Created, Preview URL
689
+
690
+ EXAMPLES
691
+ # Get template details
692
+ emailr templates get tpl_abc123
693
+
694
+ # Get full template data as JSON
695
+ emailr templates get tpl_abc123 --format json
696
+
697
+ # Pipe JSON to jq for processing
698
+ emailr templates get tpl_abc123 --format json | jq '.html_content'`).option("--format <format>","Output format: json | table","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).templates.get(t),n=await Te({id:o.id,html_content:o.html_content??void 0}),s=o.preview_html?`${a.baseUrl}/preview/${o.id}`:null;if(e.format==="json")c({...o,preview_url:s??n},"json");else {let p={ID:o.id,Name:o.name,Subject:o.subject,Variables:o.variables?.join(", ")||"-",Created:o.created_at};s?p["Preview URL"]=s:n&&(p["Local Preview"]=n),c(p,"table");}}catch(a){l(a instanceof Error?a.message:"Failed to get template"),process.exit(1);}}),i.command("fetch <template_id>").description(`Download template HTML to file or stdout
699
+
700
+ USAGE
701
+ emailr templates fetch <template_id> [options]
702
+
703
+ DESCRIPTION
704
+ Downloads the HTML content of a template to a local file or prints
705
+ it to stdout. Use this to get a local copy for editing before
706
+ pushing a preview or updating the template.
707
+
708
+ ARGUMENTS
709
+ <template_id> The unique template identifier (e.g., tpl_abc123)
710
+
711
+ OPTIONS
712
+ --output <file_path> Write HTML to specified file (default: print to stdout)
713
+ --preview Fetch preview HTML instead of published HTML
714
+
715
+ EXAMPLES
716
+ # Print HTML to stdout
717
+ emailr templates fetch tpl_abc123
718
+
719
+ # Save to file for editing
720
+ emailr templates fetch tpl_abc123 --output template.html
721
+
722
+ # Fetch preview HTML instead of published
723
+ emailr templates fetch tpl_abc123 --preview --output preview.html
724
+
725
+ # Pipe to another command
726
+ emailr templates fetch tpl_abc123 | grep "{{.*}}"
727
+
728
+ AGENTIC WORKFLOW
729
+ This is step 1 of the agentic workflow:
730
+ 1. Fetch: emailr templates fetch <id> --output template.html
731
+ 2. Edit locally or with AI assistance
732
+ 3. Push preview: emailr templates push-preview <id> --html-file template.html`).option("--output <file_path>","Write HTML to file (default: stdout)").option("--preview","Fetch preview HTML instead of published HTML").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).templates.fetch(t,{preview:e.preview??!1});if(o||(e.preview?(l("No preview HTML exists for this template"),console.log(`
733
+ Run 'emailr templates push-preview <id> --html-file <path>' to create a preview.`)):l("Template has no HTML content"),process.exit(1)),e.output){let n=N.resolve(e.output);v.writeFileSync(n,o,"utf-8"),d(`HTML saved to: ${n}`);}else console.log(o);}catch(a){l(a instanceof Error?a.message:"Failed to fetch template"),process.exit(1);}}),i.command("push-preview <template_id>").description(`Upload HTML to template's preview for sharing with AI agents
734
+
735
+ USAGE
736
+ emailr templates push-preview <template_id> --html-file <file_path>
737
+ emailr templates push-preview <template_id> --html <html_content>
738
+
739
+ DESCRIPTION
740
+ Uploads HTML content to a template's preview slot. The preview is
741
+ accessible via a public URL that can be shared with AI agents for
742
+ review and feedback. The preview persists until the template is
743
+ published or a new preview is pushed.
744
+
745
+ ARGUMENTS
746
+ <template_id> The unique template identifier (e.g., tpl_abc123)
747
+
748
+ OPTIONS
749
+ --html-file <file_path> Read HTML content from specified file
750
+ --html <html_content> Inline HTML content string
751
+ --format <format> Output format: json | table (default: table)
752
+
753
+ OUTPUT FORMATS
754
+ --format json Returns: { template_id, preview_url, status }
755
+ --format table Human-readable table with Template ID, Preview URL, Status
756
+
757
+ EXAMPLES
758
+ # Push preview from file
759
+ emailr templates push-preview tpl_abc123 --html-file template.html
760
+
761
+ # Push preview with inline HTML
762
+ emailr templates push-preview tpl_abc123 --html "<h1>Hello {{name}}</h1>"
763
+
764
+ # Get JSON output for scripting
765
+ emailr templates push-preview tpl_abc123 --html-file template.html --format json
766
+
767
+ # Extract just the preview URL
768
+ emailr templates push-preview tpl_abc123 --html-file template.html --format json | jq -r '.preview_url'
769
+
770
+ AGENTIC WORKFLOW
771
+ This is step 3 of the agentic workflow:
772
+ 1. Fetch: emailr templates fetch <id> --output template.html
773
+ 2. Edit locally or with AI assistance
774
+ 3. Push preview: emailr templates push-preview <id> --html-file template.html
775
+ 4. Share the preview URL with your AI agent for feedback
776
+ 5. Repeat steps 2-4 until satisfied`).option("--html-file <file_path>","Read HTML content from file").option("--html <html_content>","Inline HTML content").option("--format <format>","Output format: json | table","table").action(async(t,e)=>{try{!e.htmlFile&&!e.html&&(l("Either --html-file or --html is required"),console.log(`
777
+ Usage:`),console.log(" emailr templates push-preview <id> --html-file <path>"),console.log(" emailr templates push-preview <id> --html <content>"),process.exit(1)),e.htmlFile&&e.html&&(l("Cannot use both --html-file and --html. Choose one."),process.exit(1));let a=m(),r=new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}),o;if(e.htmlFile){let p=N.resolve(e.htmlFile);try{o=v.readFileSync(p,"utf-8");}catch{l(`Failed to read file: ${p}`),process.exit(1);}}else o=e.html;let n=await r.templates.pushPreview(t,o),s="updated";e.format==="json"?c({template_id:t,preview_url:n.preview_url,status:s},"json"):(d("Preview uploaded successfully"),c({"Template ID":t,"Preview URL":n.preview_url,Status:s},"table"),console.log(`
778
+ Share this URL with your AI agent for feedback.`));}catch(a){l(a instanceof Error?a.message:"Failed to push preview"),process.exit(1);}}),i.command("create").description(`Create a new email template
779
+
780
+ USAGE
781
+ emailr templates create --name <template_name> --subject <subject_line> [options]
782
+
783
+ DESCRIPTION
784
+ Creates a new email template with the specified name, subject, and content.
785
+ HTML content can be provided inline or from a file. After creation, a local
786
+ preview URL is generated for viewing the template.
787
+
788
+ OPTIONS
789
+ --name <template_name> Template name (required)
790
+ --subject <subject_line> Email subject line (required)
791
+ --html <html_content> Inline HTML content
792
+ --text <text_content> Plain text content
793
+ --html-file <file_path> Read HTML content from file
794
+ --text-file <file_path> Read text content from file
795
+ --from <email_address> Default from email address
796
+ --reply-to <email_address> Default reply-to email address
797
+ --preview-text <text> Preview text shown in email clients
798
+ --format <format> Output format: json | table (default: table)
799
+
800
+ OUTPUT FORMATS
801
+ --format json Full template object with preview_url
802
+ --format table Summary table with ID, Name, Subject, Variables, Preview URL
803
+
804
+ EXAMPLES
805
+ # Create template with inline HTML
806
+ emailr templates create --name "Welcome" --subject "Welcome to {{company}}!" \\
807
+ --html "<h1>Hello {{name}}</h1>"
808
+
809
+ # Create template from file
810
+ emailr templates create --name "Newsletter" --subject "Weekly Update" \\
811
+ --html-file newsletter.html
812
+
813
+ # Create with all options
814
+ emailr templates create --name "Order Confirmation" --subject "Order #{{order_id}}" \\
815
+ --html-file order.html --text-file order.txt \\
816
+ --from "orders@example.com" --reply-to "support@example.com"
817
+
818
+ # Get JSON output for scripting
819
+ emailr templates create --name "Test" --subject "Test" --html "<p>Test</p>" --format json
820
+
821
+ TIP
822
+ For live preview while building, use "emailr templates draft" first.
823
+ It creates a local file with hot-reload preview, then use this command to save.`).requiredOption("--name <template_name>","Template name").requiredOption("--subject <subject_line>","Email subject line").option("--html <html_content>","Inline HTML content").option("--text <text_content>","Plain text content").option("--html-file <file_path>","Read HTML content from file").option("--text-file <file_path>","Read text content from file").option("--from <email_address>","Default from email address").option("--reply-to <email_address>","Default reply-to email address").option("--preview-text <text>","Preview text shown in email clients").option("--format <format>","Output format: json | table","table").action(async t=>{try{let e=m(),a=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r={name:t.name,subject:t.subject};if(t.htmlFile){let s=await import('fs');r.html_content=s.readFileSync(t.htmlFile,"utf-8");}else t.html&&(r.html_content=t.html);if(t.textFile){let s=await import('fs');r.text_content=s.readFileSync(t.textFile,"utf-8");}else t.text&&(r.text_content=t.text);t.from&&(r.from_email=t.from),t.replyTo&&(r.reply_to=t.replyTo),t.previewText&&(r.preview_text=t.previewText);let o=await a.templates.create(r),n=await Te({id:o.id,html_content:o.html_content??void 0});if(t.format==="json")c({...o,preview_url:n},"json");else {d(`Template created: ${o.id}`);let s={ID:o.id,Name:o.name,Subject:o.subject,Variables:o.variables?.join(", ")||"-"};n&&(s["Preview URL"]=n),c(s,"table"),n&&console.log(`
824
+ Open the Preview URL in your browser to view the rendered template.`);}}catch(e){l(e instanceof Error?e.message:"Failed to create template"),process.exit(1);}}),i.command("update <template_id>").description(`Update an existing email template
825
+
826
+ USAGE
827
+ emailr templates update <template_id> [options]
828
+
829
+ DESCRIPTION
830
+ Updates an existing template with new values. Only specified fields
831
+ are updated; omitted fields remain unchanged. Use this to publish
832
+ changes after editing with the agentic workflow.
833
+
834
+ ARGUMENTS
835
+ <template_id> The unique template identifier (e.g., tpl_abc123)
836
+
837
+ OPTIONS
838
+ --name <template_name> New template name
839
+ --subject <subject_line> New email subject line
840
+ --html <html_content> New inline HTML content
841
+ --text <text_content> New plain text content
842
+ --html-file <file_path> Read new HTML content from file
843
+ --text-file <file_path> Read new text content from file
844
+ --from <email_address> New default from email address
845
+ --reply-to <email_address> New default reply-to email address
846
+ --preview-text <text> New preview text
847
+ --format <format> Output format: json | table (default: table)
848
+
849
+ OUTPUT FORMATS
850
+ --format json Full updated template object
851
+ --format table Summary table with ID, Name, Subject, Variables, Updated
852
+
853
+ EXAMPLES
854
+ # Update subject line
855
+ emailr templates update tpl_abc123 --subject "New Subject Line"
856
+
857
+ # Update HTML from file (publish after editing)
858
+ emailr templates update tpl_abc123 --html-file template.html
859
+
860
+ # Update multiple fields
861
+ emailr templates update tpl_abc123 --name "Updated Name" --subject "Updated Subject"
862
+
863
+ # Get JSON output
864
+ emailr templates update tpl_abc123 --subject "Test" --format json
865
+
866
+ TIP
867
+ For live preview while editing, use "emailr templates edit <id>" first.
868
+ It downloads the template with hot-reload preview, then use this command to save.
869
+
870
+ AGENTIC WORKFLOW
871
+ This is the final step of the agentic workflow:
872
+ 1. Fetch: emailr templates fetch <id> --output template.html
873
+ 2. Edit locally or with AI assistance
874
+ 3. Push preview: emailr templates push-preview <id> --html-file template.html
875
+ 4. Share URL with AI agent, iterate on feedback
876
+ 5. Publish: emailr templates update <id> --html-file template.html`).option("--name <template_name>","New template name").option("--subject <subject_line>","New email subject line").option("--html <html_content>","New inline HTML content").option("--text <text_content>","New plain text content").option("--html-file <file_path>","Read new HTML content from file").option("--text-file <file_path>","Read new text content from file").option("--from <email_address>","New default from email address").option("--reply-to <email_address>","New default reply-to email address").option("--preview-text <text>","New preview text").option("--format <format>","Output format: json | table","table").action(async(t,e)=>{try{let a=m(),r=new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}),o={};if(e.name&&(o.name=e.name),e.subject&&(o.subject=e.subject),e.htmlFile){let s=await import('fs');o.html_content=s.readFileSync(e.htmlFile,"utf-8");}else e.html&&(o.html_content=e.html);if(e.textFile){let s=await import('fs');o.text_content=s.readFileSync(e.textFile,"utf-8");}else e.text&&(o.text_content=e.text);e.from&&(o.from_email=e.from),e.replyTo&&(o.reply_to=e.replyTo),e.previewText&&(o.preview_text=e.previewText);let n=await r.templates.update(t,o);if(e.format==="json")c(n,"json");else {d(`Template updated: ${n.id}`);let s={ID:n.id,Name:n.name,Subject:n.subject,Variables:n.variables?.join(", ")||"-",Updated:n.updated_at};c(s,"table");}}catch(a){l(a instanceof Error?a.message:"Failed to update template"),process.exit(1);}}),i.command("delete <template_id>").description(`Delete a template
877
+
878
+ USAGE
879
+ emailr templates delete <template_id>
880
+
881
+ DESCRIPTION
882
+ Permanently deletes a template. This action cannot be undone.
883
+ Any broadcasts or automations using this template will fail.
884
+
885
+ ARGUMENTS
886
+ <template_id> The unique template identifier (e.g., tpl_abc123)
887
+
888
+ EXAMPLES
889
+ # Delete a template
890
+ emailr templates delete tpl_abc123
891
+
892
+ WARNING
893
+ This action is permanent and cannot be undone.`).action(async t=>{try{let e=m();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).templates.delete(t),d(`Template deleted: ${t}`);}catch(e){l(e instanceof Error?e.message:"Failed to delete template"),process.exit(1);}}),i.command("preview <template_id>").description(`Preview a template in the browser
894
+
895
+ USAGE
896
+ emailr templates preview <template_id> [options]
897
+
898
+ DESCRIPTION
899
+ Opens a template preview in your default browser. By default, runs
900
+ a background server that serves the template HTML. Use --foreground
901
+ to keep the process running in the terminal.
902
+
903
+ ARGUMENTS
904
+ <template_id> The unique template identifier (e.g., tpl_abc123)
905
+
906
+ OPTIONS
907
+ --no-open Do not automatically open browser
908
+ --foreground Keep process running in foreground (blocks terminal)
909
+
910
+ EXAMPLES
911
+ # Preview template in browser
912
+ emailr templates preview tpl_abc123
913
+
914
+ # Preview without opening browser (just get URL)
915
+ emailr templates preview tpl_abc123 --no-open
916
+
917
+ # Run in foreground (Ctrl+C to stop)
918
+ emailr templates preview tpl_abc123 --foreground
919
+
920
+ SEE ALSO
921
+ emailr templates edit Live editing with hot-reload
922
+ emailr templates stop-preview Stop background preview server`).option("--no-open","Do not automatically open browser").option("--foreground","Keep process running in foreground (blocks terminal)").action(async(t,e)=>{try{let a=m(),r=new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl});console.log(`Fetching template ${t}...`);let o=await r.templates.get(t),n=o.html_content??"";if(X(o.id,n),e.foreground){let f=await V(o.id);if(f||(l("Failed to start preview server"),process.exit(1)),ye(),console.log(`
923
+ Template: ${o.name}`),console.log(`Preview URL: ${f}`),e.open!==!1)try{await(await import('open')).default(f),console.log(`
176
924
  Browser opened.`);}catch{console.log(`
177
925
  Could not open browser automatically. Open the URL above manually.`);}process.on("SIGINT",async()=>{console.log(`
178
926
 
179
- Stopping preview server...`),await he().stop(),console.log("Done."),process.exit(0);});return}let s=U.join(process.cwd(),`.template-preview-${a.id}.html`);E.writeFileSync(s,r,"utf-8");let{spawn:f}=await import('child_process'),g=await import('os'),x=U.join(g.tmpdir(),"emailr-preview.pid");try{let u=E.readFileSync(x,"utf-8").trim();process.kill(parseInt(u,10),"SIGTERM");}catch{}let b=`
927
+ Stopping preview server...`),await we().stop(),console.log("Done."),process.exit(0);});return}let s=N.join(process.cwd(),`.template-preview-${o.id}.html`);v.writeFileSync(s,n,"utf-8");let{spawn:p}=await import('child_process'),b=await import('os'),T=N.join(b.tmpdir(),"emailr-preview.pid");try{let f=v.readFileSync(T,"utf-8").trim();process.kill(parseInt(f,10),"SIGTERM");}catch{}let h=`
180
928
  const http = require('http');
181
929
  const fs = require('fs');
182
930
  const path = require('path');
@@ -201,25 +949,59 @@ Stopping preview server...`),await he().stop(),console.log("Done."),process.exit
201
949
  console.log('PORT:' + port);
202
950
  });
203
951
  process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); fs.unlinkSync(filePath); } catch {} process.exit(0); });
204
- `,w=f("node",["-e",b],{detached:!0,stdio:["ignore","pipe","ignore"]}),S="";await new Promise(u=>{w.stdout?.on("data",C=>{let q=C.toString().match(/PORT:(\d+)/);q&&(S=q[1],u());}),setTimeout(u,3e3);}),w.unref();let D=`http://127.0.0.1:${S}/`;if(console.log(`
205
- Template: ${a.name}`),console.log(`Preview URL: ${D}`),console.log(`
206
- To stop: emailr templates stop-preview`),e.open!==!1)try{await(await import('open')).default(D);}catch{}process.exit(0);}catch(o){l(o instanceof Error?o.message:"Failed to preview template"),process.exit(1);}}),n.command("edit <id>").description(`Start a live editing session for a template.
207
-
208
- WORKFLOW:
209
- 1. Run: emailr templates edit <id> --file ./template.html
210
- 2. Edit the file at ./template.html - browser auto-refreshes on save
211
- 3. When done: emailr templates update <id> --html-file ./template.html
212
- 4. Stop server: emailr templates stop-preview`).option("--file <path>","Path to save the HTML file for editing","./template.html").option("--no-open","Do not automatically open browser").option("--foreground","Keep process running in foreground (blocks terminal)").action(async(t,e)=>{try{let o=m(),i=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a=U.resolve(e.file);console.log(`Fetching template ${t}...`);let r=await i.templates.get(t),s=r.html_content??"";if(E.writeFileSync(a,s,"utf-8"),console.log(`Template saved to: ${a}`),e.foreground){let C=`http://127.0.0.1:${await Q(a,()=>{console.log("File changed - browser refreshed");})}/`;if(console.log(`
213
- Template: ${r.name}`),console.log(`Template ID: ${r.id}`),console.log(`Live Preview: ${C}`),console.log(`File: ${a}`),console.log(`
214
- Watching for changes... Edit the file and see live updates.`),console.log(`When done, run: emailr templates update ${t} --html-file ${e.file}`),e.open!==!1)try{await(await import('open')).default(C);}catch{}process.on("SIGINT",async()=>{console.log(`
215
-
216
- Stopping live preview server...`),await X(),console.log("Done."),console.log(`
217
- To save changes: emailr templates update ${t} --html-file ${e.file}`),process.exit(0);});return}let{spawn:f}=await import('child_process'),g=await import('os'),x=U.join(g.tmpdir(),"emailr-preview.pid");try{let u=E.readFileSync(x,"utf-8").trim();process.kill(parseInt(u,10),"SIGTERM");}catch{}let b=`
952
+ `,y=p("node",["-e",h],{detached:!0,stdio:["ignore","pipe","ignore"]}),O="";await new Promise(f=>{y.stdout?.on("data",_=>{let q=_.toString().match(/PORT:(\d+)/);q&&(O=q[1],f());}),setTimeout(f,3e3);}),y.unref();let D=`http://127.0.0.1:${O}/`;if(console.log(`
953
+ Template: ${o.name}`),console.log(`Preview URL: ${D}`),console.log(`
954
+ To stop: emailr templates stop-preview`),e.open!==!1)try{await(await import('open')).default(D);}catch{}process.exit(0);}catch(a){l(a instanceof Error?a.message:"Failed to preview template"),process.exit(1);}}),i.command("edit <template_id>").description(`Start a live editing session for a template with hot-reload
955
+
956
+ USAGE
957
+ emailr templates edit <template_id> [options]
958
+
959
+ DESCRIPTION
960
+ Downloads a template's HTML to a local file and starts a live preview
961
+ server with hot-reload. Edit the file in your editor and see changes
962
+ instantly in the browser. When done, save changes with the update command.
963
+
964
+ ARGUMENTS
965
+ <template_id> The unique template identifier (e.g., tpl_abc123)
966
+
967
+ OPTIONS
968
+ --file <file_path> Path to save the HTML file for editing (default: ./template.html)
969
+ --no-open Do not automatically open browser
970
+ --foreground Keep process running in foreground (blocks terminal)
971
+
972
+ WORKFLOW
973
+ 1. Run: emailr templates edit <id> --file ./template.html
974
+ 2. Edit the file at ./template.html - browser auto-refreshes on save
975
+ 3. When done: emailr templates update <id> --html-file ./template.html
976
+ 4. Stop server: emailr templates stop-preview
977
+
978
+ EXAMPLES
979
+ # Start editing with default file path
980
+ emailr templates edit tpl_abc123
981
+
982
+ # Specify custom file path
983
+ emailr templates edit tpl_abc123 --file ./emails/welcome.html
984
+
985
+ # Run in foreground (Ctrl+C to stop)
986
+ emailr templates edit tpl_abc123 --foreground
987
+
988
+ # Don't open browser automatically
989
+ emailr templates edit tpl_abc123 --no-open
990
+
991
+ SEE ALSO
992
+ emailr templates draft Draft a new template with live preview
993
+ emailr templates update Save changes to the template
994
+ emailr templates stop-preview Stop the background preview server`).option("--file <file_path>","Path to save the HTML file for editing","./template.html").option("--no-open","Do not automatically open browser").option("--foreground","Keep process running in foreground (blocks terminal)").action(async(t,e)=>{try{let a=m(),r=new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}),o=N.resolve(e.file);console.log(`Fetching template ${t}...`);let n=await r.templates.get(t),s=n.html_content??"";if(v.writeFileSync(o,s,"utf-8"),console.log(`Template saved to: ${o}`),e.foreground){let _=`http://127.0.0.1:${await Y(o,()=>{console.log("File changed - browser refreshed");})}/`;if(console.log(`
995
+ Template: ${n.name}`),console.log(`Template ID: ${n.id}`),console.log(`Live Preview: ${_}`),console.log(`File: ${o}`),console.log(`
996
+ Watching for changes... Edit the file and see live updates.`),console.log(`When done, run: emailr templates update ${t} --html-file ${e.file}`),e.open!==!1)try{await(await import('open')).default(_);}catch{}process.on("SIGINT",async()=>{console.log(`
997
+
998
+ Stopping live preview server...`),await Z(),console.log("Done."),console.log(`
999
+ To save changes: emailr templates update ${t} --html-file ${e.file}`),process.exit(0);});return}let{spawn:p}=await import('child_process'),b=await import('os'),T=N.join(b.tmpdir(),"emailr-preview.pid");try{let f=v.readFileSync(T,"utf-8").trim();process.kill(parseInt(f,10),"SIGTERM");}catch{}let h=`
218
1000
  const http = require('http');
219
1001
  const fs = require('fs');
220
1002
  const path = require('path');
221
1003
  const os = require('os');
222
- const filePath = ${JSON.stringify(a)};
1004
+ const filePath = ${JSON.stringify(o)};
223
1005
  const pidFile = path.join(os.tmpdir(), 'emailr-preview.pid');
224
1006
  const portFile = path.join(os.tmpdir(), 'emailr-preview.port');
225
1007
  let clients = [];
@@ -254,16 +1036,52 @@ To save changes: emailr templates update ${t} --html-file ${e.file}`),process.ex
254
1036
  });
255
1037
  });
256
1038
  process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); } catch {} process.exit(0); });
257
- `,w=f("node",["-e",b],{detached:!0,stdio:["ignore","pipe","ignore"]}),S="";await new Promise(u=>{w.stdout?.on("data",C=>{let ne=C.toString().match(/PORT:(\d+)/);ne&&(S=ne[1],u());}),setTimeout(u,3e3);}),w.unref();let D=`http://127.0.0.1:${S}/`;if(console.log(`
258
- Template: ${r.name}`),console.log(`Template ID: ${r.id}`),console.log(`Live Preview: ${D}`),console.log(`File: ${a}`),console.log(`
1039
+ `,y=p("node",["-e",h],{detached:!0,stdio:["ignore","pipe","ignore"]}),O="";await new Promise(f=>{y.stdout?.on("data",_=>{let ie=_.toString().match(/PORT:(\d+)/);ie&&(O=ie[1],f());}),setTimeout(f,3e3);}),y.unref();let D=`http://127.0.0.1:${O}/`;if(console.log(`
1040
+ Template: ${n.name}`),console.log(`Template ID: ${n.id}`),console.log(`Live Preview: ${D}`),console.log(`File: ${o}`),console.log(`
259
1041
  Edit the file and see live updates in the browser.`),console.log(`
260
- When done:`),console.log(` emailr templates update ${t} --html-file ${e.file}`),console.log(" emailr templates stop-preview"),e.open!==!1)try{await(await import('open')).default(D);}catch{}process.exit(0);}catch(o){l(o instanceof Error?o.message:"Failed to edit template"),process.exit(1);}}),n.command("draft").description(`Start a live drafting session for a new template.
1042
+ When done:`),console.log(` emailr templates update ${t} --html-file ${e.file}`),console.log(" emailr templates stop-preview"),e.open!==!1)try{await(await import('open')).default(D);}catch{}process.exit(0);}catch(a){l(a instanceof Error?a.message:"Failed to edit template"),process.exit(1);}}),i.command("draft").description(`Start a live drafting session for a new template with hot-reload
1043
+
1044
+ USAGE
1045
+ emailr templates draft [options]
1046
+
1047
+ DESCRIPTION
1048
+ Creates a new HTML file with a starter template and starts a live
1049
+ preview server with hot-reload. Edit the file in your editor and
1050
+ see changes instantly in the browser. When done, create the template
1051
+ with the create command.
1052
+
1053
+ OPTIONS
1054
+ --file <file_path> Path to save the HTML file for drafting (default: ./new-template.html)
1055
+ --no-open Do not automatically open browser
1056
+ --blank Start with a blank file instead of starter template
1057
+ --foreground Keep process running in foreground (blocks terminal)
261
1058
 
262
- WORKFLOW:
263
- 1. Run: emailr templates draft --file ./new-template.html
264
- 2. Edit the file at ./new-template.html - browser auto-refreshes on save
265
- 3. When done: emailr templates create --name "My Template" --subject "Subject" --html-file ./new-template.html
266
- 4. Stop server: emailr templates stop-preview`).option("--file <path>","Path to save the HTML file for drafting","./new-template.html").option("--no-open","Do not automatically open browser").option("--blank","Start with a blank file instead of starter template").option("--foreground","Keep process running in foreground (blocks terminal)").action(async t=>{try{let e=U.resolve(t.file),o;if(t.blank?o="":o=`<!DOCTYPE html>
1059
+ WORKFLOW
1060
+ 1. Run: emailr templates draft --file ./new-template.html
1061
+ 2. Edit the file at ./new-template.html - browser auto-refreshes on save
1062
+ 3. When done: emailr templates create --name "My Template" --subject "Subject" --html-file ./new-template.html
1063
+ 4. Stop server: emailr templates stop-preview
1064
+
1065
+ EXAMPLES
1066
+ # Start drafting with default file path
1067
+ emailr templates draft
1068
+
1069
+ # Specify custom file path
1070
+ emailr templates draft --file ./emails/new-welcome.html
1071
+
1072
+ # Start with blank file (no starter template)
1073
+ emailr templates draft --blank
1074
+
1075
+ # Run in foreground (Ctrl+C to stop)
1076
+ emailr templates draft --foreground
1077
+
1078
+ # Don't open browser automatically
1079
+ emailr templates draft --no-open
1080
+
1081
+ SEE ALSO
1082
+ emailr templates edit Edit an existing template with live preview
1083
+ emailr templates create Create the template from your draft
1084
+ emailr templates stop-preview Stop the background preview server`).option("--file <file_path>","Path to save the HTML file for drafting","./new-template.html").option("--no-open","Do not automatically open browser").option("--blank","Start with a blank file instead of starter template").option("--foreground","Keep process running in foreground (blocks terminal)").action(async t=>{try{let e=N.resolve(t.file),a;if(t.blank?a="":a=`<!DOCTYPE html>
267
1085
  <html>
268
1086
  <head>
269
1087
  <meta charset="UTF-8">
@@ -298,13 +1116,13 @@ WORKFLOW:
298
1116
  <p><a href="{{unsubscribe_link}}">Unsubscribe</a></p>
299
1117
  </div>
300
1118
  </body>
301
- </html>`,E.writeFileSync(e,o,"utf-8"),console.log(`Template draft created: ${e}`),t.foreground){let w=`http://127.0.0.1:${await Q(e,()=>{console.log("File changed - browser refreshed");})}/`;if(console.log(`
302
- Live Preview: ${w}`),console.log(`File: ${e}`),console.log(`
1119
+ </html>`,v.writeFileSync(e,a,"utf-8"),console.log(`Template draft created: ${e}`),t.foreground){let y=`http://127.0.0.1:${await Y(e,()=>{console.log("File changed - browser refreshed");})}/`;if(console.log(`
1120
+ Live Preview: ${y}`),console.log(`File: ${e}`),console.log(`
303
1121
  Watching for changes... Edit the file and see live updates.`),console.log(`
304
- When done, create the template:`),console.log(` emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${t.file}`),t.open!==!1)try{await(await import('open')).default(w);}catch{}process.on("SIGINT",async()=>{console.log(`
1122
+ When done, create the template:`),console.log(` emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${t.file}`),t.open!==!1)try{await(await import('open')).default(y);}catch{}process.on("SIGINT",async()=>{console.log(`
305
1123
 
306
- Stopping live preview server...`),await X(),console.log("Done."),console.log(`
307
- To create template: emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${t.file}`),process.exit(0);});return}let{spawn:i}=await import('child_process'),a=await import('os'),r=U.join(a.tmpdir(),"emailr-preview.pid");try{let b=E.readFileSync(r,"utf-8").trim();process.kill(parseInt(b,10),"SIGTERM");}catch{}let s=`
1124
+ Stopping live preview server...`),await Z(),console.log("Done."),console.log(`
1125
+ To create template: emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${t.file}`),process.exit(0);});return}let{spawn:r}=await import('child_process'),o=await import('os'),n=N.join(o.tmpdir(),"emailr-preview.pid");try{let h=v.readFileSync(n,"utf-8").trim();process.kill(parseInt(h,10),"SIGTERM");}catch{}let s=`
308
1126
  const http = require('http');
309
1127
  const fs = require('fs');
310
1128
  const path = require('path');
@@ -344,61 +1162,1688 @@ To create template: emailr templates create --name "Template Name" --subject "Em
344
1162
  });
345
1163
  });
346
1164
  process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); } catch {} process.exit(0); });
347
- `,f=i("node",["-e",s],{detached:!0,stdio:["ignore","pipe","ignore"]}),g="";await new Promise(b=>{f.stdout?.on("data",w=>{let S=w.toString().match(/PORT:(\d+)/);S&&(g=S[1],b());}),setTimeout(b,3e3);}),f.unref();let x=`http://127.0.0.1:${g}/`;if(console.log(`
348
- Live Preview: ${x}`),console.log(`File: ${e}`),console.log(`
1165
+ `,p=r("node",["-e",s],{detached:!0,stdio:["ignore","pipe","ignore"]}),b="";await new Promise(h=>{p.stdout?.on("data",y=>{let O=y.toString().match(/PORT:(\d+)/);O&&(b=O[1],h());}),setTimeout(h,3e3);}),p.unref();let T=`http://127.0.0.1:${b}/`;if(console.log(`
1166
+ Live Preview: ${T}`),console.log(`File: ${e}`),console.log(`
349
1167
  Edit the file and see live updates in the browser.`),console.log(`
350
- When done:`),console.log(` emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${t.file}`),console.log(" emailr templates stop-preview"),t.open!==!1)try{await(await import('open')).default(x);}catch{}process.exit(0);}catch(e){l(e instanceof Error?e.message:"Failed to start draft session"),process.exit(1);}}),n.command("stop-preview").description("Stop any running background preview server").action(async()=>{let t=await import('os'),e=U.join(t.tmpdir(),"emailr-preview.pid");try{let o=E.readFileSync(e,"utf-8").trim();process.kill(parseInt(o,10),"SIGTERM"),E.unlinkSync(e),d("Preview server stopped");}catch{d("No preview server running");}}),n}function Ce(){let n=new Command("domains").description("Manage sending domains");return n.command("list").description("List all domains").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),i=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).domains.list();if(t.format==="json")c(i,"json");else {let a=i.map(r=>({ID:r.id,Domain:r.domain,Status:r.status,DKIM:r.dkim_verified,SPF:r.spf_verified,DMARC:r.dmarc_verified,Created:r.created_at}));c(a,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list domains"),process.exit(1);}}),n.command("get <id>").description("Get a domain by ID").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).domains.get(t);c(a,e.format);}catch(o){l(o instanceof Error?o.message:"Failed to get domain"),process.exit(1);}}),n.command("add <domain>").description("Add a new domain").option("--receiving-subdomain <subdomain>","Subdomain for receiving emails").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),i=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={domain:t};e.receivingSubdomain&&(a.receivingSubdomain=e.receivingSubdomain);let r=await i.domains.add(a);if(e.format==="json")c(r,"json");else {if(d(`Domain added: ${r.domain}`),p("Add the following DNS records to verify your domain:"),console.log(""),r.dns_records){let s=[{Type:"DKIM",...Z(r.dns_records.dkim)},{Type:"SPF",...Z(r.dns_records.spf)},{Type:"DMARC",...Z(r.dns_records.dmarc)}];c(s,"table");}console.log(""),p(`Run 'emailr domains verify ${r.id}' after adding DNS records`);}}catch(o){l(o instanceof Error?o.message:"Failed to add domain"),process.exit(1);}}),n.command("verify <id>").description("Verify a domain").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).domains.verify(t);e.format==="json"?c(a,"json"):a.verified?d("Domain verified successfully!"):(p(`Domain status: ${a.status}`),a.dkim_status&&p(`DKIM status: ${a.dkim_status}`));}catch(o){l(o instanceof Error?o.message:"Failed to verify domain"),process.exit(1);}}),n.command("check-dns <id>").description("Check DNS records for a domain").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).domains.checkDns(t);if(e.format==="json")c(a,"json");else {let r=Object.entries(a).map(([s,f])=>({Record:s,Verified:f.verified,Expected:f.expected||"-",Found:f.found?.join(", ")||"-"}));c(r,"table");}}catch(o){l(o instanceof Error?o.message:"Failed to check DNS"),process.exit(1);}}),n.command("delete <id>").description("Delete a domain").action(async t=>{try{let e=m();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).domains.delete(t),d(`Domain deleted: ${t}`);}catch(e){l(e instanceof Error?e.message:"Failed to delete domain"),process.exit(1);}}),n}function Z(n){return {"Record Type":n.type,Name:n.name,Value:n.value.length>50?n.value.substring(0,47)+"...":n.value,...n.priority!==void 0&&{Priority:n.priority}}}function je(){let n=new Command("config").description("Manage CLI configuration");return n.command("set <key> <value>").description("Set a configuration value").action(async(t,e)=>{try{let o=["api-key","base-url","format"],i=t.toLowerCase();o.includes(i)||(l(`Invalid config key: ${t}`),p(`Valid keys: ${o.join(", ")}`),process.exit(1));let r={"api-key":"apiKey","base-url":"baseUrl",format:"format"}[i];i==="format"&&!["json","table"].includes(e)&&(l(`Invalid format value: ${e}`),p("Valid formats: json, table"),process.exit(1)),A({[r]:e}),d(`Configuration saved: ${t} = ${i==="api-key"?"***":e}`),p(`Config file: ${j()}`);}catch(o){l(o instanceof Error?o.message:"Failed to save configuration"),process.exit(1);}}),n.command("get <key>").description("Get a configuration value").action(async t=>{try{let e=["api-key","base-url","format"],o=t.toLowerCase();e.includes(o)||(l(`Invalid config key: ${t}`),p(`Valid keys: ${e.join(", ")}`),process.exit(1));let a={"api-key":"apiKey","base-url":"baseUrl",format:"format"}[o],r=re(a);r?console.log(o==="api-key"?r.substring(0,8)+"..."+r.substring(r.length-4):r):p(`${t} is not set`);}catch(e){l(e instanceof Error?e.message:"Failed to get configuration"),process.exit(1);}}),n.command("list").description("List all configuration values").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o={"api-key":e.apiKey?e.apiKey.substring(0,8)+"..."+e.apiKey.substring(e.apiKey.length-4):"(not set)","base-url":e.baseUrl||"(default)",format:e.format||"table"};if(t.format==="json")c(o,"json");else {let i=Object.entries(o).map(([a,r])=>({Key:a,Value:r}));c(i,"table");}console.log(""),p(`Config file: ${j()}`);}catch(e){e instanceof Error&&e.message.includes("No API key configured")?(p("No configuration found."),p("Run 'emailr config set api-key <your-api-key>' to get started.")):(l(e instanceof Error?e.message:"Failed to list configuration"),process.exit(1));}}),n.command("path").description("Show the configuration file path").action(()=>{console.log(j());}),n.command("init").description("Initialize configuration interactively").option("--api-key <key>","API key to use").option("--base-url <url>","Base URL for API").action(async t=>{try{t.apiKey?(A({apiKey:t.apiKey,baseUrl:t.baseUrl}),d("Configuration initialized!"),p(`Config file: ${j()}`)):(p("Initialize your Emailr CLI configuration:"),console.log(""),p("Run with --api-key flag:"),console.log(" emailr config init --api-key <your-api-key>"),console.log(""),p("Or set environment variable:"),console.log(" export EMAILR_API_KEY=<your-api-key>"));}catch(e){l(e instanceof Error?e.message:"Failed to initialize configuration"),process.exit(1);}}),n}function ke(){let n=new Command("broadcasts").description("Manage broadcast campaigns");return n.command("list").description("List all broadcasts").option("--status <status>","Filter by status (draft, scheduled, sending, sent)").option("--limit <number>","Number of broadcasts to return","20").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),i=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).broadcasts.list({status:t.status,limit:parseInt(t.limit)});if(t.format==="json")c(i,"json");else {if(i.length===0){console.log("No broadcasts found.");return}let a=i.map(r=>({ID:r.id,Name:r.name,Subject:r.subject.substring(0,30)+(r.subject.length>30?"...":""),Status:r.status,Recipients:r.total_recipients||0,Sent:r.sent_count||0,Created:new Date(r.created_at).toLocaleDateString()}));c(a,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list broadcasts"),process.exit(1);}}),n.command("get <id>").description("Get broadcast details").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.get(t);e.format==="json"?c(a,"json"):c({ID:a.id,Name:a.name,Subject:a.subject,"From Email":a.from_email,Status:a.status,"Total Recipients":a.total_recipients||0,"Sent Count":a.sent_count||0,Delivered:a.delivered_count||0,Opened:a.opened_count||0,Clicked:a.clicked_count||0,Bounced:a.bounced_count||0,"Scheduled At":a.scheduled_at||"N/A","Started At":a.started_at||"N/A","Completed At":a.completed_at||"N/A","Created At":a.created_at},"table");}catch(o){l(o instanceof Error?o.message:"Failed to get broadcast"),process.exit(1);}}),n.command("create").description("Create a new broadcast").requiredOption("--name <name>","Broadcast name").requiredOption("--subject <subject>","Email subject").requiredOption("--from <email>","Sender email address").option("--template <id>","Template ID to use").option("--segment <id>","Segment ID to target").option("--html <html>","HTML content").option("--text <text>","Plain text content").option("--schedule <datetime>","Schedule time (ISO 8601)").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),i=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).broadcasts.create({name:t.name,subject:t.subject,from_email:t.from,template_id:t.template,segment_id:t.segment,html_content:t.html,text_content:t.text,scheduled_at:t.schedule});t.format==="json"?c(i,"json"):(d("Broadcast created successfully!"),c({ID:i.id,Name:i.name,Status:i.status,"Scheduled At":i.scheduled_at||"Not scheduled"},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create broadcast"),process.exit(1);}}),n.command("send <id>").description("Send a broadcast immediately").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.send(t);e.format==="json"?c(a,"json"):(d("Broadcast sent successfully!"),c({Success:a.success,Sent:a.sent,Total:a.total},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to send broadcast"),process.exit(1);}}),n.command("schedule <id>").description("Schedule a broadcast").requiredOption("--at <datetime>","Schedule time (ISO 8601)").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.schedule(t,e.at);e.format==="json"?c(a,"json"):(d("Broadcast scheduled successfully!"),c({ID:a.id,Name:a.name,Status:a.status,"Scheduled At":a.scheduled_at},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to schedule broadcast"),process.exit(1);}}),n.command("cancel <id>").description("Cancel a scheduled broadcast").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.cancel(t);e.format==="json"?c(a,"json"):d("Broadcast cancelled successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to cancel broadcast"),process.exit(1);}}),n.command("delete <id>").description("Delete a broadcast").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.delete(t);e.format==="json"?c(a,"json"):d("Broadcast deleted successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to delete broadcast"),process.exit(1);}}),n}function Ee(){let n=new Command("webhooks").description("Manage webhooks");return n.command("list").description("List all webhooks").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),i=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).webhooks.list();if(t.format==="json")c(i,"json");else {if(i.length===0){console.log("No webhooks found.");return}let a=i.map(r=>({ID:r.id,Name:r.name,URL:r.url.substring(0,40)+(r.url.length>40?"...":""),Events:r.events.join(", "),Active:r.active?"Yes":"No"}));c(a,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list webhooks"),process.exit(1);}}),n.command("get <id>").description("Get webhook details").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.get(t);e.format==="json"?c(a,"json"):c({ID:a.id,Name:a.name,URL:a.url,Events:a.events.join(", "),Active:a.active?"Yes":"No",Secret:a.secret,"Created At":a.created_at},"table");}catch(o){l(o instanceof Error?o.message:"Failed to get webhook"),process.exit(1);}}),n.command("create").description("Create a new webhook").requiredOption("--name <name>","Webhook name").requiredOption("--url <url>","Webhook URL").requiredOption("--events <events>","Events to subscribe to (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),i=t.events.split(",").map(r=>r.trim()),a=await o.webhooks.create({name:t.name,url:t.url,events:i});t.format==="json"?c(a,"json"):(d("Webhook created successfully!"),c({ID:a.id,Name:a.name,URL:a.url,Secret:a.secret},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create webhook"),process.exit(1);}}),n.command("update <id>").description("Update a webhook").option("--name <name>","New webhook name").option("--url <url>","New webhook URL").option("--events <events>","New events (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),i=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={};e.name&&(a.name=e.name),e.url&&(a.url=e.url),e.events&&(a.events=e.events.split(",").map(s=>s.trim()));let r=await i.webhooks.update(t,a);e.format==="json"?c(r,"json"):(d("Webhook updated successfully!"),c({ID:r.id,Name:r.name,URL:r.url},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update webhook"),process.exit(1);}}),n.command("enable <id>").description("Enable a webhook").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.enable(t);e.format==="json"?c(a,"json"):d("Webhook enabled successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to enable webhook"),process.exit(1);}}),n.command("disable <id>").description("Disable a webhook").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.disable(t);e.format==="json"?c(a,"json"):d("Webhook disabled successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to disable webhook"),process.exit(1);}}),n.command("delete <id>").description("Delete a webhook").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.delete(t);e.format==="json"?c(a,"json"):d("Webhook deleted successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to delete webhook"),process.exit(1);}}),n}function Fe(){let n=new Command("segments").description("Manage contact segments");return n.command("list").description("List all segments").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),i=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).segments.list();if(t.format==="json")c(i,"json");else {if(i.length===0){console.log("No segments found.");return}let a=i.map(r=>({ID:r.id,Name:r.name,Description:(r.description||"").substring(0,30)+((r.description?.length||0)>30?"...":""),"Created At":new Date(r.created_at).toLocaleDateString()}));c(a,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list segments"),process.exit(1);}}),n.command("get <id>").description("Get segment details").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).segments.get(t);e.format==="json"?c(a,"json"):c({ID:a.id,Name:a.name,Description:a.description||"N/A",Conditions:JSON.stringify(a.conditions),"Created At":a.created_at,"Updated At":a.updated_at},"table");}catch(o){l(o instanceof Error?o.message:"Failed to get segment"),process.exit(1);}}),n.command("create").description("Create a new segment").requiredOption("--name <name>","Segment name").requiredOption("--conditions <json>","Segment conditions as JSON").option("--description <description>","Segment description").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),i;try{i=JSON.parse(t.conditions);}catch{l("Invalid JSON for --conditions"),process.exit(1);}let a=await o.segments.create({name:t.name,description:t.description,conditions:i});t.format==="json"?c(a,"json"):(d("Segment created successfully!"),c({ID:a.id,Name:a.name},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create segment"),process.exit(1);}}),n.command("update <id>").description("Update a segment").option("--name <name>","New segment name").option("--description <description>","New description").option("--conditions <json>","New conditions as JSON").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),i=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={};if(e.name&&(a.name=e.name),e.description&&(a.description=e.description),e.conditions)try{a.conditions=JSON.parse(e.conditions);}catch{l("Invalid JSON for --conditions"),process.exit(1);}let r=await i.segments.update(t,a);e.format==="json"?c(r,"json"):(d("Segment updated successfully!"),c({ID:r.id,Name:r.name},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update segment"),process.exit(1);}}),n.command("delete <id>").description("Delete a segment").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).segments.delete(t);e.format==="json"?c(a,"json"):d("Segment deleted successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to delete segment"),process.exit(1);}}),n.command("count <id>").description("Get the number of contacts in a segment").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).segments.getContactsCount(t);e.format==="json"?c(a,"json"):console.log(`Segment contains ${a.count} contacts.`);}catch(o){l(o instanceof Error?o.message:"Failed to get segment count"),process.exit(1);}}),n}function it(n){try{let t=n.startsWith("http")?n:`http://localhost${n}`,e=new URL(t);return {key:e.searchParams.get("key")??void 0,code:e.searchParams.get("code")??void 0,state:e.searchParams.get("state")??void 0,error:e.searchParams.get("error")??void 0,message:e.searchParams.get("message")??void 0}}catch{return {}}}function st(){return `<!DOCTYPE html>
351
- <html lang="en">
352
- <head>
353
- <meta charset="UTF-8">
354
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
355
- <title>Login Successful - Emailr CLI</title>
356
- <style>
357
- body {
358
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
359
- display: flex;
360
- justify-content: center;
361
- align-items: center;
362
- min-height: 100vh;
363
- margin: 0;
364
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
365
- color: #fff;
366
- }
367
- .container {
368
- text-align: center;
369
- padding: 2rem;
370
- background: rgba(255, 255, 255, 0.1);
371
- border-radius: 16px;
372
- backdrop-filter: blur(10px);
373
- max-width: 400px;
374
- }
375
- .icon {
376
- font-size: 4rem;
377
- margin-bottom: 1rem;
378
- }
379
- h1 {
380
- margin: 0 0 1rem 0;
381
- font-size: 1.5rem;
382
- }
383
- p {
384
- margin: 0;
385
- opacity: 0.9;
386
- }
387
- </style>
388
- </head>
389
- <body>
390
- <div class="container">
391
- <div class="icon">\u2713</div>
392
- <h1>Login Successful!</h1>
393
- <p>You can close this window and return to your terminal.</p>
394
- </div>
395
- </body>
396
- </html>`}function ee(n){return `<!DOCTYPE html>
397
- <html lang="en">
398
- <head>
399
- <meta charset="UTF-8">
400
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
401
- <title>Login Failed - Emailr CLI</title>
1168
+ When done:`),console.log(` emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${t.file}`),console.log(" emailr templates stop-preview"),t.open!==!1)try{await(await import('open')).default(T);}catch{}process.exit(0);}catch(e){l(e instanceof Error?e.message:"Failed to start draft session"),process.exit(1);}}),i.command("stop-preview").description(`Stop any running background preview server
1169
+
1170
+ USAGE
1171
+ emailr templates stop-preview
1172
+
1173
+ DESCRIPTION
1174
+ Stops the background preview server started by the edit, draft, or
1175
+ preview commands. Use this when you're done editing and want to
1176
+ clean up the background process.
1177
+
1178
+ EXAMPLES
1179
+ # Stop the preview server
1180
+ emailr templates stop-preview
1181
+
1182
+ SEE ALSO
1183
+ emailr templates edit Start live editing session
1184
+ emailr templates draft Start live drafting session
1185
+ emailr templates preview Preview a template in browser`).action(async()=>{let t=await import('os'),e=N.join(t.tmpdir(),"emailr-preview.pid");try{let a=v.readFileSync(e,"utf-8").trim();process.kill(parseInt(a,10),"SIGTERM"),v.unlinkSync(e),d("Preview server stopped");}catch{d("No preview server running");}}),i}function Ee(){let i=new Command("domains").description(`Manage sending domains
1186
+
1187
+ USAGE
1188
+ emailr domains <subcommand> [options]
1189
+
1190
+ DESCRIPTION
1191
+ Add, verify, and manage sending domains for email delivery. Domains must
1192
+ be verified before you can send emails from addresses on that domain.
1193
+ Verification requires adding DNS records to prove domain ownership.
1194
+
1195
+ SUBCOMMANDS
1196
+ list List all domains with verification status
1197
+ get <id> Get domain details including DNS records
1198
+ add <domain> Add a new domain and get required DNS records
1199
+ verify <id> Trigger verification check for a domain
1200
+ check-dns <id> Check current DNS record status
1201
+ delete <id> Delete a domain permanently
1202
+
1203
+ DOMAIN VERIFICATION WORKFLOW
1204
+ 1. Add domain: emailr domains add example.com
1205
+ 2. Configure DNS: Add the DKIM, SPF, and DMARC records shown
1206
+ 3. Wait: DNS propagation can take up to 48 hours
1207
+ 4. Verify: emailr domains verify <domain_id>
1208
+ 5. Check status: emailr domains check-dns <domain_id>
1209
+
1210
+ DNS RECORD TYPES
1211
+ DKIM (DomainKeys Identified Mail):
1212
+ - Cryptographic signature for email authentication
1213
+ - Proves emails are authorized by domain owner
1214
+ - TXT record with public key
1215
+
1216
+ SPF (Sender Policy Framework):
1217
+ - Specifies which servers can send email for your domain
1218
+ - Prevents email spoofing
1219
+ - TXT record listing authorized senders
1220
+
1221
+ DMARC (Domain-based Message Authentication):
1222
+ - Policy for handling authentication failures
1223
+ - Specifies how receivers should treat failed emails
1224
+ - TXT record with policy settings
1225
+
1226
+ OPTIONS
1227
+ --format <format> Output format: json | table (default: table)
1228
+
1229
+ OUTPUT FORMATS
1230
+ --format json Machine-readable JSON output
1231
+ --format table Human-readable table output (default)
1232
+
1233
+ EXAMPLES
1234
+ # List all domains with verification status
1235
+ emailr domains list
1236
+
1237
+ # Add a new domain
1238
+ emailr domains add example.com
1239
+
1240
+ # Add domain with receiving subdomain
1241
+ emailr domains add example.com --receiving-subdomain inbound
1242
+
1243
+ # Check DNS record status
1244
+ emailr domains check-dns dom_abc123
1245
+
1246
+ # Verify domain after DNS configuration
1247
+ emailr domains verify dom_abc123
1248
+
1249
+ # Get domain details
1250
+ emailr domains get dom_abc123
1251
+
1252
+ # Delete a domain
1253
+ emailr domains delete dom_abc123
1254
+
1255
+ # Get JSON output for scripting
1256
+ emailr domains list --format json
1257
+
1258
+ VERIFICATION STATUS
1259
+ pending Domain added, awaiting DNS configuration
1260
+ verifying Verification in progress
1261
+ verified All DNS records verified, ready to send
1262
+ failed Verification failed, check DNS records
1263
+
1264
+ SEE ALSO
1265
+ emailr send Send emails using verified domains
1266
+ emailr webhooks Receive domain verification events`);return i.command("list").description(`List all domains
1267
+
1268
+ USAGE
1269
+ emailr domains list [options]
1270
+
1271
+ DESCRIPTION
1272
+ Retrieves a list of all domains configured for your account.
1273
+ Shows domain name, verification status, and individual DNS record status.
1274
+
1275
+ OPTIONS
1276
+ --format <format> Output format: json | table (default: table)
1277
+
1278
+ OUTPUT FORMATS
1279
+ --format json Machine-readable JSON array of domain objects
1280
+ --format table Human-readable table with columns: ID, Domain, Status, DKIM, SPF, DMARC, Created
1281
+
1282
+ EXAMPLES
1283
+ # List all domains
1284
+ emailr domains list
1285
+
1286
+ # Get JSON output for scripting
1287
+ emailr domains list --format json
1288
+
1289
+ # Filter verified domains with jq
1290
+ emailr domains list --format json | jq '.[] | select(.status == "verified")'
1291
+
1292
+ # Count domains
1293
+ emailr domains list --format json | jq 'length'`).option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),r=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).domains.list();if(t.format==="json")c(r,"json");else {let o=r.map(n=>({ID:n.id,Domain:n.domain,Status:n.status,DKIM:n.dkim_verified,SPF:n.spf_verified,DMARC:n.dmarc_verified,Created:n.created_at}));c(o,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list domains"),process.exit(1);}}),i.command("get <domain_id>").description(`Get domain details
1294
+
1295
+ USAGE
1296
+ emailr domains get <domain_id> [options]
1297
+
1298
+ DESCRIPTION
1299
+ Retrieves detailed information about a specific domain including
1300
+ verification status and DNS record configuration.
1301
+
1302
+ ARGUMENTS
1303
+ <domain_id> The unique domain identifier (e.g., dom_abc123)
1304
+
1305
+ OPTIONS
1306
+ --format <format> Output format: json | table (default: table)
1307
+
1308
+ OUTPUT FORMATS
1309
+ --format json Full domain object with all fields and DNS records
1310
+ --format table Summary table with domain details
1311
+
1312
+ EXAMPLES
1313
+ # Get domain details
1314
+ emailr domains get dom_abc123
1315
+
1316
+ # Get full domain data as JSON
1317
+ emailr domains get dom_abc123 --format json
1318
+
1319
+ # Extract DNS records
1320
+ emailr domains get dom_abc123 --format json | jq '.dns_records'`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).domains.get(t);c(o,e.format);}catch(a){l(a instanceof Error?a.message:"Failed to get domain"),process.exit(1);}}),i.command("add <domain_name>").description(`Add a new domain
1321
+
1322
+ USAGE
1323
+ emailr domains add <domain_name> [options]
1324
+
1325
+ DESCRIPTION
1326
+ Adds a new sending domain and returns the DNS records required for
1327
+ verification. After adding, configure these records with your DNS
1328
+ provider, then run 'verify' to complete setup.
1329
+
1330
+ ARGUMENTS
1331
+ <domain_name> The domain to add (e.g., example.com)
1332
+
1333
+ OPTIONS
1334
+ --receiving-subdomain <subdomain> Subdomain for receiving emails (optional)
1335
+ --format <format> Output format: json | table (default: table)
1336
+
1337
+ DNS RECORDS RETURNED
1338
+ After adding a domain, you'll receive three DNS records to configure:
1339
+
1340
+ DKIM Record:
1341
+ Type: TXT
1342
+ Name: emailr._domainkey.example.com
1343
+ Value: v=DKIM1; k=rsa; p=<public_key>
1344
+
1345
+ SPF Record:
1346
+ Type: TXT
1347
+ Name: example.com
1348
+ Value: v=spf1 include:_spf.emailr.com ~all
1349
+
1350
+ DMARC Record:
1351
+ Type: TXT
1352
+ Name: _dmarc.example.com
1353
+ Value: v=DMARC1; p=none; rua=mailto:dmarc@example.com
1354
+
1355
+ OUTPUT FORMATS
1356
+ --format json Full domain object with DNS records
1357
+ --format table Summary with DNS records in table format
1358
+
1359
+ EXAMPLES
1360
+ # Add a domain
1361
+ emailr domains add example.com
1362
+
1363
+ # Add domain with receiving subdomain
1364
+ emailr domains add example.com --receiving-subdomain inbound
1365
+
1366
+ # Get JSON output with DNS records
1367
+ emailr domains add example.com --format json
1368
+
1369
+ NEXT STEPS
1370
+ 1. Copy the DNS records shown in the output
1371
+ 2. Add them to your DNS provider (Cloudflare, Route53, etc.)
1372
+ 3. Wait for DNS propagation (up to 48 hours)
1373
+ 4. Run: emailr domains verify <domain_id>`).option("--receiving-subdomain <subdomain>","Subdomain for receiving emails").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),r=new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}),o={domain:t};e.receivingSubdomain&&(o.receivingSubdomain=e.receivingSubdomain);let n=await r.domains.add(o);if(e.format==="json")c(n,"json");else {if(d(`Domain added: ${n.domain}`),u("Add the following DNS records to verify your domain:"),console.log(""),n.dns_records){let s=[{Type:"DKIM",...Q(n.dns_records.dkim)},{Type:"SPF",...Q(n.dns_records.spf)},{Type:"DMARC",...Q(n.dns_records.dmarc)}];c(s,"table");}console.log(""),u(`Run 'emailr domains verify ${n.id}' after adding DNS records`);}}catch(a){l(a instanceof Error?a.message:"Failed to add domain"),process.exit(1);}}),i.command("verify <domain_id>").description(`Verify a domain
1374
+
1375
+ USAGE
1376
+ emailr domains verify <domain_id> [options]
1377
+
1378
+ DESCRIPTION
1379
+ Triggers a verification check for a domain. This queries DNS to confirm
1380
+ that the required DKIM, SPF, and DMARC records are properly configured.
1381
+ Run this after adding DNS records to your domain.
1382
+
1383
+ ARGUMENTS
1384
+ <domain_id> The unique domain identifier (e.g., dom_abc123)
1385
+
1386
+ OPTIONS
1387
+ --format <format> Output format: json | table (default: table)
1388
+
1389
+ VERIFICATION PROCESS
1390
+ 1. Emailr queries DNS for your domain's records
1391
+ 2. Compares found records against expected values
1392
+ 3. Updates verification status for each record type
1393
+ 4. Domain is verified when all records pass
1394
+
1395
+ OUTPUT FORMATS
1396
+ --format json Verification result with status details
1397
+ --format table Human-readable verification status
1398
+
1399
+ EXAMPLES
1400
+ # Verify a domain
1401
+ emailr domains verify dom_abc123
1402
+
1403
+ # Get JSON verification result
1404
+ emailr domains verify dom_abc123 --format json
1405
+
1406
+ TROUBLESHOOTING
1407
+ If verification fails:
1408
+ 1. Run 'check-dns' to see which records are missing
1409
+ 2. Verify records are correctly configured with your DNS provider
1410
+ 3. Wait for DNS propagation (can take up to 48 hours)
1411
+ 4. Try verification again`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).domains.verify(t);e.format==="json"?c(o,"json"):o.verified?d("Domain verified successfully!"):(u(`Domain status: ${o.status}`),o.dkim_status&&u(`DKIM status: ${o.dkim_status}`));}catch(a){l(a instanceof Error?a.message:"Failed to verify domain"),process.exit(1);}}),i.command("check-dns <domain_id>").description(`Check DNS records for a domain
1412
+
1413
+ USAGE
1414
+ emailr domains check-dns <domain_id> [options]
1415
+
1416
+ DESCRIPTION
1417
+ Queries DNS to check the current status of DKIM, SPF, and DMARC records
1418
+ for a domain. Shows expected vs. found values to help troubleshoot
1419
+ verification issues.
1420
+
1421
+ ARGUMENTS
1422
+ <domain_id> The unique domain identifier (e.g., dom_abc123)
1423
+
1424
+ OPTIONS
1425
+ --format <format> Output format: json | table (default: table)
1426
+
1427
+ DNS RECORD STATUS
1428
+ For each record type (DKIM, SPF, DMARC), shows:
1429
+ - Verified: Whether the record matches expected value
1430
+ - Expected: The value Emailr expects to find
1431
+ - Found: The actual value(s) found in DNS
1432
+
1433
+ OUTPUT FORMATS
1434
+ --format json Detailed DNS check results as JSON
1435
+ --format table Table showing Record, Verified, Expected, Found
1436
+
1437
+ EXAMPLES
1438
+ # Check DNS records
1439
+ emailr domains check-dns dom_abc123
1440
+
1441
+ # Get JSON output for debugging
1442
+ emailr domains check-dns dom_abc123 --format json
1443
+
1444
+ # Check specific record with jq
1445
+ emailr domains check-dns dom_abc123 --format json | jq '.dkim'
1446
+
1447
+ COMMON ISSUES
1448
+ Record not found:
1449
+ - DNS propagation may still be in progress
1450
+ - Record may be misconfigured at DNS provider
1451
+
1452
+ Value mismatch:
1453
+ - Copy the exact value from 'add' command output
1454
+ - Ensure no extra spaces or characters
1455
+
1456
+ Multiple records found:
1457
+ - Remove duplicate TXT records
1458
+ - Keep only the Emailr-specific record`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).domains.checkDns(t);if(e.format==="json")c(o,"json");else {let n=Object.entries(o).map(([s,p])=>({Record:s,Verified:p.verified,Expected:p.expected||"-",Found:p.found?.join(", ")||"-"}));c(n,"table");}}catch(a){l(a instanceof Error?a.message:"Failed to check DNS"),process.exit(1);}}),i.command("delete <domain_id>").description(`Delete a domain
1459
+
1460
+ USAGE
1461
+ emailr domains delete <domain_id> [options]
1462
+
1463
+ DESCRIPTION
1464
+ Permanently deletes a domain from your account. This action cannot be
1465
+ undone. You will no longer be able to send emails from this domain.
1466
+
1467
+ ARGUMENTS
1468
+ <domain_id> The unique domain identifier (e.g., dom_abc123)
1469
+
1470
+ OPTIONS
1471
+ --format <format> Output format: json | table (default: table)
1472
+
1473
+ OUTPUT FORMATS
1474
+ --format json Returns: { success: boolean }
1475
+ --format table Human-readable success message
1476
+
1477
+ EXAMPLES
1478
+ # Delete a domain
1479
+ emailr domains delete dom_abc123
1480
+
1481
+ # Get JSON output
1482
+ emailr domains delete dom_abc123 --format json
1483
+
1484
+ WARNING
1485
+ This action is permanent and cannot be undone.
1486
+ - Emails from this domain will fail to send
1487
+ - DNS records can be removed from your DNS provider
1488
+ - Re-add the domain if you need to restore functionality`).option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).domains.delete(t),d(`Domain deleted: ${t}`);}catch(e){l(e instanceof Error?e.message:"Failed to delete domain"),process.exit(1);}}),i}function Q(i){return {"Record Type":i.type,Name:i.name,Value:i.value.length>50?i.value.substring(0,47)+"...":i.value,...i.priority!==void 0&&{Priority:i.priority}}}function Ne(){let i=new Command("config").description(`Manage CLI configuration
1489
+
1490
+ USAGE
1491
+ emailr config <subcommand> [options]
1492
+
1493
+ DESCRIPTION
1494
+ Configure the Emailr CLI with your API credentials and preferences.
1495
+ Settings can be stored in a config file or provided via environment
1496
+ variables. Environment variables take precedence over config file values.
1497
+
1498
+ SUBCOMMANDS
1499
+ set <key> <value> Set a configuration value
1500
+ get <key> Get a configuration value
1501
+ list List all configuration values
1502
+ path Show the configuration file path
1503
+ init Initialize configuration interactively
1504
+
1505
+ CONFIGURABLE SETTINGS
1506
+ api-key Your Emailr API key (required for API access)
1507
+ base-url Custom API base URL (default: https://api.emailr.dev)
1508
+ format Default output format: json | table (default: table)
1509
+
1510
+ ENVIRONMENT VARIABLES
1511
+ EMAILR_API_KEY API key (overrides config file api-key)
1512
+ EMAILR_BASE_URL Base URL (overrides config file base-url)
1513
+
1514
+ Environment variables take precedence over config file values, allowing
1515
+ you to override settings without modifying the config file.
1516
+
1517
+ CONFIG FILE LOCATION
1518
+ ~/.emailr/config.json
1519
+
1520
+ The config file is created automatically when you run 'emailr config set'
1521
+ or 'emailr config init'. The file stores settings in JSON format.
1522
+
1523
+ OUTPUT FORMATS
1524
+ --format json Machine-readable JSON output (for list command)
1525
+ --format table Human-readable table output (default)
1526
+
1527
+ EXAMPLES
1528
+ # Set your API key
1529
+ emailr config set api-key re_abc123xyz
1530
+
1531
+ # Set a custom API base URL
1532
+ emailr config set base-url https://custom.api.example.com
1533
+
1534
+ # View current configuration
1535
+ emailr config list
1536
+
1537
+ # Get a specific setting
1538
+ emailr config get api-key
1539
+
1540
+ # Show config file location
1541
+ emailr config path
1542
+
1543
+ # Initialize with API key
1544
+ emailr config init --api-key re_abc123xyz
1545
+
1546
+ # Use environment variable instead of config file
1547
+ export EMAILR_API_KEY=re_abc123xyz
1548
+ emailr templates list
1549
+
1550
+ SEE ALSO
1551
+ emailr --help Show all available commands`);return i.command("set <key> <value>").description(`Set a configuration value
1552
+
1553
+ USAGE
1554
+ emailr config set <key> <value>
1555
+
1556
+ DESCRIPTION
1557
+ Stores a configuration value in the config file (~/.emailr/config.json).
1558
+ The config file is created automatically if it doesn't exist.
1559
+
1560
+ ARGUMENTS
1561
+ <key> Configuration key to set
1562
+ <value> Value to assign to the key
1563
+
1564
+ VALID KEYS
1565
+ api-key Your Emailr API key (masked in output for security)
1566
+ base-url Custom API base URL
1567
+ format Default output format: json | table
1568
+
1569
+ EXAMPLES
1570
+ # Set your API key
1571
+ emailr config set api-key re_abc123xyz
1572
+
1573
+ # Set a custom API base URL
1574
+ emailr config set base-url https://custom.api.example.com
1575
+
1576
+ # Set default output format to JSON
1577
+ emailr config set format json
1578
+
1579
+ NOTE
1580
+ Environment variables (EMAILR_API_KEY, EMAILR_BASE_URL) take precedence
1581
+ over config file values at runtime.`).action(async(t,e)=>{try{let a=["api-key","base-url","format"],r=t.toLowerCase();a.includes(r)||(l(`Invalid config key: ${t}`),u(`Valid keys: ${a.join(", ")}`),process.exit(1));let n={"api-key":"apiKey","base-url":"baseUrl",format:"format"}[r];r==="format"&&!["json","table"].includes(e)&&(l(`Invalid format value: ${e}`),u("Valid formats: json, table"),process.exit(1)),G({[n]:e}),d(`Configuration saved: ${t} = ${r==="api-key"?"***":e}`),u(`Config file: ${I()}`);}catch(a){l(a instanceof Error?a.message:"Failed to save configuration"),process.exit(1);}}),i.command("get <key>").description(`Get a configuration value
1582
+
1583
+ USAGE
1584
+ emailr config get <key>
1585
+
1586
+ DESCRIPTION
1587
+ Retrieves and displays a single configuration value from the config file.
1588
+ API keys are partially masked for security (shows first 8 and last 4 chars).
1589
+
1590
+ ARGUMENTS
1591
+ <key> Configuration key to retrieve
1592
+
1593
+ VALID KEYS
1594
+ api-key Your Emailr API key (partially masked in output)
1595
+ base-url Custom API base URL
1596
+ format Default output format
1597
+
1598
+ EXAMPLES
1599
+ # Get your API key (masked)
1600
+ emailr config get api-key
1601
+
1602
+ # Get the base URL
1603
+ emailr config get base-url
1604
+
1605
+ # Get default output format
1606
+ emailr config get format
1607
+
1608
+ SEE ALSO
1609
+ emailr config list Show all configuration values`).action(async t=>{try{let e=["api-key","base-url","format"],a=t.toLowerCase();e.includes(a)||(l(`Invalid config key: ${t}`),u(`Valid keys: ${e.join(", ")}`),process.exit(1));let o={"api-key":"apiKey","base-url":"baseUrl",format:"format"}[a],n=ne(o);n?console.log(a==="api-key"?n.substring(0,8)+"..."+n.substring(n.length-4):n):u(`${t} is not set`);}catch(e){l(e instanceof Error?e.message:"Failed to get configuration"),process.exit(1);}}),i.command("list").description(`List all configuration values
1610
+
1611
+ USAGE
1612
+ emailr config list [options]
1613
+
1614
+ DESCRIPTION
1615
+ Displays all configuration values from the config file. API keys are
1616
+ partially masked for security. Shows the config file location at the end.
1617
+
1618
+ OPTIONS
1619
+ --format <format> Output format: json | table (default: table)
1620
+
1621
+ OUTPUT FORMATS
1622
+ --format json Machine-readable JSON object with all settings
1623
+ --format table Human-readable table with Key and Value columns
1624
+
1625
+ EXAMPLES
1626
+ # List all settings in table format
1627
+ emailr config list
1628
+
1629
+ # List all settings as JSON
1630
+ emailr config list --format json
1631
+
1632
+ # Pipe JSON to jq for processing
1633
+ emailr config list --format json | jq '.["api-key"]'
1634
+
1635
+ SEE ALSO
1636
+ emailr config get Get a single configuration value
1637
+ emailr config path Show config file location`).option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),a={"api-key":e.apiKey?e.apiKey.substring(0,8)+"..."+e.apiKey.substring(e.apiKey.length-4):"(not set)","base-url":e.baseUrl||"(default)",format:e.format||"table"};if(t.format==="json")c(a,"json");else {let r=Object.entries(a).map(([o,n])=>({Key:o,Value:n}));c(r,"table");}console.log(""),u(`Config file: ${I()}`);}catch(e){e instanceof Error&&e.message.includes("No API key configured")?(u("No configuration found."),u("Run 'emailr config set api-key <your-api-key>' to get started.")):(l(e instanceof Error?e.message:"Failed to list configuration"),process.exit(1));}}),i.command("path").description(`Show the configuration file path
1638
+
1639
+ USAGE
1640
+ emailr config path
1641
+
1642
+ DESCRIPTION
1643
+ Displays the full path to the configuration file. Useful for debugging
1644
+ or when you need to manually edit or backup the config file.
1645
+
1646
+ CONFIG FILE LOCATION
1647
+ ~/.emailr/config.json
1648
+
1649
+ The config file stores settings in JSON format:
1650
+ {
1651
+ "apiKey": "re_abc123...",
1652
+ "baseUrl": "https://api.emailr.dev",
1653
+ "format": "table"
1654
+ }
1655
+
1656
+ EXAMPLES
1657
+ # Show config file path
1658
+ emailr config path
1659
+
1660
+ # Open config file in editor (macOS)
1661
+ open $(emailr config path)
1662
+
1663
+ # View config file contents
1664
+ cat $(emailr config path)`).action(()=>{console.log(I());}),i.command("init").description(`Initialize configuration interactively
1665
+
1666
+ USAGE
1667
+ emailr config init [options]
1668
+
1669
+ DESCRIPTION
1670
+ Sets up the CLI configuration with your API credentials. Creates the
1671
+ config file (~/.emailr/config.json) if it doesn't exist.
1672
+
1673
+ OPTIONS
1674
+ --api-key <api_key> API key to use
1675
+ --base-url <url> Base URL for API (optional)
1676
+
1677
+ EXAMPLES
1678
+ # Initialize with API key
1679
+ emailr config init --api-key re_abc123xyz
1680
+
1681
+ # Initialize with custom base URL
1682
+ emailr config init --api-key re_abc123xyz --base-url https://custom.api.example.com
1683
+
1684
+ # Show initialization instructions
1685
+ emailr config init
1686
+
1687
+ ALTERNATIVE: ENVIRONMENT VARIABLES
1688
+ Instead of using config init, you can set environment variables:
1689
+
1690
+ export EMAILR_API_KEY=re_abc123xyz
1691
+ export EMAILR_BASE_URL=https://api.emailr.dev # optional
1692
+
1693
+ Environment variables take precedence over config file values.
1694
+
1695
+ SEE ALSO
1696
+ emailr config set Set individual configuration values
1697
+ emailr config list View current configuration`).option("--api-key <key>","API key to use").option("--base-url <url>","Base URL for API").action(async t=>{try{t.apiKey?(G({apiKey:t.apiKey,baseUrl:t.baseUrl}),d("Configuration initialized!"),u(`Config file: ${I()}`)):(u("Initialize your Emailr CLI configuration:"),console.log(""),u("Run with --api-key flag:"),console.log(" emailr config init --api-key <your-api-key>"),console.log(""),u("Or set environment variable:"),console.log(" export EMAILR_API_KEY=<your-api-key>"));}catch(e){l(e instanceof Error?e.message:"Failed to initialize configuration"),process.exit(1);}}),i}function _e(){let i=new Command("broadcasts").description(`Manage broadcast campaigns
1698
+
1699
+ USAGE
1700
+ emailr broadcasts <subcommand> [options]
1701
+
1702
+ DESCRIPTION
1703
+ Create, schedule, send, and manage email broadcast campaigns. Broadcasts
1704
+ are bulk email sends to a group of contacts, typically defined by a segment.
1705
+
1706
+ BROADCASTS, TEMPLATES, AND SEGMENTS
1707
+ Broadcasts bring together templates and segments to send targeted emails:
1708
+ - Templates: Define the email content (HTML, subject, variables)
1709
+ - Segments: Define the audience (which contacts receive the email)
1710
+ - Broadcasts: Combine a template with a segment and handle delivery
1711
+
1712
+ A typical workflow:
1713
+ 1. Create a template with your email content
1714
+ 2. Create a segment to target specific contacts
1715
+ 3. Create a broadcast linking the template and segment
1716
+ 4. Send immediately or schedule for later
1717
+
1718
+ SUBCOMMANDS
1719
+ list List all broadcasts with optional status filter
1720
+ get <id> Get broadcast details and delivery statistics
1721
+ create Create a new broadcast campaign
1722
+ send <id> Send a broadcast immediately
1723
+ schedule <id> Schedule a broadcast for future delivery
1724
+ cancel <id> Cancel a scheduled broadcast
1725
+ delete <id> Delete a broadcast
1726
+
1727
+ BROADCAST STATUS VALUES
1728
+ draft Broadcast created but not yet sent or scheduled
1729
+ scheduled Broadcast scheduled for future delivery
1730
+ sending Broadcast is currently being sent
1731
+ sent Broadcast has been fully delivered
1732
+
1733
+ OPTIONS
1734
+ --format <format> Output format: json | table (default: table)
1735
+
1736
+ OUTPUT FORMATS
1737
+ --format json Machine-readable JSON output
1738
+ --format table Human-readable table output (default)
1739
+
1740
+ SCHEDULING OPTION
1741
+ --schedule <datetime> Schedule time in ISO 8601 format
1742
+
1743
+ ISO 8601 Datetime Format Examples:
1744
+ 2024-12-25T10:00:00Z UTC time
1745
+ 2024-12-25T10:00:00-05:00 Eastern Time (UTC-5)
1746
+ 2024-12-25T10:00:00+01:00 Central European Time (UTC+1)
1747
+ 2024-12-25T15:30:00Z Specific time with minutes
1748
+
1749
+ Note: Always include timezone offset or 'Z' for UTC to avoid ambiguity.
1750
+
1751
+ EXAMPLES
1752
+ # List all broadcasts
1753
+ emailr broadcasts list
1754
+
1755
+ # List only scheduled broadcasts
1756
+ emailr broadcasts list --status scheduled
1757
+
1758
+ # Get broadcast details with delivery stats
1759
+ emailr broadcasts get brd_abc123
1760
+
1761
+ # Create a broadcast using a template and segment
1762
+ emailr broadcasts create --name "Weekly Newsletter" \\
1763
+ --subject "This Week's Updates" \\
1764
+ --from "newsletter@example.com" \\
1765
+ --template tpl_abc123 \\
1766
+ --segment seg_xyz789
1767
+
1768
+ # Create a broadcast with inline HTML
1769
+ emailr broadcasts create --name "Flash Sale" \\
1770
+ --subject "24-Hour Flash Sale!" \\
1771
+ --from "sales@example.com" \\
1772
+ --segment seg_xyz789 \\
1773
+ --html "<h1>Flash Sale</h1><p>Don't miss out!</p>"
1774
+
1775
+ # Create and schedule a broadcast
1776
+ emailr broadcasts create --name "Holiday Promo" \\
1777
+ --subject "Holiday Special" \\
1778
+ --from "promo@example.com" \\
1779
+ --template tpl_abc123 \\
1780
+ --segment seg_xyz789 \\
1781
+ --schedule "2024-12-25T10:00:00Z"
1782
+
1783
+ # Send a draft broadcast immediately
1784
+ emailr broadcasts send brd_abc123
1785
+
1786
+ # Schedule an existing broadcast
1787
+ emailr broadcasts schedule brd_abc123 --at "2024-12-25T10:00:00Z"
1788
+
1789
+ # Cancel a scheduled broadcast
1790
+ emailr broadcasts cancel brd_abc123
1791
+
1792
+ # Delete a broadcast
1793
+ emailr broadcasts delete brd_abc123
1794
+
1795
+ # Get JSON output for scripting
1796
+ emailr broadcasts list --format json
1797
+
1798
+ SEE ALSO
1799
+ emailr templates Manage email templates
1800
+ emailr segments Manage contact segments
1801
+ emailr contacts Manage individual contacts`);return i.command("list").description(`List all broadcasts
1802
+
1803
+ USAGE
1804
+ emailr broadcasts list [options]
1805
+
1806
+ DESCRIPTION
1807
+ Retrieves a list of all broadcast campaigns. Supports filtering by status
1808
+ and limiting the number of results returned.
1809
+
1810
+ OPTIONS
1811
+ --status <status> Filter by status: draft | scheduled | sending | sent
1812
+ --limit <count> Number of broadcasts to return (default: 20)
1813
+ --format <format> Output format: json | table (default: table)
1814
+
1815
+ STATUS VALUES
1816
+ draft Broadcast created but not yet sent or scheduled
1817
+ scheduled Broadcast scheduled for future delivery
1818
+ sending Broadcast is currently being sent
1819
+ sent Broadcast has been fully delivered
1820
+
1821
+ OUTPUT FORMATS
1822
+ --format json Machine-readable JSON array of broadcast objects
1823
+ --format table Human-readable table with columns: ID, Name, Subject, Status, Recipients, Sent, Created
1824
+
1825
+ EXAMPLES
1826
+ # List all broadcasts
1827
+ emailr broadcasts list
1828
+
1829
+ # List only draft broadcasts
1830
+ emailr broadcasts list --status draft
1831
+
1832
+ # List only scheduled broadcasts
1833
+ emailr broadcasts list --status scheduled
1834
+
1835
+ # List sent broadcasts
1836
+ emailr broadcasts list --status sent
1837
+
1838
+ # List more broadcasts
1839
+ emailr broadcasts list --limit 50
1840
+
1841
+ # Get JSON output for scripting
1842
+ emailr broadcasts list --format json
1843
+
1844
+ # Pipe JSON to jq for processing
1845
+ emailr broadcasts list --format json | jq '.[] | select(.status == "scheduled")'`).option("--status <status>","Filter by status (draft, scheduled, sending, sent)").option("--limit <number>","Number of broadcasts to return","20").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),r=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).broadcasts.list({status:t.status,limit:parseInt(t.limit)});if(t.format==="json")c(r,"json");else {if(r.length===0){console.log("No broadcasts found.");return}let o=r.map(n=>({ID:n.id,Name:n.name,Subject:n.subject.substring(0,30)+(n.subject.length>30?"...":""),Status:n.status,Recipients:n.total_recipients||0,Sent:n.sent_count||0,Created:new Date(n.created_at).toLocaleDateString()}));c(o,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list broadcasts"),process.exit(1);}}),i.command("get <broadcast_id>").description(`Get broadcast details
1846
+
1847
+ USAGE
1848
+ emailr broadcasts get <broadcast_id> [options]
1849
+
1850
+ DESCRIPTION
1851
+ Retrieves detailed information about a specific broadcast including
1852
+ delivery statistics (sent, delivered, opened, clicked, bounced).
1853
+
1854
+ ARGUMENTS
1855
+ <broadcast_id> The unique broadcast identifier (e.g., brd_abc123)
1856
+
1857
+ OPTIONS
1858
+ --format <format> Output format: json | table (default: table)
1859
+
1860
+ OUTPUT FORMATS
1861
+ --format json Full broadcast object with all fields and statistics
1862
+ --format table Summary table with broadcast details and delivery stats
1863
+
1864
+ DELIVERY STATISTICS
1865
+ Total Recipients Number of contacts targeted by the broadcast
1866
+ Sent Count Number of emails sent
1867
+ Delivered Number of emails successfully delivered
1868
+ Opened Number of unique opens
1869
+ Clicked Number of unique clicks
1870
+ Bounced Number of bounced emails
1871
+
1872
+ EXAMPLES
1873
+ # Get broadcast details
1874
+ emailr broadcasts get brd_abc123
1875
+
1876
+ # Get full broadcast data as JSON
1877
+ emailr broadcasts get brd_abc123 --format json
1878
+
1879
+ # Pipe JSON to jq for processing
1880
+ emailr broadcasts get brd_abc123 --format json | jq '{sent: .sent_count, opened: .opened_count}'`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).broadcasts.get(t);e.format==="json"?c(o,"json"):c({ID:o.id,Name:o.name,Subject:o.subject,"From Email":o.from_email,Status:o.status,"Total Recipients":o.total_recipients||0,"Sent Count":o.sent_count||0,Delivered:o.delivered_count||0,Opened:o.opened_count||0,Clicked:o.clicked_count||0,Bounced:o.bounced_count||0,"Scheduled At":o.scheduled_at||"N/A","Started At":o.started_at||"N/A","Completed At":o.completed_at||"N/A","Created At":o.created_at},"table");}catch(a){l(a instanceof Error?a.message:"Failed to get broadcast"),process.exit(1);}}),i.command("create").description(`Create a new broadcast
1881
+
1882
+ USAGE
1883
+ emailr broadcasts create --name <name> --subject <subject> --from <email> [options]
1884
+
1885
+ DESCRIPTION
1886
+ Creates a new broadcast campaign. You can use a template for content or
1887
+ provide inline HTML/text. Target recipients using a segment ID.
1888
+
1889
+ OPTIONS
1890
+ --name <broadcast_name> Broadcast name for internal reference (required)
1891
+ --subject <subject_line> Email subject line (required)
1892
+ --from <email_address> Sender email address (required)
1893
+ --template <template_id> Template ID to use for content
1894
+ --segment <segment_id> Segment ID to target recipients
1895
+ --html <html_content> Inline HTML content (alternative to --template)
1896
+ --text <text_content> Plain text content
1897
+ --schedule <datetime> Schedule time in ISO 8601 format
1898
+ --format <format> Output format: json | table (default: table)
1899
+
1900
+ CONTENT OPTIONS
1901
+ Use either --template OR --html/--text for content:
1902
+ - --template: Use a pre-built template (recommended for reusable content)
1903
+ - --html/--text: Provide inline content (good for one-off emails)
1904
+
1905
+ SCHEDULING OPTION
1906
+ --schedule <datetime> Schedule time in ISO 8601 format
1907
+
1908
+ ISO 8601 Datetime Format Examples:
1909
+ 2024-12-25T10:00:00Z UTC time
1910
+ 2024-12-25T10:00:00-05:00 Eastern Time (UTC-5)
1911
+ 2024-12-25T10:00:00+01:00 Central European Time (UTC+1)
1912
+
1913
+ If not specified, broadcast is created as draft and can be sent later.
1914
+
1915
+ OUTPUT FORMATS
1916
+ --format json Full broadcast object with all fields
1917
+ --format table Summary table with ID, Name, Status, Scheduled At
1918
+
1919
+ EXAMPLES
1920
+ # Create broadcast with template and segment
1921
+ emailr broadcasts create --name "Weekly Newsletter" \\
1922
+ --subject "This Week's Updates" \\
1923
+ --from "newsletter@example.com" \\
1924
+ --template tpl_abc123 \\
1925
+ --segment seg_xyz789
1926
+
1927
+ # Create broadcast with inline HTML
1928
+ emailr broadcasts create --name "Flash Sale" \\
1929
+ --subject "24-Hour Flash Sale!" \\
1930
+ --from "sales@example.com" \\
1931
+ --segment seg_xyz789 \\
1932
+ --html "<h1>Flash Sale</h1><p>Don't miss out!</p>"
1933
+
1934
+ # Create and schedule broadcast for Christmas morning UTC
1935
+ emailr broadcasts create --name "Holiday Promo" \\
1936
+ --subject "Holiday Special" \\
1937
+ --from "promo@example.com" \\
1938
+ --template tpl_abc123 \\
1939
+ --segment seg_xyz789 \\
1940
+ --schedule "2024-12-25T10:00:00Z"
1941
+
1942
+ # Create broadcast scheduled for 9 AM Eastern
1943
+ emailr broadcasts create --name "Morning Update" \\
1944
+ --subject "Good Morning!" \\
1945
+ --from "updates@example.com" \\
1946
+ --template tpl_abc123 \\
1947
+ --segment seg_xyz789 \\
1948
+ --schedule "2024-06-15T09:00:00-04:00"
1949
+
1950
+ # Get JSON output for scripting
1951
+ emailr broadcasts create --name "Test" --subject "Test" \\
1952
+ --from "test@example.com" --html "<p>Test</p>" --format json
1953
+
1954
+ SEE ALSO
1955
+ emailr templates Create and manage email templates
1956
+ emailr segments Create and manage contact segments`).requiredOption("--name <name>","Broadcast name").requiredOption("--subject <subject>","Email subject").requiredOption("--from <email>","Sender email address").option("--template <id>","Template ID to use").option("--segment <id>","Segment ID to target").option("--html <html>","HTML content").option("--text <text>","Plain text content").option("--schedule <datetime>","Schedule time (ISO 8601)").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),r=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).broadcasts.create({name:t.name,subject:t.subject,from_email:t.from,template_id:t.template,segment_id:t.segment,html_content:t.html,text_content:t.text,scheduled_at:t.schedule});t.format==="json"?c(r,"json"):(d("Broadcast created successfully!"),c({ID:r.id,Name:r.name,Status:r.status,"Scheduled At":r.scheduled_at||"Not scheduled"},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create broadcast"),process.exit(1);}}),i.command("send <broadcast_id>").description(`Send a broadcast immediately
1957
+
1958
+ USAGE
1959
+ emailr broadcasts send <broadcast_id> [options]
1960
+
1961
+ DESCRIPTION
1962
+ Sends a broadcast immediately to all recipients in the target segment.
1963
+ The broadcast must be in 'draft' or 'scheduled' status. Once sent,
1964
+ the status changes to 'sending' and then 'sent' when complete.
1965
+
1966
+ ARGUMENTS
1967
+ <broadcast_id> The unique broadcast identifier (e.g., brd_abc123)
1968
+
1969
+ OPTIONS
1970
+ --format <format> Output format: json | table (default: table)
1971
+
1972
+ OUTPUT FORMATS
1973
+ --format json Returns: { success: boolean, sent: number, total: number }
1974
+ --format table Human-readable summary with success status and counts
1975
+
1976
+ EXAMPLES
1977
+ # Send a broadcast immediately
1978
+ emailr broadcasts send brd_abc123
1979
+
1980
+ # Get JSON output for scripting
1981
+ emailr broadcasts send brd_abc123 --format json
1982
+
1983
+ # Extract sent count from JSON
1984
+ emailr broadcasts send brd_abc123 --format json | jq '.sent'
1985
+
1986
+ NOTE
1987
+ Sending is asynchronous. The command returns when sending starts,
1988
+ not when all emails are delivered. Use 'emailr broadcasts get' to
1989
+ check delivery progress.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).broadcasts.send(t);e.format==="json"?c(o,"json"):(d("Broadcast sent successfully!"),c({Success:o.success,Sent:o.sent,Total:o.total},"table"));}catch(a){l(a instanceof Error?a.message:"Failed to send broadcast"),process.exit(1);}}),i.command("schedule <broadcast_id>").description(`Schedule a broadcast for future delivery
1990
+
1991
+ USAGE
1992
+ emailr broadcasts schedule <broadcast_id> --at <datetime> [options]
1993
+
1994
+ DESCRIPTION
1995
+ Schedules a broadcast to be sent at a specific time. The broadcast
1996
+ must be in 'draft' status. After scheduling, the status changes to
1997
+ 'scheduled'. Use 'cancel' to unschedule if needed.
1998
+
1999
+ ARGUMENTS
2000
+ <broadcast_id> The unique broadcast identifier (e.g., brd_abc123)
2001
+
2002
+ OPTIONS
2003
+ --at <datetime> Schedule time in ISO 8601 format (required)
2004
+ --format <format> Output format: json | table (default: table)
2005
+
2006
+ ISO 8601 DATETIME FORMAT
2007
+ The --at option requires a datetime in ISO 8601 format with timezone:
2008
+
2009
+ Format: YYYY-MM-DDTHH:MM:SS\xB1HH:MM or YYYY-MM-DDTHH:MM:SSZ
2010
+
2011
+ Examples:
2012
+ 2024-12-25T10:00:00Z UTC time (Z = Zulu/UTC)
2013
+ 2024-12-25T10:00:00-05:00 Eastern Standard Time (UTC-5)
2014
+ 2024-12-25T10:00:00-04:00 Eastern Daylight Time (UTC-4)
2015
+ 2024-12-25T10:00:00+01:00 Central European Time (UTC+1)
2016
+ 2024-12-25T10:00:00+09:00 Japan Standard Time (UTC+9)
2017
+ 2024-06-15T15:30:00Z Specific time with minutes
2018
+
2019
+ Always include timezone to avoid ambiguity.
2020
+
2021
+ OUTPUT FORMATS
2022
+ --format json Full broadcast object with scheduled_at field
2023
+ --format table Summary table with ID, Name, Status, Scheduled At
2024
+
2025
+ EXAMPLES
2026
+ # Schedule for Christmas morning UTC
2027
+ emailr broadcasts schedule brd_abc123 --at "2024-12-25T10:00:00Z"
2028
+
2029
+ # Schedule for 9 AM Eastern Time
2030
+ emailr broadcasts schedule brd_abc123 --at "2024-06-15T09:00:00-04:00"
2031
+
2032
+ # Schedule for noon Central European Time
2033
+ emailr broadcasts schedule brd_abc123 --at "2024-06-15T12:00:00+01:00"
2034
+
2035
+ # Get JSON output for scripting
2036
+ emailr broadcasts schedule brd_abc123 --at "2024-12-25T10:00:00Z" --format json
2037
+
2038
+ SEE ALSO
2039
+ emailr broadcasts cancel Cancel a scheduled broadcast`).requiredOption("--at <datetime>","Schedule time (ISO 8601)").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).broadcasts.schedule(t,e.at);e.format==="json"?c(o,"json"):(d("Broadcast scheduled successfully!"),c({ID:o.id,Name:o.name,Status:o.status,"Scheduled At":o.scheduled_at},"table"));}catch(a){l(a instanceof Error?a.message:"Failed to schedule broadcast"),process.exit(1);}}),i.command("cancel <broadcast_id>").description(`Cancel a scheduled broadcast
2040
+
2041
+ USAGE
2042
+ emailr broadcasts cancel <broadcast_id> [options]
2043
+
2044
+ DESCRIPTION
2045
+ Cancels a scheduled broadcast, returning it to 'draft' status.
2046
+ The broadcast can then be rescheduled or sent immediately.
2047
+ Only broadcasts with 'scheduled' status can be cancelled.
2048
+
2049
+ ARGUMENTS
2050
+ <broadcast_id> The unique broadcast identifier (e.g., brd_abc123)
2051
+
2052
+ OPTIONS
2053
+ --format <format> Output format: json | table (default: table)
2054
+
2055
+ OUTPUT FORMATS
2056
+ --format json Returns: { success: boolean }
2057
+ --format table Human-readable success message
2058
+
2059
+ EXAMPLES
2060
+ # Cancel a scheduled broadcast
2061
+ emailr broadcasts cancel brd_abc123
2062
+
2063
+ # Get JSON output for scripting
2064
+ emailr broadcasts cancel brd_abc123 --format json
2065
+
2066
+ NOTE
2067
+ Broadcasts that are already 'sending' or 'sent' cannot be cancelled.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).broadcasts.cancel(t);e.format==="json"?c(o,"json"):d("Broadcast cancelled successfully!");}catch(a){l(a instanceof Error?a.message:"Failed to cancel broadcast"),process.exit(1);}}),i.command("delete <broadcast_id>").description(`Delete a broadcast
2068
+
2069
+ USAGE
2070
+ emailr broadcasts delete <broadcast_id> [options]
2071
+
2072
+ DESCRIPTION
2073
+ Permanently deletes a broadcast. This action cannot be undone.
2074
+ Delivery statistics and history will be lost.
2075
+
2076
+ ARGUMENTS
2077
+ <broadcast_id> The unique broadcast identifier (e.g., brd_abc123)
2078
+
2079
+ OPTIONS
2080
+ --format <format> Output format: json | table (default: table)
2081
+
2082
+ OUTPUT FORMATS
2083
+ --format json Returns: { success: boolean }
2084
+ --format table Human-readable success message
2085
+
2086
+ EXAMPLES
2087
+ # Delete a broadcast
2088
+ emailr broadcasts delete brd_abc123
2089
+
2090
+ # Get JSON output for scripting
2091
+ emailr broadcasts delete brd_abc123 --format json
2092
+
2093
+ WARNING
2094
+ This action is permanent and cannot be undone.
2095
+ All delivery statistics will be lost.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).broadcasts.delete(t);e.format==="json"?c(o,"json"):d("Broadcast deleted successfully!");}catch(a){l(a instanceof Error?a.message:"Failed to delete broadcast"),process.exit(1);}}),i}function Ie(){let i=new Command("webhooks").description(`Manage webhooks
2096
+
2097
+ USAGE
2098
+ emailr webhooks <subcommand> [options]
2099
+
2100
+ DESCRIPTION
2101
+ Create, update, and manage webhook endpoints for receiving real-time
2102
+ notifications about email and contact events. Webhooks allow you to
2103
+ integrate Emailr with your own systems and automate workflows.
2104
+
2105
+ SUBCOMMANDS
2106
+ list List all webhooks
2107
+ get <id> Get webhook details including secret
2108
+ create Create a new webhook endpoint
2109
+ update <id> Update webhook name, URL, or events
2110
+ enable <id> Enable a disabled webhook
2111
+ disable <id> Disable a webhook (stops event delivery)
2112
+ delete <id> Delete a webhook permanently
2113
+
2114
+ AVAILABLE EVENT TYPES
2115
+ Email Events:
2116
+ email.sent Email accepted for delivery
2117
+ email.delivered Email successfully delivered to recipient
2118
+ email.bounced Email permanently rejected by recipient server
2119
+ email.complained Recipient marked email as spam
2120
+ email.opened Recipient opened the email
2121
+ email.clicked Recipient clicked a link in the email
2122
+ email.failed Email delivery failed
2123
+ email.delivery_delayed Email delivery temporarily delayed
2124
+
2125
+ Contact Events:
2126
+ contact.created New contact added to audience
2127
+ contact.updated Contact information updated
2128
+ contact.deleted Contact removed from audience
2129
+
2130
+ Domain Events:
2131
+ domain.verified Domain identity verified
2132
+ domain.verification_failed Domain verification failed
2133
+ domain.mail_from_verified MAIL FROM domain verified
2134
+ domain.mail_from_failed MAIL FROM verification failed
2135
+ domain.deactivated Domain deactivated
2136
+
2137
+ OPTIONS
2138
+ --format <format> Output format: json | table (default: table)
2139
+
2140
+ OUTPUT FORMATS
2141
+ --format json Machine-readable JSON output
2142
+ --format table Human-readable table output (default)
2143
+
2144
+ EVENTS OPTION FORMAT
2145
+ The --events option accepts a comma-separated list of event types:
2146
+
2147
+ Single event:
2148
+ --events "email.sent"
2149
+
2150
+ Multiple events:
2151
+ --events "email.sent,email.delivered,email.bounced"
2152
+
2153
+ All email events:
2154
+ --events "email.sent,email.delivered,email.bounced,email.complained,email.opened,email.clicked,email.failed"
2155
+
2156
+ Contact events:
2157
+ --events "contact.created,contact.updated,contact.deleted"
2158
+
2159
+ Mixed events:
2160
+ --events "email.delivered,email.bounced,contact.created"
2161
+
2162
+ EXAMPLES
2163
+ # List all webhooks
2164
+ emailr webhooks list
2165
+
2166
+ # Get webhook details (includes secret for signature verification)
2167
+ emailr webhooks get whk_abc123
2168
+
2169
+ # Create webhook for email delivery events
2170
+ emailr webhooks create --name "Delivery Tracker" \\
2171
+ --url "https://example.com/webhooks/emailr" \\
2172
+ --events "email.sent,email.delivered,email.bounced"
2173
+
2174
+ # Create webhook for all email tracking events
2175
+ emailr webhooks create --name "Email Analytics" \\
2176
+ --url "https://example.com/webhooks/analytics" \\
2177
+ --events "email.delivered,email.opened,email.clicked,email.bounced"
2178
+
2179
+ # Create webhook for contact changes
2180
+ emailr webhooks create --name "CRM Sync" \\
2181
+ --url "https://example.com/webhooks/crm" \\
2182
+ --events "contact.created,contact.updated,contact.deleted"
2183
+
2184
+ # Update webhook URL
2185
+ emailr webhooks update whk_abc123 --url "https://new-url.com/webhook"
2186
+
2187
+ # Update webhook events
2188
+ emailr webhooks update whk_abc123 --events "email.sent,email.delivered"
2189
+
2190
+ # Disable webhook temporarily
2191
+ emailr webhooks disable whk_abc123
2192
+
2193
+ # Re-enable webhook
2194
+ emailr webhooks enable whk_abc123
2195
+
2196
+ # Delete webhook
2197
+ emailr webhooks delete whk_abc123
2198
+
2199
+ # Get JSON output for scripting
2200
+ emailr webhooks list --format json
2201
+
2202
+ WEBHOOK PAYLOAD
2203
+ Webhooks receive POST requests with JSON payloads:
2204
+
2205
+ {
2206
+ "type": "email.delivered",
2207
+ "created_at": "2024-02-22T23:41:12.126Z",
2208
+ "data": {
2209
+ "email_id": "em_abc123",
2210
+ "to": "recipient@example.com",
2211
+ "subject": "Your order confirmation"
2212
+ }
2213
+ }
2214
+
2215
+ SIGNATURE VERIFICATION
2216
+ Each webhook request includes an X-Emailr-Signature header.
2217
+ Use the webhook secret (shown in 'get' command) to verify signatures.
2218
+
2219
+ SEE ALSO
2220
+ emailr domains Manage sending domains
2221
+ emailr send Send emails directly`);return i.command("list").description(`List all webhooks
2222
+
2223
+ USAGE
2224
+ emailr webhooks list [options]
2225
+
2226
+ DESCRIPTION
2227
+ Retrieves a list of all webhook endpoints configured for your account.
2228
+ Shows webhook ID, name, URL, subscribed events, and active status.
2229
+
2230
+ OPTIONS
2231
+ --format <format> Output format: json | table (default: table)
2232
+
2233
+ OUTPUT FORMATS
2234
+ --format json Machine-readable JSON array of webhook objects
2235
+ --format table Human-readable table with columns: ID, Name, URL, Events, Active
2236
+
2237
+ EXAMPLES
2238
+ # List all webhooks
2239
+ emailr webhooks list
2240
+
2241
+ # Get JSON output for scripting
2242
+ emailr webhooks list --format json
2243
+
2244
+ # Filter active webhooks with jq
2245
+ emailr webhooks list --format json | jq '.[] | select(.active == true)'
2246
+
2247
+ # Count webhooks
2248
+ emailr webhooks list --format json | jq 'length'`).option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),r=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).webhooks.list();if(t.format==="json")c(r,"json");else {if(r.data.length===0){console.log("No webhooks found.");return}let o=r.data.map(n=>({ID:n.id,Name:n.name,URL:n.url.substring(0,40)+(n.url.length>40?"...":""),Events:n.events.join(", "),Active:n.active?"Yes":"No"}));c(o,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list webhooks"),process.exit(1);}}),i.command("get <webhook_id>").description(`Get webhook details
2249
+
2250
+ USAGE
2251
+ emailr webhooks get <webhook_id> [options]
2252
+
2253
+ DESCRIPTION
2254
+ Retrieves detailed information about a specific webhook including
2255
+ the webhook secret used for signature verification.
2256
+
2257
+ ARGUMENTS
2258
+ <webhook_id> The unique webhook identifier (e.g., whk_abc123)
2259
+
2260
+ OPTIONS
2261
+ --format <format> Output format: json | table (default: table)
2262
+
2263
+ OUTPUT FORMATS
2264
+ --format json Full webhook object with all fields including secret
2265
+ --format table Summary table with webhook details
2266
+
2267
+ WEBHOOK SECRET
2268
+ The secret is used to verify webhook signatures. Each webhook request
2269
+ includes an X-Emailr-Signature header that you can verify using this secret.
2270
+
2271
+ EXAMPLES
2272
+ # Get webhook details
2273
+ emailr webhooks get whk_abc123
2274
+
2275
+ # Get full webhook data as JSON
2276
+ emailr webhooks get whk_abc123 --format json
2277
+
2278
+ # Extract just the secret
2279
+ emailr webhooks get whk_abc123 --format json | jq -r '.secret'`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).webhooks.get(t);e.format==="json"?c(o,"json"):c({ID:o.id,Name:o.name,URL:o.url,Events:o.events.join(", "),Active:o.active?"Yes":"No",Secret:o.secret,"Created At":o.created_at},"table");}catch(a){l(a instanceof Error?a.message:"Failed to get webhook"),process.exit(1);}}),i.command("create").description(`Create a new webhook
2280
+
2281
+ USAGE
2282
+ emailr webhooks create --name <name> --url <url> --events <events> [options]
2283
+
2284
+ DESCRIPTION
2285
+ Creates a new webhook endpoint to receive real-time notifications
2286
+ about email and contact events. The webhook is enabled by default.
2287
+
2288
+ OPTIONS
2289
+ --name <webhook_name> Webhook name for identification (required)
2290
+ --url <endpoint_url> HTTPS URL to receive webhook POST requests (required)
2291
+ --events <event_list> Comma-separated list of event types (required)
2292
+ --format <format> Output format: json | table (default: table)
2293
+
2294
+ AVAILABLE EVENT TYPES
2295
+ Email Events:
2296
+ email.sent Email accepted for delivery
2297
+ email.delivered Email successfully delivered
2298
+ email.bounced Email permanently rejected
2299
+ email.complained Recipient marked as spam
2300
+ email.opened Recipient opened email
2301
+ email.clicked Recipient clicked link
2302
+ email.failed Delivery failed
2303
+ email.delivery_delayed Delivery temporarily delayed
2304
+
2305
+ Contact Events:
2306
+ contact.created New contact added
2307
+ contact.updated Contact updated
2308
+ contact.deleted Contact removed
2309
+
2310
+ Domain Events:
2311
+ domain.verified Domain verified
2312
+ domain.verification_failed Verification failed
2313
+ domain.mail_from_verified MAIL FROM verified
2314
+ domain.mail_from_failed MAIL FROM failed
2315
+ domain.deactivated Domain deactivated
2316
+
2317
+ EVENTS FORMAT
2318
+ Provide events as a comma-separated list (no spaces after commas):
2319
+ --events "email.sent,email.delivered,email.bounced"
2320
+
2321
+ OUTPUT FORMATS
2322
+ --format json Full webhook object including generated secret
2323
+ --format table Summary with ID, Name, URL, and Secret
2324
+
2325
+ EXAMPLES
2326
+ # Create webhook for delivery tracking
2327
+ emailr webhooks create --name "Delivery Tracker" \\
2328
+ --url "https://example.com/webhooks/emailr" \\
2329
+ --events "email.sent,email.delivered,email.bounced"
2330
+
2331
+ # Create webhook for email analytics
2332
+ emailr webhooks create --name "Analytics" \\
2333
+ --url "https://analytics.example.com/hook" \\
2334
+ --events "email.delivered,email.opened,email.clicked"
2335
+
2336
+ # Create webhook for CRM integration
2337
+ emailr webhooks create --name "CRM Sync" \\
2338
+ --url "https://crm.example.com/emailr" \\
2339
+ --events "contact.created,contact.updated,contact.deleted"
2340
+
2341
+ # Create webhook for bounce handling
2342
+ emailr webhooks create --name "Bounce Handler" \\
2343
+ --url "https://example.com/bounces" \\
2344
+ --events "email.bounced,email.complained"
2345
+
2346
+ # Get JSON output with secret
2347
+ emailr webhooks create --name "Test" \\
2348
+ --url "https://example.com/test" \\
2349
+ --events "email.sent" --format json
2350
+
2351
+ NOTE
2352
+ Save the webhook secret returned by this command. You'll need it
2353
+ to verify webhook signatures in your endpoint.`).requiredOption("--name <name>","Webhook name").requiredOption("--url <url>","Webhook URL").requiredOption("--events <events>","Events to subscribe to (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),a=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r=t.events.split(",").map(n=>n.trim()),o=await a.webhooks.create({name:t.name,url:t.url,events:r});t.format==="json"?c(o,"json"):(d("Webhook created successfully!"),c({ID:o.id,Name:o.name,URL:o.url,Secret:o.secret},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create webhook"),process.exit(1);}}),i.command("update <webhook_id>").description(`Update a webhook
2354
+
2355
+ USAGE
2356
+ emailr webhooks update <webhook_id> [options]
2357
+
2358
+ DESCRIPTION
2359
+ Updates an existing webhook's name, URL, or subscribed events.
2360
+ Only provide the options you want to change.
2361
+
2362
+ ARGUMENTS
2363
+ <webhook_id> The unique webhook identifier (e.g., whk_abc123)
2364
+
2365
+ OPTIONS
2366
+ --name <webhook_name> New webhook name
2367
+ --url <endpoint_url> New webhook URL
2368
+ --events <event_list> New comma-separated list of events (replaces existing)
2369
+ --format <format> Output format: json | table (default: table)
2370
+
2371
+ EVENTS FORMAT
2372
+ The --events option replaces all existing events:
2373
+ --events "email.sent,email.delivered"
2374
+
2375
+ To add events, first get current events, then include all in update.
2376
+
2377
+ OUTPUT FORMATS
2378
+ --format json Updated webhook object
2379
+ --format table Summary with ID, Name, URL
2380
+
2381
+ EXAMPLES
2382
+ # Update webhook name
2383
+ emailr webhooks update whk_abc123 --name "New Name"
2384
+
2385
+ # Update webhook URL
2386
+ emailr webhooks update whk_abc123 --url "https://new-url.com/webhook"
2387
+
2388
+ # Update subscribed events
2389
+ emailr webhooks update whk_abc123 --events "email.sent,email.delivered,email.bounced"
2390
+
2391
+ # Update multiple fields
2392
+ emailr webhooks update whk_abc123 \\
2393
+ --name "Updated Webhook" \\
2394
+ --url "https://new-url.com/hook" \\
2395
+ --events "email.delivered,email.bounced"
2396
+
2397
+ # Get JSON output
2398
+ emailr webhooks update whk_abc123 --name "New Name" --format json`).option("--name <name>","New webhook name").option("--url <url>","New webhook URL").option("--events <events>","New events (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),r=new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}),o={};e.name&&(o.name=e.name),e.url&&(o.url=e.url),e.events&&(o.events=e.events.split(",").map(s=>s.trim()));let n=await r.webhooks.update(t,o);e.format==="json"?c(n,"json"):(d("Webhook updated successfully!"),c({ID:n.id,Name:n.name,URL:n.url},"table"));}catch(a){l(a instanceof Error?a.message:"Failed to update webhook"),process.exit(1);}}),i.command("enable <webhook_id>").description(`Enable a webhook
2399
+
2400
+ USAGE
2401
+ emailr webhooks enable <webhook_id> [options]
2402
+
2403
+ DESCRIPTION
2404
+ Enables a disabled webhook to resume receiving event notifications.
2405
+ Use this after fixing issues with your webhook endpoint.
2406
+
2407
+ ARGUMENTS
2408
+ <webhook_id> The unique webhook identifier (e.g., whk_abc123)
2409
+
2410
+ OPTIONS
2411
+ --format <format> Output format: json | table (default: table)
2412
+
2413
+ OUTPUT FORMATS
2414
+ --format json Returns: { success: boolean, active: boolean }
2415
+ --format table Human-readable success message
2416
+
2417
+ EXAMPLES
2418
+ # Enable a webhook
2419
+ emailr webhooks enable whk_abc123
2420
+
2421
+ # Get JSON output
2422
+ emailr webhooks enable whk_abc123 --format json`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).webhooks.enable(t);e.format==="json"?c(o,"json"):d("Webhook enabled successfully!");}catch(a){l(a instanceof Error?a.message:"Failed to enable webhook"),process.exit(1);}}),i.command("disable <webhook_id>").description(`Disable a webhook
2423
+
2424
+ USAGE
2425
+ emailr webhooks disable <webhook_id> [options]
2426
+
2427
+ DESCRIPTION
2428
+ Disables a webhook to temporarily stop receiving event notifications.
2429
+ Events that occur while disabled are not queued or retried.
2430
+ Use 'enable' to resume receiving events.
2431
+
2432
+ ARGUMENTS
2433
+ <webhook_id> The unique webhook identifier (e.g., whk_abc123)
2434
+
2435
+ OPTIONS
2436
+ --format <format> Output format: json | table (default: table)
2437
+
2438
+ OUTPUT FORMATS
2439
+ --format json Returns: { success: boolean, active: boolean }
2440
+ --format table Human-readable success message
2441
+
2442
+ EXAMPLES
2443
+ # Disable a webhook
2444
+ emailr webhooks disable whk_abc123
2445
+
2446
+ # Get JSON output
2447
+ emailr webhooks disable whk_abc123 --format json
2448
+
2449
+ NOTE
2450
+ Events are not queued while webhook is disabled. If you need to
2451
+ temporarily stop processing, consider handling this in your endpoint.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).webhooks.disable(t);e.format==="json"?c(o,"json"):d("Webhook disabled successfully!");}catch(a){l(a instanceof Error?a.message:"Failed to disable webhook"),process.exit(1);}}),i.command("delete <webhook_id>").description(`Delete a webhook
2452
+
2453
+ USAGE
2454
+ emailr webhooks delete <webhook_id> [options]
2455
+
2456
+ DESCRIPTION
2457
+ Permanently deletes a webhook endpoint. This action cannot be undone.
2458
+ The webhook will immediately stop receiving events.
2459
+
2460
+ ARGUMENTS
2461
+ <webhook_id> The unique webhook identifier (e.g., whk_abc123)
2462
+
2463
+ OPTIONS
2464
+ --format <format> Output format: json | table (default: table)
2465
+
2466
+ OUTPUT FORMATS
2467
+ --format json Returns: { success: boolean }
2468
+ --format table Human-readable success message
2469
+
2470
+ EXAMPLES
2471
+ # Delete a webhook
2472
+ emailr webhooks delete whk_abc123
2473
+
2474
+ # Get JSON output
2475
+ emailr webhooks delete whk_abc123 --format json
2476
+
2477
+ WARNING
2478
+ This action is permanent and cannot be undone.
2479
+ Create a new webhook if you need to restore functionality.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).webhooks.delete(t);e.format==="json"?c(o,"json"):d("Webhook deleted successfully!");}catch(a){l(a instanceof Error?a.message:"Failed to delete webhook"),process.exit(1);}}),i}function ke(){let i=new Command("segments").description(`Manage contact segments
2480
+
2481
+ USAGE
2482
+ emailr segments <subcommand> [options]
2483
+
2484
+ DESCRIPTION
2485
+ Create, update, list, and manage contact segments. Segments are dynamic
2486
+ groups of contacts defined by conditions. Use segments to target specific
2487
+ audiences for broadcasts and campaigns.
2488
+
2489
+ SUBCOMMANDS
2490
+ list List all segments
2491
+ get <id> Get a segment by ID
2492
+ create Create a new segment with conditions
2493
+ update <id> Update an existing segment
2494
+ delete <id> Delete a segment
2495
+ count <id> Get the number of contacts in a segment
2496
+
2497
+ OPTIONS
2498
+ --format <format> Output format: json | table (default: table)
2499
+
2500
+ OUTPUT FORMATS
2501
+ --format json Machine-readable JSON output
2502
+ --format table Human-readable table output (default)
2503
+
2504
+ CONDITIONS OPTION
2505
+ --conditions <json> Segment conditions as JSON array
2506
+
2507
+ Conditions define which contacts belong to a segment. Each condition
2508
+ specifies a field, operator, and value to match against.
2509
+
2510
+ Condition Structure:
2511
+ {
2512
+ "field": "<field_name>",
2513
+ "operator": "<operator>",
2514
+ "value": "<value>"
2515
+ }
2516
+
2517
+ Available Fields:
2518
+ - email Contact email address
2519
+ - first_name Contact first name
2520
+ - last_name Contact last name
2521
+ - subscribed Subscription status (true/false)
2522
+ - created_at Contact creation date
2523
+ - metadata.* Custom metadata fields (e.g., metadata.plan)
2524
+
2525
+ Available Operators:
2526
+ - equals Exact match
2527
+ - not_equals Does not match
2528
+ - contains Contains substring (strings only)
2529
+ - not_contains Does not contain substring
2530
+ - starts_with Starts with prefix
2531
+ - ends_with Ends with suffix
2532
+ - greater_than Greater than (numbers/dates)
2533
+ - less_than Less than (numbers/dates)
2534
+ - is_set Field has a value
2535
+ - is_not_set Field is empty/null
2536
+
2537
+ COMMON SEGMENT CONDITION EXAMPLES
2538
+
2539
+ # Subscribed contacts only
2540
+ '[{"field": "subscribed", "operator": "equals", "value": true}]'
2541
+
2542
+ # Contacts with Gmail addresses
2543
+ '[{"field": "email", "operator": "contains", "value": "@gmail.com"}]'
2544
+
2545
+ # Premium plan users (using metadata)
2546
+ '[{"field": "metadata.plan", "operator": "equals", "value": "premium"}]'
2547
+
2548
+ # Contacts created in the last 30 days
2549
+ '[{"field": "created_at", "operator": "greater_than", "value": "2024-01-01"}]'
2550
+
2551
+ # Multiple conditions (AND logic)
2552
+ '[
2553
+ {"field": "subscribed", "operator": "equals", "value": true},
2554
+ {"field": "metadata.plan", "operator": "equals", "value": "enterprise"}
2555
+ ]'
2556
+
2557
+ # VIP customers with high engagement score
2558
+ '[
2559
+ {"field": "metadata.vip", "operator": "equals", "value": true},
2560
+ {"field": "metadata.score", "operator": "greater_than", "value": 80}
2561
+ ]'
2562
+
2563
+ EXAMPLES
2564
+ # List all segments
2565
+ emailr segments list
2566
+
2567
+ # Get a specific segment
2568
+ emailr segments get seg_abc123
2569
+
2570
+ # Create a segment for subscribed contacts
2571
+ emailr segments create --name "Active Subscribers" \\
2572
+ --conditions '[{"field": "subscribed", "operator": "equals", "value": true}]'
2573
+
2574
+ # Create a segment for premium users
2575
+ emailr segments create --name "Premium Users" \\
2576
+ --description "Users on premium plan" \\
2577
+ --conditions '[{"field": "metadata.plan", "operator": "equals", "value": "premium"}]'
2578
+
2579
+ # Update segment conditions
2580
+ emailr segments update seg_abc123 \\
2581
+ --conditions '[{"field": "metadata.plan", "operator": "equals", "value": "enterprise"}]'
2582
+
2583
+ # Get contact count in a segment
2584
+ emailr segments count seg_abc123
2585
+
2586
+ # Delete a segment
2587
+ emailr segments delete seg_abc123
2588
+
2589
+ # Get JSON output for scripting
2590
+ emailr segments list --format json
2591
+
2592
+ SEE ALSO
2593
+ emailr contacts Manage individual contacts
2594
+ emailr broadcasts Send emails to segments`);return i.command("list").description(`List all segments
2595
+
2596
+ USAGE
2597
+ emailr segments list [options]
2598
+
2599
+ DESCRIPTION
2600
+ Retrieves a list of all segments in your account. Returns segment ID,
2601
+ name, description, and creation date.
2602
+
2603
+ OPTIONS
2604
+ --format <format> Output format: json | table (default: table)
2605
+
2606
+ OUTPUT FORMATS
2607
+ --format json Machine-readable JSON array of segment objects
2608
+ --format table Human-readable table with columns: ID, Name, Description, Created At
2609
+
2610
+ EXAMPLES
2611
+ # List all segments
2612
+ emailr segments list
2613
+
2614
+ # Get JSON output for scripting
2615
+ emailr segments list --format json
2616
+
2617
+ # Pipe JSON to jq for processing
2618
+ emailr segments list --format json | jq '.[].name'`).option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),r=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).segments.list();if(t.format==="json")c(r,"json");else {if(r.length===0){console.log("No segments found.");return}let o=r.map(n=>({ID:n.id,Name:n.name,Description:(n.description||"").substring(0,30)+((n.description?.length||0)>30?"...":""),"Created At":new Date(n.created_at).toLocaleDateString()}));c(o,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list segments"),process.exit(1);}}),i.command("get <segment_id>").description(`Get segment details
2619
+
2620
+ USAGE
2621
+ emailr segments get <segment_id> [options]
2622
+
2623
+ DESCRIPTION
2624
+ Retrieves detailed information about a specific segment including
2625
+ its name, description, conditions, and timestamps.
2626
+
2627
+ ARGUMENTS
2628
+ <segment_id> The unique segment identifier (e.g., seg_abc123)
2629
+
2630
+ OPTIONS
2631
+ --format <format> Output format: json | table (default: table)
2632
+
2633
+ OUTPUT FORMATS
2634
+ --format json Full segment object including conditions array
2635
+ --format table Summary table with ID, Name, Description, Conditions, Created At, Updated At
2636
+
2637
+ EXAMPLES
2638
+ # Get segment details
2639
+ emailr segments get seg_abc123
2640
+
2641
+ # Get full segment data as JSON
2642
+ emailr segments get seg_abc123 --format json
2643
+
2644
+ # Pipe JSON to jq to view conditions
2645
+ emailr segments get seg_abc123 --format json | jq '.conditions'`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).segments.get(t);e.format==="json"?c(o,"json"):c({ID:o.id,Name:o.name,Description:o.description||"N/A",Conditions:JSON.stringify(o.conditions),"Created At":o.created_at,"Updated At":o.updated_at},"table");}catch(a){l(a instanceof Error?a.message:"Failed to get segment"),process.exit(1);}}),i.command("create").description(`Create a new segment
2646
+
2647
+ USAGE
2648
+ emailr segments create --name <segment_name> --conditions <json> [options]
2649
+
2650
+ DESCRIPTION
2651
+ Creates a new segment with the specified name and conditions. Conditions
2652
+ define which contacts belong to the segment using field/operator/value rules.
2653
+
2654
+ OPTIONS
2655
+ --name <segment_name> Segment name (required)
2656
+ --conditions <json> Segment conditions as JSON array (required)
2657
+ --description <text> Segment description
2658
+ --format <format> Output format: json | table (default: table)
2659
+
2660
+ CONDITIONS FORMAT
2661
+ Conditions are specified as a JSON array of condition objects:
2662
+ '[{"field": "<field>", "operator": "<op>", "value": "<val>"}]'
2663
+
2664
+ See 'emailr segments --help' for full conditions documentation.
2665
+
2666
+ OUTPUT FORMATS
2667
+ --format json Full segment object with all fields
2668
+ --format table Summary table with ID and Name
2669
+
2670
+ EXAMPLES
2671
+ # Create segment for subscribed contacts
2672
+ emailr segments create --name "Active Subscribers" \\
2673
+ --conditions '[{"field": "subscribed", "operator": "equals", "value": true}]'
2674
+
2675
+ # Create segment with description
2676
+ emailr segments create --name "Premium Users" \\
2677
+ --description "Users on premium plan" \\
2678
+ --conditions '[{"field": "metadata.plan", "operator": "equals", "value": "premium"}]'
2679
+
2680
+ # Create segment with multiple conditions
2681
+ emailr segments create --name "Engaged Premium" \\
2682
+ --conditions '[
2683
+ {"field": "subscribed", "operator": "equals", "value": true},
2684
+ {"field": "metadata.plan", "operator": "equals", "value": "premium"}
2685
+ ]'
2686
+
2687
+ # Create segment for Gmail users
2688
+ emailr segments create --name "Gmail Users" \\
2689
+ --conditions '[{"field": "email", "operator": "contains", "value": "@gmail.com"}]'
2690
+
2691
+ # Get JSON output for scripting
2692
+ emailr segments create --name "Test" \\
2693
+ --conditions '[{"field": "subscribed", "operator": "equals", "value": true}]' \\
2694
+ --format json`).requiredOption("--name <name>","Segment name").requiredOption("--conditions <json>","Segment conditions as JSON").option("--description <description>","Segment description").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),a=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r;try{r=JSON.parse(t.conditions);}catch{l("Invalid JSON for --conditions"),process.exit(1);}let o=await a.segments.create({name:t.name,description:t.description,conditions:r});t.format==="json"?c(o,"json"):(d("Segment created successfully!"),c({ID:o.id,Name:o.name},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create segment"),process.exit(1);}}),i.command("update <segment_id>").description(`Update a segment
2695
+
2696
+ USAGE
2697
+ emailr segments update <segment_id> [options]
2698
+
2699
+ DESCRIPTION
2700
+ Updates an existing segment with new values. Only specified fields
2701
+ are updated; omitted fields remain unchanged.
2702
+
2703
+ ARGUMENTS
2704
+ <segment_id> The unique segment identifier (e.g., seg_abc123)
2705
+
2706
+ OPTIONS
2707
+ --name <segment_name> New segment name
2708
+ --description <text> New description
2709
+ --conditions <json> New conditions as JSON array (replaces existing)
2710
+ --format <format> Output format: json | table (default: table)
2711
+
2712
+ CONDITIONS FORMAT
2713
+ Conditions are specified as a JSON array of condition objects.
2714
+ Note: This replaces all existing conditions, not individual ones.
2715
+ See 'emailr segments --help' for full conditions documentation.
2716
+
2717
+ OUTPUT FORMATS
2718
+ --format json Full updated segment object
2719
+ --format table Summary table with ID and Name
2720
+
2721
+ EXAMPLES
2722
+ # Update segment name
2723
+ emailr segments update seg_abc123 --name "New Segment Name"
2724
+
2725
+ # Update segment description
2726
+ emailr segments update seg_abc123 --description "Updated description"
2727
+
2728
+ # Update segment conditions
2729
+ emailr segments update seg_abc123 \\
2730
+ --conditions '[{"field": "metadata.plan", "operator": "equals", "value": "enterprise"}]'
2731
+
2732
+ # Update multiple fields
2733
+ emailr segments update seg_abc123 \\
2734
+ --name "Enterprise Users" \\
2735
+ --description "Users on enterprise plan" \\
2736
+ --conditions '[{"field": "metadata.plan", "operator": "equals", "value": "enterprise"}]'
2737
+
2738
+ # Get JSON output
2739
+ emailr segments update seg_abc123 --name "Updated" --format json`).option("--name <name>","New segment name").option("--description <description>","New description").option("--conditions <json>","New conditions as JSON").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),r=new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}),o={};if(e.name&&(o.name=e.name),e.description&&(o.description=e.description),e.conditions)try{o.conditions=JSON.parse(e.conditions);}catch{l("Invalid JSON for --conditions"),process.exit(1);}let n=await r.segments.update(t,o);e.format==="json"?c(n,"json"):(d("Segment updated successfully!"),c({ID:n.id,Name:n.name},"table"));}catch(a){l(a instanceof Error?a.message:"Failed to update segment"),process.exit(1);}}),i.command("delete <segment_id>").description(`Delete a segment
2740
+
2741
+ USAGE
2742
+ emailr segments delete <segment_id>
2743
+
2744
+ DESCRIPTION
2745
+ Permanently deletes a segment. This action cannot be undone.
2746
+ Contacts in the segment are NOT deleted, only the segment definition.
2747
+
2748
+ ARGUMENTS
2749
+ <segment_id> The unique segment identifier (e.g., seg_abc123)
2750
+
2751
+ OPTIONS
2752
+ --format <format> Output format: json | table (default: table)
2753
+
2754
+ OUTPUT FORMATS
2755
+ --format json Returns: { success: boolean }
2756
+ --format table Human-readable success message
2757
+
2758
+ EXAMPLES
2759
+ # Delete a segment
2760
+ emailr segments delete seg_abc123
2761
+
2762
+ WARNING
2763
+ This action is permanent and cannot be undone.
2764
+ Any broadcasts targeting this segment will need to be updated.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).segments.delete(t);e.format==="json"?c(o,"json"):d("Segment deleted successfully!");}catch(a){l(a instanceof Error?a.message:"Failed to delete segment"),process.exit(1);}}),i.command("count <segment_id>").description(`Get the number of contacts in a segment
2765
+
2766
+ USAGE
2767
+ emailr segments count <segment_id> [options]
2768
+
2769
+ DESCRIPTION
2770
+ Returns the count of contacts that match the segment's conditions.
2771
+ Useful for verifying segment conditions before sending broadcasts.
2772
+
2773
+ ARGUMENTS
2774
+ <segment_id> The unique segment identifier (e.g., seg_abc123)
2775
+
2776
+ OPTIONS
2777
+ --format <format> Output format: json | table (default: table)
2778
+
2779
+ OUTPUT FORMATS
2780
+ --format json Returns: { count: <number> }
2781
+ --format table Human-readable message with contact count
2782
+
2783
+ EXAMPLES
2784
+ # Get contact count in a segment
2785
+ emailr segments count seg_abc123
2786
+
2787
+ # Get JSON output for scripting
2788
+ emailr segments count seg_abc123 --format json
2789
+
2790
+ # Extract just the count value
2791
+ emailr segments count seg_abc123 --format json | jq '.count'
2792
+
2793
+ TIP
2794
+ Use this before sending broadcasts to verify your segment
2795
+ targets the expected number of contacts.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let a=m(),o=await new Emailr({apiKey:a.apiKey,baseUrl:a.baseUrl}).segments.getContactsCount(t);e.format==="json"?c(o,"json"):console.log(`Segment contains ${o.count} contacts.`);}catch(a){l(a instanceof Error?a.message:"Failed to get segment count"),process.exit(1);}}),i}function st(i){try{let t=i.startsWith("http")?i:`http://localhost${i}`,e=new URL(t);return {key:e.searchParams.get("key")??void 0,code:e.searchParams.get("code")??void 0,state:e.searchParams.get("state")??void 0,error:e.searchParams.get("error")??void 0,message:e.searchParams.get("message")??void 0}}catch{return {}}}function lt(){return `<!DOCTYPE html>
2796
+ <html lang="en">
2797
+ <head>
2798
+ <meta charset="UTF-8">
2799
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2800
+ <title>Login Successful - Emailr CLI</title>
2801
+ <style>
2802
+ body {
2803
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
2804
+ display: flex;
2805
+ justify-content: center;
2806
+ align-items: center;
2807
+ min-height: 100vh;
2808
+ margin: 0;
2809
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
2810
+ color: #fff;
2811
+ }
2812
+ .container {
2813
+ text-align: center;
2814
+ padding: 2rem;
2815
+ background: rgba(255, 255, 255, 0.1);
2816
+ border-radius: 16px;
2817
+ backdrop-filter: blur(10px);
2818
+ max-width: 400px;
2819
+ }
2820
+ .icon {
2821
+ font-size: 4rem;
2822
+ margin-bottom: 1rem;
2823
+ }
2824
+ h1 {
2825
+ margin: 0 0 1rem 0;
2826
+ font-size: 1.5rem;
2827
+ }
2828
+ p {
2829
+ margin: 0;
2830
+ opacity: 0.9;
2831
+ }
2832
+ </style>
2833
+ </head>
2834
+ <body>
2835
+ <div class="container">
2836
+ <div class="icon">\u2713</div>
2837
+ <h1>Login Successful!</h1>
2838
+ <p>You can close this window and return to your terminal.</p>
2839
+ </div>
2840
+ </body>
2841
+ </html>`}function ee(i){return `<!DOCTYPE html>
2842
+ <html lang="en">
2843
+ <head>
2844
+ <meta charset="UTF-8">
2845
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2846
+ <title>Login Failed - Emailr CLI</title>
402
2847
  <style>
403
2848
  body {
404
2849
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
@@ -444,11 +2889,11 @@ When done:`),console.log(` emailr templates create --name "Template Name" --sub
444
2889
  <div class="icon">\u2717</div>
445
2890
  <h1>Login Failed</h1>
446
2891
  <p>Something went wrong during authentication.</p>
447
- <div class="error-message">${n.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}</div>
2892
+ <div class="error-message">${i.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}</div>
448
2893
  <p style="margin-top: 1rem;">Please close this window and try again.</p>
449
2894
  </div>
450
2895
  </body>
451
- </html>`}function Te(){let n=null,t=0,e=null,o=null,i=null;return {async start(){return new Promise((a,r)=>{n=qe.createServer((s,f)=>{if(s.method!=="GET"||!s.url?.startsWith("/callback")){f.writeHead(404,{"Content-Type":"text/plain"}),f.end("Not Found");return}let g=it(s.url);if(i&&g.state!==i){f.writeHead(400,{"Content-Type":"text/html"}),f.end(ee("Security verification failed. State parameter mismatch.")),e&&e({success:false,error:"State parameter mismatch"});return}if(g.error){let b=g.message||g.error;f.writeHead(200,{"Content-Type":"text/html"}),f.end(ee(b)),e&&e({success:false,error:b});return}let x=g.key||g.code;if(x){f.writeHead(200,{"Content-Type":"text/html"}),f.end(st()),e&&e({success:true,apiKey:x});return}f.writeHead(400,{"Content-Type":"text/html"}),f.end(ee("Invalid callback: missing required parameters.")),e&&e({success:false,error:"Invalid callback: missing required parameters"});}),n.listen(0,"127.0.0.1",()=>{let s=n.address();s&&typeof s=="object"?(t=s.port,a({port:t,url:`http://127.0.0.1:${t}/callback`})):r(new Error("Failed to get server address"));}),n.on("error",s=>{r(new Error(`Failed to start callback server: ${s.message}`));});})},async waitForCallback(a,r){return i=a,new Promise(s=>{e=s,o=setTimeout(()=>{e&&e({success:false,error:"Login timed out. Please try again."});},r);})},async stop(){if(o&&(clearTimeout(o),o=null),n)return new Promise(a=>{n.close(()=>{n=null,a();});})}}}function Pe(){return ct.randomBytes(32).toString("hex")}function Ie(n){return new Promise(t=>{let e=process.platform,o;switch(e){case "darwin":o=`open "${n}"`;break;case "win32":o=`start "" "${n}"`;break;default:o=`xdg-open "${n}"`;break}exec(o,i=>{t(!i);});})}var W=120,dt=process.env.EMAILR_WEB_URL||"https://app.emailr.dev";function pt(n,t){let e=`http://127.0.0.1:${t}/callback`,o=new URLSearchParams({state:n,callback_url:e});return `${dt}/consent/authorize?${o.toString()}`}function Ue(){return new Command("login").description("Log in to Emailr via browser authentication").option("-t, --timeout <seconds>","Timeout in seconds",String(W)).option("--no-browser","Don't automatically open the browser").action(async t=>{await ft({timeout:parseInt(t.timeout,10)||W,noBrowser:t.browser===false});})}async function ft(n){let t=Te(),e=(n.timeout||W)*1e3;try{p("Starting authentication server...");let{port:o,url:i}=await t.start(),a=Pe(),r=pt(a,o);console.log(""),p("Authorization URL:"),console.log(` ${r}`),console.log(""),n.noBrowser?p("Please open the URL above in your browser to continue."):await Ie(r)?p("Browser opened. Please complete authentication in your browser."):(k("Could not open browser automatically."),p("Please open the URL above in your browser to continue.")),console.log(""),p(`Waiting for authentication (timeout: ${n.timeout||W}s)...`);let s=await t.waitForCallback(a,e);s.success&&s.apiKey?(A({apiKey:s.apiKey}),console.log(""),d("Login successful!"),p(`API key saved to: ${j()}`),p("You can now use the Emailr CLI.")):(console.log(""),l(s.error||"Authentication failed."),p("Please try again or use manual configuration:"),console.log(" emailr config set api-key <your-api-key>"),process.exit(1));}catch(o){console.log(""),l(o instanceof Error?o.message:"An unexpected error occurred."),p("Please try again or use manual configuration:"),console.log(" emailr config set api-key <your-api-key>"),process.exit(1);}finally{await t.stop();}}var oe=U.join(G.homedir(),".config","opencode","skills","emailr-cli"),ht=`---
2896
+ </html>`}function je(){let i=null,t=0,e=null,a=null,r=null;return {async start(){return new Promise((o,n)=>{i=Je.createServer((s,p)=>{if(s.method!=="GET"||!s.url?.startsWith("/callback")){p.writeHead(404,{"Content-Type":"text/plain"}),p.end("Not Found");return}let b=st(s.url);if(r&&b.state!==r){p.writeHead(400,{"Content-Type":"text/html"}),p.end(ee("Security verification failed. State parameter mismatch.")),e&&e({success:false,error:"State parameter mismatch"});return}if(b.error){let h=b.message||b.error;p.writeHead(200,{"Content-Type":"text/html"}),p.end(ee(h)),e&&e({success:false,error:h});return}let T=b.key||b.code;if(T){p.writeHead(200,{"Content-Type":"text/html"}),p.end(lt()),e&&e({success:true,apiKey:T});return}p.writeHead(400,{"Content-Type":"text/html"}),p.end(ee("Invalid callback: missing required parameters.")),e&&e({success:false,error:"Invalid callback: missing required parameters"});}),i.listen(0,"127.0.0.1",()=>{let s=i.address();s&&typeof s=="object"?(t=s.port,o({port:t,url:`http://127.0.0.1:${t}/callback`})):n(new Error("Failed to get server address"));}),i.on("error",s=>{n(new Error(`Failed to start callback server: ${s.message}`));});})},async waitForCallback(o,n){return r=o,new Promise(s=>{e=s,a=setTimeout(()=>{e&&e({success:false,error:"Login timed out. Please try again."});},n);})},async stop(){if(a&&(clearTimeout(a),a=null),i)return new Promise(o=>{i.close(()=>{i=null,o();});})}}}function Ce(){return ct.randomBytes(32).toString("hex")}function Ae(i){return new Promise(t=>{let e=process.platform,a;switch(e){case "darwin":a=`open "${i}"`;break;case "win32":a=`start "" "${i}"`;break;default:a=`xdg-open "${i}"`;break}exec(a,r=>{t(!r);});})}var $=120,pt=process.env.EMAILR_WEB_URL||"https://app.emailr.dev";function ut(i,t){let e=`http://127.0.0.1:${t}/callback`,a=new URLSearchParams({state:i,callback_url:e});return `${pt}/consent/authorize?${a.toString()}`}function Pe(){return new Command("login").description("Log in to Emailr via browser authentication").option("-t, --timeout <seconds>","Timeout in seconds",String($)).option("--no-browser","Don't automatically open the browser").action(async t=>{await ft({timeout:parseInt(t.timeout,10)||$,noBrowser:t.browser===false});})}async function ft(i){let t=je(),e=(i.timeout||$)*1e3;try{u("Starting authentication server...");let{port:a,url:r}=await t.start(),o=Ce(),n=ut(o,a);console.log(""),u("Authorization URL:"),console.log(` ${n}`),console.log(""),i.noBrowser?u("Please open the URL above in your browser to continue."):await Ae(n)?u("Browser opened. Please complete authentication in your browser."):(k("Could not open browser automatically."),u("Please open the URL above in your browser to continue.")),console.log(""),u(`Waiting for authentication (timeout: ${i.timeout||$}s)...`);let s=await t.waitForCallback(o,e);s.success&&s.apiKey?(G({apiKey:s.apiKey}),console.log(""),d("Login successful!"),u(`API key saved to: ${I()}`),u("You can now use the Emailr CLI.")):(console.log(""),l(s.error||"Authentication failed."),u("Please try again or use manual configuration:"),console.log(" emailr config set api-key <your-api-key>"),process.exit(1));}catch(a){console.log(""),l(a instanceof Error?a.message:"An unexpected error occurred."),u("Please try again or use manual configuration:"),console.log(" emailr config set api-key <your-api-key>"),process.exit(1);}finally{await t.stop();}}var ae=N.join(W.homedir(),".config","opencode","skills","emailr-cli"),wt=`---
452
2897
  name: emailr-cli
453
2898
  description: Operate the Emailr CLI to send emails, manage contacts, templates, domains, broadcasts, webhooks, and segments. Includes LIVE PREVIEW editing for templates with hot-reload.
454
2899
  ---
@@ -488,7 +2933,7 @@ IMPORTANT: Always use \`--background\` flag so the command returns immediately a
488
2933
  - Create: \`emailr templates create --name "Name" --subject "Subject" --html-file ./template.html\`
489
2934
  - Update: \`emailr templates update <id> --html-file ./template.html\`
490
2935
  - Delete: \`emailr templates delete <id>\`
491
- `;function yt(){try{return execSync("which opencode",{stdio:"ignore"}),!0}catch{return false}}function wt(){console.log("Installing OpenCode AI agent...");try{return execSync("npm install -g opencode-ai@latest",{stdio:"inherit"}),!0}catch{if(process.platform==="darwin")try{return execSync("brew install anomalyco/tap/opencode",{stdio:"inherit"}),!0}catch{return false}return false}}function vt(){E.existsSync(oe)||E.mkdirSync(oe,{recursive:true});let n=U.join(oe,"SKILL.md");E.writeFileSync(n,ht,"utf-8");}function Ke(){return new Command("agent").description(`Launch an AI agent with Emailr CLI expertise
2936
+ `;function yt(){try{return execSync("which opencode",{stdio:"ignore"}),!0}catch{return false}}function St(){console.log("Installing OpenCode AI agent...");try{return execSync("npm install -g opencode-ai@latest",{stdio:"inherit"}),!0}catch{if(process.platform==="darwin")try{return execSync("brew install anomalyco/tap/opencode",{stdio:"inherit"}),!0}catch{return false}return false}}function vt(){v.existsSync(ae)||v.mkdirSync(ae,{recursive:true});let i=N.join(ae,"SKILL.md");v.writeFileSync(i,wt,"utf-8");}function xe(){return new Command("agent").description(`Launch an AI agent with Emailr CLI expertise
492
2937
 
493
2938
  This opens OpenCode (opencode.ai), an AI coding agent that knows how to use all Emailr CLI commands.
494
2939
  The agent can help you:
@@ -496,7 +2941,93 @@ The agent can help you:
496
2941
  - Manage contacts, broadcasts, and segments
497
2942
  - Configure domains and webhooks
498
2943
 
499
- The agent runs in your terminal with full access to the emailr CLI.`).option("--install","Install OpenCode if not already installed").option("--model <model>","Model to use (e.g., anthropic/claude-sonnet-4)").action(async t=>{yt()||(t.install?wt()||(l("Failed to install OpenCode. Please install manually:"),console.log(" npm install -g opencode-ai@latest"),console.log(" # or"),console.log(" brew install anomalyco/tap/opencode"),process.exit(1)):(l("OpenCode AI agent is not installed."),console.log(`
500
- Install it with one of:`),console.log(" emailr agent --install"),console.log(" npm install -g opencode-ai@latest"),console.log(" brew install anomalyco/tap/opencode"),process.exit(1)));try{vt(),d("Emailr CLI skill loaded");}catch(i){k(`Could not install skill: ${i instanceof Error?i.message:String(i)}`);}let e=[];t.model&&e.push("--model",t.model),console.log(`
2944
+ The agent runs in your terminal with full access to the emailr CLI.`).option("--install","Install OpenCode if not already installed").option("--model <model>","Model to use (e.g., anthropic/claude-sonnet-4)").action(async t=>{yt()||(t.install?St()||(l("Failed to install OpenCode. Please install manually:"),console.log(" npm install -g opencode-ai@latest"),console.log(" # or"),console.log(" brew install anomalyco/tap/opencode"),process.exit(1)):(l("OpenCode AI agent is not installed."),console.log(`
2945
+ Install it with one of:`),console.log(" emailr agent --install"),console.log(" npm install -g opencode-ai@latest"),console.log(" brew install anomalyco/tap/opencode"),process.exit(1)));try{vt(),d("Emailr CLI skill loaded");}catch(r){k(`Could not install skill: ${r instanceof Error?r.message:String(r)}`);}let e=[];t.model&&e.push("--model",t.model),console.log(`
501
2946
  Starting Emailr AI Agent...`),console.log("The agent has the emailr-cli skill loaded."),console.log(`Ask it to help with emails, templates, contacts, or broadcasts.
502
- `);let o=spawn("opencode",e,{stdio:"inherit",env:process.env});o.on("error",i=>{l(`Failed to start agent: ${i.message}`),process.exit(1);}),o.on("exit",i=>{process.exit(i??0);});})}var h=new Command;h.name("emailr").description("Emailr CLI - Send emails and manage your email infrastructure").version("1.5.3");h.addCommand(ce());h.addCommand(le());h.addCommand(Se());h.addCommand(Ce());h.addCommand(ke());h.addCommand(Ee());h.addCommand(Fe());h.addCommand(je());h.addCommand(Ue());h.addCommand(Ke());h.parse();
2947
+ `);let a=spawn("opencode",e,{stdio:"inherit",env:process.env});a.on("error",r=>{l(`Failed to start agent: ${r.message}`),process.exit(1);}),a.on("exit",r=>{process.exit(r??0);});})}var g=new Command;g.name("emailr").description(`Emailr CLI - Send emails and manage your email infrastructure
2948
+
2949
+ USAGE
2950
+ emailr <command> [subcommand] [options]
2951
+
2952
+ DESCRIPTION
2953
+ The Emailr CLI provides a complete interface for sending emails and managing
2954
+ your email infrastructure. All commands support both human-readable table
2955
+ output and machine-readable JSON output for scripting and AI agent workflows.
2956
+
2957
+ COMMANDS
2958
+ send Send individual emails with HTML/text content or templates
2959
+ templates Create, edit, preview, and publish email templates
2960
+ contacts Manage email contacts and their metadata
2961
+ segments Create and manage contact segments with conditions
2962
+ broadcasts Send bulk email campaigns to segments
2963
+ webhooks Configure webhook endpoints for email events
2964
+ domains Add and verify sending domains (DKIM, SPF, DMARC)
2965
+ config Configure CLI settings (API key, base URL)
2966
+ login Authenticate with your Emailr account
2967
+ agent Run the AI agent for automated email tasks
2968
+
2969
+ GETTING STARTED
2970
+ 1. Set your API key:
2971
+ emailr config set api-key <your_api_key>
2972
+
2973
+ 2. Verify your configuration:
2974
+ emailr config list
2975
+
2976
+ 3. Send your first email:
2977
+ emailr send --to user@example.com --subject "Hello" --html "<p>Hi!</p>"
2978
+
2979
+ OUTPUT FORMATS
2980
+ Most commands support --format option:
2981
+ --format table Human-readable table output (default)
2982
+ --format json Machine-readable JSON output for scripting
2983
+
2984
+ ENVIRONMENT VARIABLES
2985
+ EMAILR_API_KEY API key (overrides config file)
2986
+ EMAILR_BASE_URL Custom API base URL (overrides config file)
2987
+
2988
+ EXAMPLES
2989
+ # Send a simple email
2990
+ emailr send --to user@example.com --subject "Hello" --html "<p>Hi!</p>"
2991
+
2992
+ # Send using a template
2993
+ emailr send --to user@example.com --template tpl_abc123 \\
2994
+ --template-data '{"name": "John"}'
2995
+
2996
+ # List all templates
2997
+ emailr templates list
2998
+
2999
+ # Create a new contact
3000
+ emailr contacts create --email user@example.com --name "John Doe"
3001
+
3002
+ # Create a segment for active subscribers
3003
+ emailr segments create --name "Active" \\
3004
+ --conditions '[{"field": "subscribed", "operator": "equals", "value": true}]'
3005
+
3006
+ # Send a broadcast to a segment
3007
+ emailr broadcasts create --name "Newsletter" --template tpl_abc123 \\
3008
+ --segment seg_xyz789
3009
+
3010
+ # Add and verify a sending domain
3011
+ emailr domains add example.com
3012
+ emailr domains verify example.com
3013
+
3014
+ # Configure a webhook for delivery events
3015
+ emailr webhooks create --name "Deliveries" --url "https://..." \\
3016
+ --events "email.delivered,email.bounced"
3017
+
3018
+ # Get JSON output for scripting
3019
+ emailr contacts list --format json | jq '.[].email'
3020
+
3021
+ AGENTIC WORKFLOW
3022
+ For collaborating with AI agents on email templates:
3023
+
3024
+ 1. Fetch template: emailr templates fetch <id> --output template.html
3025
+ 2. Edit locally or with AI assistance
3026
+ 3. Push preview: emailr templates push-preview <id> --html-file template.html
3027
+ 4. Share preview URL with AI agent for feedback
3028
+ 5. Iterate until satisfied
3029
+ 6. Publish: emailr templates update <id> --html-file template.html
3030
+
3031
+ MORE INFORMATION
3032
+ Run 'emailr <command> --help' for detailed help on any command.
3033
+ Run 'emailr <command> <subcommand> --help' for subcommand details.`).version("1.5.4");g.addCommand(ce());g.addCommand(me());g.addCommand(Oe());g.addCommand(Ee());g.addCommand(_e());g.addCommand(Ie());g.addCommand(ke());g.addCommand(Ne());g.addCommand(Pe());g.addCommand(xe());g.parse();