emailr-cli 1.7.7 → 1.8.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 +286 -87
  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 v,{readFileSync}from'fs';import B from'os';import _ 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=[_.join(B.homedir(),".emailrrc"),_.join(B.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.
2
+ import {Command}from'commander';import {Emailr}from'emailr';import O,{readFileSync}from'fs';import V from'os';import _ from'path';import le from'cli-table3';import T from'chalk';import Xe from'http';import {URL}from'url';import ft from'crypto';import {spawn,execSync,exec}from'child_process';var Ge=[_.join(V.homedir(),".emailrrc"),_.join(V.homedir(),".config","emailr","config.json")];function c(){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 Ge)if(O.existsSync(i))try{let t=O.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,8 +7,8 @@ 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 i=_.join(B.homedir(),".config","emailr");return _.join(i,"config.json")}function G(i){let t=j(),e=_.dirname(t);v.existsSync(e)||v.mkdirSync(e,{recursive:true});let o={};if(v.existsSync(t))try{o=JSON.parse(v.readFileSync(t,"utf-8"));}catch{}let r={...o,...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),o=new re({head:e.map(r=>S.cyan(r)),style:{head:[],border:[]}});for(let r of i){let a=e.map(n=>{let s=r[n];return se(s)});o.push(a);}console.log(o.toString());}function De(i){let t=new re({style:{head:[],border:[]}});for(let[e,o]of Object.entries(i))t.push([S.cyan(e),se(o)]);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 I(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
10
+ Or run: emailr config set api-key <your-api-key>`)}function N(){let i=_.join(V.homedir(),".config","emailr");return _.join(i,"config.json")}function G(i){let t=N(),e=_.dirname(t);O.existsSync(e)||O.mkdirSync(e,{recursive:true});let o={};if(O.existsSync(t))try{o=JSON.parse(O.readFileSync(t,"utf-8"));}catch{}let s={...o,...i};O.writeFileSync(t,JSON.stringify(s,null,2)+`
11
+ `);}function se(i){try{return c()[i]?.toString()}catch{return}}function m(i,t="table"){t==="json"?console.log(JSON.stringify(i,null,2)):Ke(i);}function Ke(i){Array.isArray(i)?He(i):typeof i=="object"&&i!==null?qe(i):console.log(i);}function He(i){if(i.length===0){console.log(T.gray("No results"));return}let t=i[0];if(typeof t!="object"||t===null){i.forEach(s=>console.log(s));return}let e=Object.keys(t),o=new le({head:e.map(s=>T.cyan(s)),style:{head:[],border:[]}});for(let s of i){let a=e.map(r=>{let n=s[r];return me(n)});o.push(a);}console.log(o.toString());}function qe(i){let t=new le({style:{head:[],border:[]}});for(let[e,o]of Object.entries(i))t.push([T.cyan(e),me(o)]);console.log(t.toString());}function me(i){return i==null?T.gray("-"):typeof i=="boolean"?i?T.green("\u2713"):T.red("\u2717"):typeof i=="object"?JSON.stringify(i):String(i)}function p(i){console.log(T.green("\u2713"),i);}function l(i){console.error(T.red("\u2717"),i);}function I(i){console.warn(T.yellow("\u26A0"),i);}function f(i){console.log(T.blue("\u2139"),i);}function de(){return new Command("send").description(`Send an email
12
12
 
13
13
  USAGE
14
14
  emailr send --to <email_address> [options]
@@ -156,7 +156,7 @@ EXAMPLES
156
156
  SEE ALSO
157
157
  emailr templates Manage email templates
158
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(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r=t.to.split(",").map(s=>s.trim()),a={to:r.length===1?r[0]:r};if(t.from&&(a.from=t.from),t.subject&&(a.subject=t.subject),t.htmlFile)try{a.html=readFileSync(t.htmlFile,"utf-8");}catch{l(`Failed to read HTML file: ${t.htmlFile}`),process.exit(1);}else t.html&&(a.html=t.html);if(t.textFile)try{a.text=readFileSync(t.textFile,"utf-8");}catch{l(`Failed to read text file: ${t.textFile}`),process.exit(1);}else t.text&&(a.text=t.text);if(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(p=>p.trim());a.cc=s.length===1?s[0]:s;}if(t.bcc){let s=t.bcc.split(",").map(p=>p.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 n=await o.emails.send(a);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
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=c(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),s=t.to.split(",").map(n=>n.trim()),a={to:s.length===1?s[0]:s};if(t.from&&(a.from=t.from),t.subject&&(a.subject=t.subject),t.htmlFile)try{a.html=readFileSync(t.htmlFile,"utf-8");}catch{l(`Failed to read HTML file: ${t.htmlFile}`),process.exit(1);}else t.html&&(a.html=t.html);if(t.textFile)try{a.text=readFileSync(t.textFile,"utf-8");}catch{l(`Failed to read text file: ${t.textFile}`),process.exit(1);}else t.text&&(a.text=t.text);if(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 n=t.cc.split(",").map(d=>d.trim());a.cc=n.length===1?n[0]:n;}if(t.bcc){let n=t.bcc.split(",").map(d=>d.trim());a.bcc=n.length===1?n[0]:n;}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"?m(r,"json"):(p("Email sent successfully!"),m({"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 pe(){let i=new Command("contacts").description(`Manage contacts
160
160
 
161
161
  USAGE
162
162
  emailr contacts <subcommand> [options]
@@ -286,7 +286,7 @@ EXAMPLES
286
286
  emailr contacts list --format json
287
287
 
288
288
  # Combine filters with pagination
289
- 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("--search <query>","Search by email, first name, or last name").option("--tags <tags>","Filter by tags (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}),r={limit:parseInt(t.limit,10),offset:parseInt(t.offset,10)};if(t.subscribed&&(r.subscribed=!0),t.unsubscribed&&(r.subscribed=!1),t.search&&(r.search=t.search),t.tags){let n=t.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);n.length>0&&(r.tags=n.join(","));}let a=await o.contacts.list(r);if(t.format==="json")c(a,"json");else {let n=a.contacts.map(s=>({ID:s.id,Email:s.email,Name:[s.first_name,s.last_name].filter(Boolean).join(" ")||"-",Subscribed:s.subscribed,Tags:s.tags?.join(", ")||"-",Created:s.created_at}));c(n,"table"),console.log(`
289
+ 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("--search <query>","Search by email, first name, or last name").option("--tags <tags>","Filter by tags (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=c(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),s={limit:parseInt(t.limit,10),offset:parseInt(t.offset,10)};if(t.subscribed&&(s.subscribed=!0),t.unsubscribed&&(s.subscribed=!1),t.search&&(s.search=t.search),t.tags){let r=t.tags.split(",").map(n=>n.trim().toLowerCase()).filter(Boolean);r.length>0&&(s.tags=r.join(","));}let a=await o.contacts.list(s);if(t.format==="json")m(a,"json");else {let r=a.contacts.map(n=>({ID:n.id,Email:n.email,Name:[n.first_name,n.last_name].filter(Boolean).join(" ")||"-",Subscribed:n.subscribed,Tags:n.tags?.join(", ")||"-",Created:n.created_at}));m(r,"table"),console.log(`
290
290
  Total: ${a.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
291
291
 
292
292
  USAGE
@@ -314,7 +314,7 @@ EXAMPLES
314
314
  emailr contacts get con_abc123 --format json
315
315
 
316
316
  # Pipe JSON to jq for processing
317
- emailr contacts get con_abc123 --format json | jq '.metadata'`).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);}}),i.command("create").description(`Create a new contact
317
+ emailr contacts get con_abc123 --format json | jq '.metadata'`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).contacts.get(t);m(a,e.format);}catch(o){l(o instanceof Error?o.message:"Failed to get contact"),process.exit(1);}}),i.command("create").description(`Create a new contact
318
318
 
319
319
  USAGE
320
320
  emailr contacts create --email <email_address> [options]
@@ -357,7 +357,7 @@ EXAMPLES
357
357
  emailr contacts create --email "user@example.com" --subscribed false
358
358
 
359
359
  # Get JSON output for scripting
360
- 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("--tags <tags>","Comma-separated tags").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o=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);}t.tags&&(r.tags=t.tags.split(",").map(n=>n.trim().toLowerCase()).filter(Boolean));let a=await o.contacts.create(r);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);}}),i.command("update <contact_id>").description(`Update a contact
360
+ 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("--tags <tags>","Comma-separated tags").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=c(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),s={email:t.email};if(t.firstName&&(s.first_name=t.firstName),t.lastName&&(s.last_name=t.lastName),t.subscribed!==void 0&&(s.subscribed=t.subscribed),t.metadata)try{s.metadata=JSON.parse(t.metadata);}catch{l("Invalid JSON for --metadata"),process.exit(1);}t.tags&&(s.tags=t.tags.split(",").map(r=>r.trim().toLowerCase()).filter(Boolean));let a=await o.contacts.create(s);t.format==="json"?m(a,"json"):(p(`Contact created: ${a.id}`),m(a,"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
361
361
 
362
362
  USAGE
363
363
  emailr contacts update <contact_id> [options]
@@ -406,7 +406,7 @@ EXAMPLES
406
406
  emailr contacts update con_abc123 --metadata '{"plan": "enterprise", "upgraded": true}'
407
407
 
408
408
  # Get JSON output
409
- 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("--tags <tags>","Comma-separated tags").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),r=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);}e.tags&&(a.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let n=await r.contacts.update(t,a);e.format==="json"?c(n,"json"):(d(`Contact updated: ${n.id}`),c(n,"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update contact"),process.exit(1);}}),i.command("delete <contact_id>").description(`Delete a contact
409
+ 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("--tags <tags>","Comma-separated tags").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),s=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);}e.tags&&(a.tags=e.tags.split(",").map(n=>n.trim().toLowerCase()).filter(Boolean));let r=await s.contacts.update(t,a);e.format==="json"?m(r,"json"):(p(`Contact updated: ${r.id}`),m(r,"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update contact"),process.exit(1);}}),i.command("delete <contact_id>").description(`Delete a contact
410
410
 
411
411
  USAGE
412
412
  emailr contacts delete <contact_id>
@@ -423,7 +423,7 @@ EXAMPLES
423
423
  emailr contacts delete con_abc123
424
424
 
425
425
  WARNING
426
- 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 _.join(B.homedir(),".config","emailr","templates")}function qe(){let i=pe();v.existsSync(i)||v.mkdirSync(i,{recursive:true});}function W(i){return _.join(pe(),`${i}.html`)}function X(i,t){qe();let e=W(i);v.writeFileSync(e,t,"utf-8");}function ue(i){let t=W(i);return v.existsSync(t)?v.readFileSync(t,"utf-8"):null}function fe(i){let t=W(i);return v.existsSync(t)}var w=null,U=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 Be(i){return `<!DOCTYPE html>
426
+ This action is permanent and cannot be undone.`).action(async t=>{try{let e=c();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).contacts.delete(t),p(`Contact deleted: ${t}`);}catch(e){l(e instanceof Error?e.message:"Failed to delete contact"),process.exit(1);}}),i}function fe(){return _.join(V.homedir(),".config","emailr","templates")}function We(){let i=fe();O.existsSync(i)||O.mkdirSync(i,{recursive:true});}function W(i){return _.join(fe(),`${i}.html`)}function X(i,t){We();let e=W(i);O.writeFileSync(e,t,"utf-8");}function be(i){let t=W(i);return O.existsSync(t)?O.readFileSync(t,"utf-8"):null}function he(i){let t=W(i);return O.existsSync(t)}var v=null,P=null,H=false;function we(i){return i.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}function ze(i){return i===null||i.trim()===""}function Ye(i){return `<!DOCTYPE html>
427
427
  <html lang="en">
428
428
  <head>
429
429
  <meta charset="UTF-8">
@@ -489,14 +489,14 @@ WARNING
489
489
  <div class="icon">\u{1F4ED}</div>
490
490
  <h1>No Content Available</h1>
491
491
  <p>This template exists but has no HTML content to display.</p>
492
- <div class="template-id">${he(i)}</div>
492
+ <div class="template-id">${we(i)}</div>
493
493
  <div class="hint">
494
494
  <p><strong>To add content:</strong></p>
495
495
  <p>Update the template using the CLI with the <code>--html</code> option or provide HTML content when creating the template.</p>
496
496
  </div>
497
497
  </div>
498
498
  </body>
499
- </html>`}function be(i){return `<!DOCTYPE html>
499
+ </html>`}function ge(i){return `<!DOCTYPE html>
500
500
  <html lang="en">
501
501
  <head>
502
502
  <meta charset="UTF-8">
@@ -548,11 +548,11 @@ WARNING
548
548
  <div class="icon">404</div>
549
549
  <h1>Template Not Found</h1>
550
550
  <p>The requested template could not be found in local storage.</p>
551
- <div class="template-id">${he(i)}</div>
551
+ <div class="template-id">${we(i)}</div>
552
552
  <p style="margin-top: 1rem;">Try creating or retrieving the template first using the CLI.</p>
553
553
  </div>
554
554
  </body>
555
- </html>`}function We(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||"/",o=We(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(be(o));return}let r=ue(o);if(r===null){t.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),t.end(be(o));return}if($e(r)){t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(Be(o));return}t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(r);}}var ge={async start(){return H&&U!==null?U:new Promise((i,t)=>{w=Je.createServer(Xe()),w.listen(0,"127.0.0.1",()=>{let e=w.address();e&&typeof e=="object"?(U=e.port,H=true,w.unref(),i(U)):t(new Error("Failed to get server address"));}),w.on("error",e=>{H=false,U=null,w=null,t(new Error(`Failed to start preview server: ${e.message}`));});})},getPort(){return U},isRunning(){return H},async stop(){if(w)return new Promise(i=>{w.close(()=>{w=null,U=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 P=null,z=null,J=null,R=[],ve=`
555
+ </html>`}function Ze(i){let t=i.match(/^\/preview\/([^/]+)$/);return t?t[1]:null}function Qe(){return (i,t)=>{if(i.method!=="GET"){t.writeHead(405,{"Content-Type":"text/plain"}),t.end("Method Not Allowed");return}let e=i.url||"/",o=Ze(e);if(!o){t.writeHead(404,{"Content-Type":"text/plain"}),t.end("Not Found");return}if(!he(o)){t.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),t.end(ge(o));return}let s=be(o);if(s===null){t.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),t.end(ge(o));return}if(ze(s)){t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(Ye(o));return}t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),t.end(s);}}var ye={async start(){return H&&P!==null?P:new Promise((i,t)=>{v=Xe.createServer(Qe()),v.listen(0,"127.0.0.1",()=>{let e=v.address();e&&typeof e=="object"?(P=e.port,H=true,v.unref(),i(P)):t(new Error("Failed to get server address"));}),v.on("error",e=>{H=false,P=null,v=null,t(new Error(`Failed to start preview server: ${e.message}`));});})},getPort(){return P},isRunning(){return H},async stop(){if(v)return new Promise(i=>{v.close(()=>{v=null,P=null,H=false,i();});})}};function Se(){return ye}async function z(i){try{return `http://127.0.0.1:${await ye.start()}/preview/${i}`}catch{return null}}function ve(){v&&v.ref();}var k=null,Y=null,$=null,M=[],Oe=`
556
556
  <script>
557
557
  (function() {
558
558
  const evtSource = new EventSource('/__live-reload');
@@ -566,11 +566,11 @@ WARNING
566
566
  };
567
567
  })();
568
568
  </script>
569
- `;function Ye(i){return i.includes("</body>")?i.replace("</body>",`${ve}</body>`):i+ve}function Ze(){R.forEach(i=>{try{i.write(`data: reload
569
+ `;function at(i){return i.includes("</body>")?i.replace("</body>",`${Oe}</body>`):i+Oe}function ot(){M.forEach(i=>{try{i.write(`data: reload
570
570
 
571
- `);}catch{}});}async function Y(i,t){let e=_.resolve(i);return new Promise((o,r)=>{P=Je.createServer((a,n)=>{if(a.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
571
+ `);}catch{}});}async function Z(i,t){let e=_.resolve(i);return new Promise((o,s)=>{k=Xe.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
572
572
 
573
- `),R.push(n),a.on("close",()=>{R=R.filter(s=>s!==n);});return}if(a.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");}),P.listen(0,"127.0.0.1",()=>{let a=P.address();if(a&&typeof a=="object"){z=a.port;let n=null;J=v.watch(e,s=>{s==="change"&&(n&&clearTimeout(n),n=setTimeout(()=>{Ze(),t?.();},100));}),o(z);}else r(new Error("Failed to get server address"));}),P.on("error",a=>{r(new Error(`Failed to start server: ${a.message}`));});})}async function Z(){if(J&&(J.close(),J=null),R.forEach(i=>{try{i.end();}catch{}}),R=[],P)return new Promise(i=>{P.close(()=>{P=null,z=null,i();});})}async function Te(i){try{let t=i.html_content??"";X(i.id,t);}catch(t){return I(`Could not save template for preview: ${t instanceof Error?t.message:String(t)}`),null}try{let t=await V(i.id);return t===null?(I("Could not start preview server"),null):t}catch(t){return I(`Could not generate preview URL: ${t instanceof Error?t.message:String(t)}`),null}}function Oe(){let i=new Command("templates").description(`Manage email templates
573
+ `),M.push(r),a.on("close",()=>{M=M.filter(n=>n!==r);});return}if(a.method==="GET"){try{let n=O.readFileSync(e,"utf-8"),d=at(n);r.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),r.end(d);}catch(n){r.writeHead(500,{"Content-Type":"text/plain"}),r.end(`Error reading file: ${n instanceof Error?n.message:String(n)}`);}return}r.writeHead(405,{"Content-Type":"text/plain"}),r.end("Method Not Allowed");}),k.listen(0,"127.0.0.1",()=>{let a=k.address();if(a&&typeof a=="object"){Y=a.port;let r=null;$=O.watch(e,n=>{n==="change"&&(r&&clearTimeout(r),r=setTimeout(()=>{ot(),t?.();},100));}),o(Y);}else s(new Error("Failed to get server address"));}),k.on("error",a=>{s(new Error(`Failed to start server: ${a.message}`));});})}async function Q(){if($&&($.close(),$=null),M.forEach(i=>{try{i.end();}catch{}}),M=[],k)return new Promise(i=>{k.close(()=>{k=null,Y=null,i();});})}async function Ee(i){try{let t=i.html_content??"";X(i.id,t);}catch(t){return I(`Could not save template for preview: ${t instanceof Error?t.message:String(t)}`),null}try{let t=await z(i.id);return t===null?(I("Could not start preview server"),null):t}catch(t){return I(`Could not generate preview URL: ${t instanceof Error?t.message:String(t)}`),null}}function _e(){let i=new Command("templates").description(`Manage email templates
574
574
 
575
575
  USAGE
576
576
  emailr templates <subcommand> [options]
@@ -671,7 +671,7 @@ EXAMPLES
671
671
  emailr templates list --page 2 --limit 10
672
672
 
673
673
  # Get JSON output for scripting
674
- emailr templates list --format json`).option("--limit <count>","Number of templates to return","20").option("--page <page_number>","Page number for pagination","1").option("--tags <tags>","Filter by tags (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}),r={limit:parseInt(t.limit,10),page:parseInt(t.page,10)};if(t.tags){let n=t.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);n.length>0&&(r.tags=n.join(","));}let a=await o.templates.list(r);if(t.format==="json")c(a,"json");else {let n=a.map(s=>({ID:s.id,Name:s.name,Subject:s.subject,Variables:s.variables?.join(", ")||"-",Tags:s.tags?.join(", ")||"-",Created:s.created_at}));c(n,"table"),console.log(`
674
+ emailr templates list --format json`).option("--limit <count>","Number of templates to return","20").option("--page <page_number>","Page number for pagination","1").option("--tags <tags>","Filter by tags (comma-separated)").option("--format <format>","Output format: json | table","table").action(async t=>{try{let e=c(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),s={limit:parseInt(t.limit,10),page:parseInt(t.page,10)};if(t.tags){let r=t.tags.split(",").map(n=>n.trim().toLowerCase()).filter(Boolean);r.length>0&&(s.tags=r.join(","));}let a=await o.templates.list(s);if(t.format==="json")m(a,"json");else {let r=a.map(n=>({ID:n.id,Name:n.name,Subject:n.subject,Variables:n.variables?.join(", ")||"-",Tags:n.tags?.join(", ")||"-",Created:n.created_at}));m(r,"table"),console.log(`
675
675
  Total: ${a.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
676
676
 
677
677
  USAGE
@@ -700,7 +700,7 @@ EXAMPLES
700
700
  emailr templates get tpl_abc123 --format json
701
701
 
702
702
  # Pipe JSON to jq for processing
703
- emailr templates get tpl_abc123 --format json | jq '.html_content'`).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),n=await Te({id:a.id,html_content:a.html_content??void 0}),s=a.preview_html?`${o.baseUrl}/preview/${a.id}`:null;if(e.format==="json")c({...a,preview_url:s??n},"json");else {let p={ID:a.id,Name:a.name,Subject:a.subject,Variables:a.variables?.join(", ")||"-",Created:a.created_at};s?p["Preview URL"]=s:n&&(p["Local Preview"]=n),c(p,"table");}}catch(o){l(o instanceof Error?o.message:"Failed to get template"),process.exit(1);}}),i.command("fetch <template_id>").description(`Download template HTML to file or stdout
703
+ emailr templates get tpl_abc123 --format json | jq '.html_content'`).option("--format <format>","Output format: json | table","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).templates.get(t),r=await Ee({id:a.id,html_content:a.html_content??void 0}),n=a.preview_html?`${o.baseUrl}/preview/${a.id}`:null;if(e.format==="json")m({...a,preview_url:n??r},"json");else {let d={ID:a.id,Name:a.name,Subject:a.subject,Variables:a.variables?.join(", ")||"-",Created:a.created_at};n?d["Preview URL"]=n:r&&(d["Local Preview"]=r),m(d,"table");}}catch(o){l(o instanceof Error?o.message:"Failed to get template"),process.exit(1);}}),i.command("fetch <template_id>").description(`Download template HTML to file or stdout
704
704
 
705
705
  USAGE
706
706
  emailr templates fetch <template_id> [options]
@@ -734,8 +734,8 @@ AGENTIC WORKFLOW
734
734
  This is step 1 of the agentic workflow:
735
735
  1. Fetch: emailr templates fetch <id> --output template.html
736
736
  2. Edit locally or with AI assistance
737
- 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 o=m(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).templates.fetch(t,{preview:e.preview??!1});if(a||(e.preview?(l("No preview HTML exists for this template"),console.log(`
738
- 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=_.resolve(e.output);v.writeFileSync(n,a,"utf-8"),d(`HTML saved to: ${n}`);}else console.log(a);}catch(o){l(o instanceof Error?o.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
737
+ 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 o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).templates.fetch(t,{preview:e.preview??!1});if(a||(e.preview?(l("No preview HTML exists for this template"),console.log(`
738
+ 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 r=_.resolve(e.output);O.writeFileSync(r,a,"utf-8"),p(`HTML saved to: ${r}`);}else console.log(a);}catch(o){l(o instanceof Error?o.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
739
739
 
740
740
  USAGE
741
741
  emailr templates push-preview <template_id> --html-file <file_path>
@@ -779,7 +779,7 @@ AGENTIC WORKFLOW
779
779
  3. Push preview: emailr templates push-preview <id> --html-file template.html
780
780
  4. Share the preview URL with your AI agent for feedback
781
781
  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(`
782
- 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 o=m(),r=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a;if(e.htmlFile){let p=_.resolve(e.htmlFile);try{a=v.readFileSync(p,"utf-8");}catch{l(`Failed to read file: ${p}`),process.exit(1);}}else a=e.html;let n=await r.templates.pushPreview(t,a),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(`
782
+ 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 o=c(),s=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a;if(e.htmlFile){let d=_.resolve(e.htmlFile);try{a=O.readFileSync(d,"utf-8");}catch{l(`Failed to read file: ${d}`),process.exit(1);}}else a=e.html;let r=await s.templates.pushPreview(t,a),n="updated";e.format==="json"?m({template_id:t,preview_url:r.preview_url,status:n},"json"):(p("Preview uploaded successfully"),m({"Template ID":t,"Preview URL":r.preview_url,Status:n},"table"),console.log(`
783
783
  Share this URL with your AI agent for feedback.`));}catch(o){l(o instanceof Error?o.message:"Failed to push preview"),process.exit(1);}}),i.command("create").description(`Create a new email template
784
784
 
785
785
  USAGE
@@ -827,7 +827,7 @@ EXAMPLES
827
827
 
828
828
  TIP
829
829
  For live preview while building, use "emailr templates draft" first.
830
- 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("--from-name <name>","Sender display name").option("--reply-to <email_address>","Default reply-to email address").option("--preview-text <text>","Preview text shown in email clients").option("--tags <tags>","Comma-separated tags").option("--format <format>","Output format: json | table","table").action(async t=>{try{let e=m(),o=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.fromName&&(r.from_name=t.fromName),t.replyTo&&(r.reply_to=t.replyTo),t.previewText&&(r.preview_text=t.previewText),t.tags&&(r.tags=t.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let a=await o.templates.create(r),n=await Te({id:a.id,html_content:a.html_content??void 0});if(t.format==="json")c({...a,preview_url:n},"json");else {d(`Template created: ${a.id}`);let s={ID:a.id,Name:a.name,Subject:a.subject,Variables:a.variables?.join(", ")||"-",Tags:a.tags?.join(", ")||"-"};n&&(s["Preview URL"]=n),c(s,"table"),n&&console.log(`
830
+ 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("--from-name <name>","Sender display name").option("--reply-to <email_address>","Default reply-to email address").option("--preview-text <text>","Preview text shown in email clients").option("--tags <tags>","Comma-separated tags").option("--format <format>","Output format: json | table","table").action(async t=>{try{let e=c(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),s={name:t.name,subject:t.subject};if(t.htmlFile){let n=await import('fs');s.html_content=n.readFileSync(t.htmlFile,"utf-8");}else t.html&&(s.html_content=t.html);if(t.textFile){let n=await import('fs');s.text_content=n.readFileSync(t.textFile,"utf-8");}else t.text&&(s.text_content=t.text);t.from&&(s.from_email=t.from),t.fromName&&(s.from_name=t.fromName),t.replyTo&&(s.reply_to=t.replyTo),t.previewText&&(s.preview_text=t.previewText),t.tags&&(s.tags=t.tags.split(",").map(n=>n.trim().toLowerCase()).filter(Boolean));let a=await o.templates.create(s),r=await Ee({id:a.id,html_content:a.html_content??void 0});if(t.format==="json")m({...a,preview_url:r},"json");else {p(`Template created: ${a.id}`);let n={ID:a.id,Name:a.name,Subject:a.subject,Variables:a.variables?.join(", ")||"-",Tags:a.tags?.join(", ")||"-"};r&&(n["Preview URL"]=r),m(n,"table"),r&&console.log(`
831
831
  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
832
832
 
833
833
  USAGE
@@ -882,7 +882,7 @@ AGENTIC WORKFLOW
882
882
  2. Edit locally or with AI assistance
883
883
  3. Push preview: emailr templates push-preview <id> --html-file template.html
884
884
  4. Share URL with AI agent, iterate on feedback
885
- 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("--from-name <name>","New sender display name").option("--reply-to <email_address>","New default reply-to email address").option("--preview-text <text>","New preview text").option("--tags <tags>","Comma-separated tags").option("--format <format>","Output format: json | table","table").action(async(t,e)=>{try{let o=m(),r=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.fromName&&(a.from_name=e.fromName),e.replyTo&&(a.reply_to=e.replyTo),e.previewText&&(a.preview_text=e.previewText),e.tags&&(a.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let n=await r.templates.update(t,a);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(", ")||"-",Tags:n.tags?.join(", ")||"-",Updated:n.updated_at};c(s,"table");}}catch(o){l(o instanceof Error?o.message:"Failed to update template"),process.exit(1);}}),i.command("delete <template_id>").description(`Delete a template
885
+ 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("--from-name <name>","New sender display name").option("--reply-to <email_address>","New default reply-to email address").option("--preview-text <text>","New preview text").option("--tags <tags>","Comma-separated tags").option("--format <format>","Output format: json | table","table").action(async(t,e)=>{try{let o=c(),s=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 n=await import('fs');a.html_content=n.readFileSync(e.htmlFile,"utf-8");}else e.html&&(a.html_content=e.html);if(e.textFile){let n=await import('fs');a.text_content=n.readFileSync(e.textFile,"utf-8");}else e.text&&(a.text_content=e.text);e.from&&(a.from_email=e.from),e.fromName&&(a.from_name=e.fromName),e.replyTo&&(a.reply_to=e.replyTo),e.previewText&&(a.preview_text=e.previewText),e.tags&&(a.tags=e.tags.split(",").map(n=>n.trim().toLowerCase()).filter(Boolean));let r=await s.templates.update(t,a);if(e.format==="json")m(r,"json");else {p(`Template updated: ${r.id}`);let n={ID:r.id,Name:r.name,Subject:r.subject,Variables:r.variables?.join(", ")||"-",Tags:r.tags?.join(", ")||"-",Updated:r.updated_at};m(n,"table");}}catch(o){l(o instanceof Error?o.message:"Failed to update template"),process.exit(1);}}),i.command("delete <template_id>").description(`Delete a template
886
886
 
887
887
  USAGE
888
888
  emailr templates delete <template_id>
@@ -899,7 +899,7 @@ EXAMPLES
899
899
  emailr templates delete tpl_abc123
900
900
 
901
901
  WARNING
902
- 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
902
+ This action is permanent and cannot be undone.`).action(async t=>{try{let e=c();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).templates.delete(t),p(`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
903
903
 
904
904
  USAGE
905
905
  emailr templates preview <template_id> [options]
@@ -928,17 +928,17 @@ EXAMPLES
928
928
 
929
929
  SEE ALSO
930
930
  emailr templates edit Live editing with hot-reload
931
- 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 o=m(),r=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl});console.log(`Fetching template ${t}...`);let a=await r.templates.get(t),n=a.html_content??"";if(X(a.id,n),e.foreground){let f=await V(a.id);if(f||(l("Failed to start preview server"),process.exit(1)),ye(),console.log(`
932
- Template: ${a.name}`),console.log(`Preview URL: ${f}`),e.open!==!1)try{await(await import('open')).default(f),console.log(`
931
+ 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 o=c(),s=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl});console.log(`Fetching template ${t}...`);let a=await s.templates.get(t),r=a.html_content??"";if(X(a.id,r),e.foreground){let g=await z(a.id);if(g||(l("Failed to start preview server"),process.exit(1)),ve(),console.log(`
932
+ Template: ${a.name}`),console.log(`Preview URL: ${g}`),e.open!==!1)try{await(await import('open')).default(g),console.log(`
933
933
  Browser opened.`);}catch{console.log(`
934
934
  Could not open browser automatically. Open the URL above manually.`);}process.on("SIGINT",async()=>{console.log(`
935
935
 
936
- Stopping preview server...`),await we().stop(),console.log("Done."),process.exit(0);});return}let s=_.join(process.cwd(),`.template-preview-${a.id}.html`);v.writeFileSync(s,n,"utf-8");let{spawn:p}=await import('child_process'),b=await import('os'),T=_.join(b.tmpdir(),"emailr-preview.pid");try{let f=v.readFileSync(T,"utf-8").trim();process.kill(parseInt(f,10),"SIGTERM");}catch{}let h=`
936
+ Stopping preview server...`),await Se().stop(),console.log("Done."),process.exit(0);});return}let n=_.join(process.cwd(),`.template-preview-${a.id}.html`);O.writeFileSync(n,r,"utf-8");let{spawn:d}=await import('child_process'),h=await import('os'),y=_.join(h.tmpdir(),"emailr-preview.pid");try{let g=O.readFileSync(y,"utf-8").trim();process.kill(parseInt(g,10),"SIGTERM");}catch{}let u=`
937
937
  const http = require('http');
938
938
  const fs = require('fs');
939
939
  const path = require('path');
940
940
  const os = require('os');
941
- const filePath = ${JSON.stringify(s)};
941
+ const filePath = ${JSON.stringify(n)};
942
942
  const pidFile = path.join(os.tmpdir(), 'emailr-preview.pid');
943
943
  const portFile = path.join(os.tmpdir(), 'emailr-preview.port');
944
944
  const server = http.createServer((req, res) => {
@@ -958,9 +958,9 @@ Stopping preview server...`),await we().stop(),console.log("Done."),process.exit
958
958
  console.log('PORT:' + port);
959
959
  });
960
960
  process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); fs.unlinkSync(filePath); } catch {} process.exit(0); });
961
- `,y=p("node",["-e",h],{detached:!0,stdio:["ignore","pipe","ignore"]}),O="";await new Promise(f=>{y.stdout?.on("data",N=>{let q=N.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(`
962
- Template: ${a.name}`),console.log(`Preview URL: ${D}`),console.log(`
963
- 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);}}),i.command("edit <template_id>").description(`Start a live editing session for a template with hot-reload
961
+ `,b=d("node",["-e",u],{detached:!0,stdio:["ignore","pipe","ignore"]}),w="";await new Promise(g=>{b.stdout?.on("data",x=>{let J=x.toString().match(/PORT:(\d+)/);J&&(w=J[1],g());}),setTimeout(g,3e3);}),b.unref();let j=`http://127.0.0.1:${w}/`;if(console.log(`
962
+ Template: ${a.name}`),console.log(`Preview URL: ${j}`),console.log(`
963
+ To stop: emailr templates stop-preview`),e.open!==!1)try{await(await import('open')).default(j);}catch{}process.exit(0);}catch(o){l(o instanceof Error?o.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
964
964
 
965
965
  USAGE
966
966
  emailr templates edit <template_id> [options]
@@ -1000,12 +1000,12 @@ EXAMPLES
1000
1000
  SEE ALSO
1001
1001
  emailr templates draft Draft a new template with live preview
1002
1002
  emailr templates update Save changes to the template
1003
- 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 o=m(),r=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a=_.resolve(e.file);console.log(`Fetching template ${t}...`);let n=await r.templates.get(t),s=n.html_content??"";if(v.writeFileSync(a,s,"utf-8"),console.log(`Template saved to: ${a}`),e.foreground){let N=`http://127.0.0.1:${await Y(a,()=>{console.log("File changed - browser refreshed");})}/`;if(console.log(`
1004
- Template: ${n.name}`),console.log(`Template ID: ${n.id}`),console.log(`Live Preview: ${N}`),console.log(`File: ${a}`),console.log(`
1005
- 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(N);}catch{}process.on("SIGINT",async()=>{console.log(`
1003
+ 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 o=c(),s=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a=_.resolve(e.file);console.log(`Fetching template ${t}...`);let r=await s.templates.get(t),n=r.html_content??"";if(O.writeFileSync(a,n,"utf-8"),console.log(`Template saved to: ${a}`),e.foreground){let x=`http://127.0.0.1:${await Z(a,()=>{console.log("File changed - browser refreshed");})}/`;if(console.log(`
1004
+ Template: ${r.name}`),console.log(`Template ID: ${r.id}`),console.log(`Live Preview: ${x}`),console.log(`File: ${a}`),console.log(`
1005
+ 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(x);}catch{}process.on("SIGINT",async()=>{console.log(`
1006
1006
 
1007
- Stopping live preview server...`),await Z(),console.log("Done."),console.log(`
1008
- 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=_.join(b.tmpdir(),"emailr-preview.pid");try{let f=v.readFileSync(T,"utf-8").trim();process.kill(parseInt(f,10),"SIGTERM");}catch{}let h=`
1007
+ Stopping live preview server...`),await Q(),console.log("Done."),console.log(`
1008
+ To save changes: emailr templates update ${t} --html-file ${e.file}`),process.exit(0);});return}let{spawn:d}=await import('child_process'),h=await import('os'),y=_.join(h.tmpdir(),"emailr-preview.pid");try{let g=O.readFileSync(y,"utf-8").trim();process.kill(parseInt(g,10),"SIGTERM");}catch{}let u=`
1009
1009
  const http = require('http');
1010
1010
  const fs = require('fs');
1011
1011
  const path = require('path');
@@ -1045,10 +1045,10 @@ To save changes: emailr templates update ${t} --html-file ${e.file}`),process.ex
1045
1045
  });
1046
1046
  });
1047
1047
  process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); } catch {} process.exit(0); });
1048
- `,y=p("node",["-e",h],{detached:!0,stdio:["ignore","pipe","ignore"]}),O="";await new Promise(f=>{y.stdout?.on("data",N=>{let ie=N.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(`
1049
- Template: ${n.name}`),console.log(`Template ID: ${n.id}`),console.log(`Live Preview: ${D}`),console.log(`File: ${a}`),console.log(`
1048
+ `,b=d("node",["-e",u],{detached:!0,stdio:["ignore","pipe","ignore"]}),w="";await new Promise(g=>{b.stdout?.on("data",x=>{let ne=x.toString().match(/PORT:(\d+)/);ne&&(w=ne[1],g());}),setTimeout(g,3e3);}),b.unref();let j=`http://127.0.0.1:${w}/`;if(console.log(`
1049
+ Template: ${r.name}`),console.log(`Template ID: ${r.id}`),console.log(`Live Preview: ${j}`),console.log(`File: ${a}`),console.log(`
1050
1050
  Edit the file and see live updates in the browser.`),console.log(`
1051
- 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);}}),i.command("draft").description(`Start a live drafting session for a new template with hot-reload
1051
+ 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(j);}catch{}process.exit(0);}catch(o){l(o instanceof Error?o.message:"Failed to edit template"),process.exit(1);}}),i.command("draft").description(`Start a live drafting session for a new template with hot-reload
1052
1052
 
1053
1053
  USAGE
1054
1054
  emailr templates draft [options]
@@ -1125,13 +1125,13 @@ SEE ALSO
1125
1125
  <p><a href="{{unsubscribe_link}}">Unsubscribe</a></p>
1126
1126
  </div>
1127
1127
  </body>
1128
- </html>`,v.writeFileSync(e,o,"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(`
1129
- Live Preview: ${y}`),console.log(`File: ${e}`),console.log(`
1128
+ </html>`,O.writeFileSync(e,o,"utf-8"),console.log(`Template draft created: ${e}`),t.foreground){let b=`http://127.0.0.1:${await Z(e,()=>{console.log("File changed - browser refreshed");})}/`;if(console.log(`
1129
+ Live Preview: ${b}`),console.log(`File: ${e}`),console.log(`
1130
1130
  Watching for changes... Edit the file and see live updates.`),console.log(`
1131
- 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(`
1131
+ 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(b);}catch{}process.on("SIGINT",async()=>{console.log(`
1132
1132
 
1133
- Stopping live preview server...`),await Z(),console.log("Done."),console.log(`
1134
- 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'),a=await import('os'),n=_.join(a.tmpdir(),"emailr-preview.pid");try{let h=v.readFileSync(n,"utf-8").trim();process.kill(parseInt(h,10),"SIGTERM");}catch{}let s=`
1133
+ Stopping live preview server...`),await Q(),console.log("Done."),console.log(`
1134
+ To create template: emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${t.file}`),process.exit(0);});return}let{spawn:s}=await import('child_process'),a=await import('os'),r=_.join(a.tmpdir(),"emailr-preview.pid");try{let u=O.readFileSync(r,"utf-8").trim();process.kill(parseInt(u,10),"SIGTERM");}catch{}let n=`
1135
1135
  const http = require('http');
1136
1136
  const fs = require('fs');
1137
1137
  const path = require('path');
@@ -1171,10 +1171,10 @@ To create template: emailr templates create --name "Template Name" --subject "Em
1171
1171
  });
1172
1172
  });
1173
1173
  process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); } catch {} process.exit(0); });
1174
- `,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(`
1175
- Live Preview: ${T}`),console.log(`File: ${e}`),console.log(`
1174
+ `,d=s("node",["-e",n],{detached:!0,stdio:["ignore","pipe","ignore"]}),h="";await new Promise(u=>{d.stdout?.on("data",b=>{let w=b.toString().match(/PORT:(\d+)/);w&&(h=w[1],u());}),setTimeout(u,3e3);}),d.unref();let y=`http://127.0.0.1:${h}/`;if(console.log(`
1175
+ Live Preview: ${y}`),console.log(`File: ${e}`),console.log(`
1176
1176
  Edit the file and see live updates in the browser.`),console.log(`
1177
- 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
1177
+ 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(y);}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
1178
1178
 
1179
1179
  USAGE
1180
1180
  emailr templates stop-preview
@@ -1191,7 +1191,7 @@ EXAMPLES
1191
1191
  SEE ALSO
1192
1192
  emailr templates edit Start live editing session
1193
1193
  emailr templates draft Start live drafting session
1194
- emailr templates preview Preview a template in browser`).action(async()=>{let t=await import('os'),e=_.join(t.tmpdir(),"emailr-preview.pid");try{let o=v.readFileSync(e,"utf-8").trim();process.kill(parseInt(o,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
1194
+ emailr templates preview Preview a template in browser`).action(async()=>{let t=await import('os'),e=_.join(t.tmpdir(),"emailr-preview.pid");try{let o=O.readFileSync(e,"utf-8").trim();process.kill(parseInt(o,10),"SIGTERM"),O.unlinkSync(e),p("Preview server stopped");}catch{p("No preview server running");}}),i}function je(){let i=new Command("domains").description(`Manage sending domains
1195
1195
 
1196
1196
  USAGE
1197
1197
  emailr domains <subcommand> [options]
@@ -1299,7 +1299,7 @@ EXAMPLES
1299
1299
  emailr domains list --format json | jq '.[] | select(.status == "verified")'
1300
1300
 
1301
1301
  # Count domains
1302
- 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 a=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(a,"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
1302
+ emailr domains list --format json | jq 'length'`).option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=c(),s=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).domains.list();if(t.format==="json")m(s,"json");else {let a=s.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}));m(a,"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
1303
1303
 
1304
1304
  USAGE
1305
1305
  emailr domains get <domain_id> [options]
@@ -1326,7 +1326,7 @@ EXAMPLES
1326
1326
  emailr domains get dom_abc123 --format json
1327
1327
 
1328
1328
  # Extract DNS records
1329
- emailr domains get dom_abc123 --format json | jq '.dns_records'`).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);}}),i.command("add <domain_name>").description(`Add a new domain
1329
+ emailr domains get dom_abc123 --format json | jq '.dns_records'`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).domains.get(t);m(a,e.format);}catch(o){l(o instanceof Error?o.message:"Failed to get domain"),process.exit(1);}}),i.command("add <domain_name>").description(`Add a new domain
1330
1330
 
1331
1331
  USAGE
1332
1332
  emailr domains add <domain_name> [options]
@@ -1379,7 +1379,7 @@ NEXT STEPS
1379
1379
  1. Copy the DNS records shown in the output
1380
1380
  2. Add them to your DNS provider (Cloudflare, Route53, etc.)
1381
1381
  3. Wait for DNS propagation (up to 48 hours)
1382
- 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 o=m(),r=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={domain:t};e.receivingSubdomain&&(a.receivingSubdomain=e.receivingSubdomain);let n=await r.domains.add(a);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(o){l(o instanceof Error?o.message:"Failed to add domain"),process.exit(1);}}),i.command("verify <domain_id>").description(`Verify a domain
1382
+ 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 o=c(),s=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={domain:t};e.receivingSubdomain&&(a.receivingSubdomain=e.receivingSubdomain);let r=await s.domains.add(a);if(e.format==="json")m(r,"json");else {if(p(`Domain added: ${r.domain}`),f("Add the following DNS records to verify your domain:"),console.log(""),r.dns_records){let n=[{Type:"DKIM",...ee(r.dns_records.dkim)},{Type:"SPF",...ee(r.dns_records.spf)},{Type:"DMARC",...ee(r.dns_records.dmarc)}];m(n,"table");}console.log(""),f(`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);}}),i.command("verify <domain_id>").description(`Verify a domain
1383
1383
 
1384
1384
  USAGE
1385
1385
  emailr domains verify <domain_id> [options]
@@ -1417,7 +1417,7 @@ TROUBLESHOOTING
1417
1417
  1. Run 'check-dns' to see which records are missing
1418
1418
  2. Verify records are correctly configured with your DNS provider
1419
1419
  3. Wait for DNS propagation (can take up to 48 hours)
1420
- 4. Try verification again`).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!"):(u(`Domain status: ${a.status}`),a.dkim_status&&u(`DKIM status: ${a.dkim_status}`));}catch(o){l(o instanceof Error?o.message:"Failed to verify domain"),process.exit(1);}}),i.command("check-dns <domain_id>").description(`Check DNS records for a domain
1420
+ 4. Try verification again`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).domains.verify(t);e.format==="json"?m(a,"json"):a.verified?p("Domain verified successfully!"):(f(`Domain status: ${a.status}`),a.dkim_status&&f(`DKIM status: ${a.dkim_status}`));}catch(o){l(o instanceof Error?o.message:"Failed to verify domain"),process.exit(1);}}),i.command("check-dns <domain_id>").description(`Check DNS records for a domain
1421
1421
 
1422
1422
  USAGE
1423
1423
  emailr domains check-dns <domain_id> [options]
@@ -1464,7 +1464,7 @@ COMMON ISSUES
1464
1464
 
1465
1465
  Multiple records found:
1466
1466
  - Remove duplicate TXT records
1467
- - Keep only the Emailr-specific record`).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 n=Object.entries(a).map(([s,p])=>({Record:s,Verified:p.verified,Expected:p.expected||"-",Found:p.found?.join(", ")||"-"}));c(n,"table");}}catch(o){l(o instanceof Error?o.message:"Failed to check DNS"),process.exit(1);}}),i.command("delete <domain_id>").description(`Delete a domain
1467
+ - Keep only the Emailr-specific record`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).domains.checkDns(t);if(e.format==="json")m(a,"json");else {let r=Object.entries(a).map(([n,d])=>({Record:n,Verified:d.verified,Expected:d.expected||"-",Found:d.found?.join(", ")||"-"}));m(r,"table");}}catch(o){l(o instanceof Error?o.message:"Failed to check DNS"),process.exit(1);}}),i.command("delete <domain_id>").description(`Delete a domain
1468
1468
 
1469
1469
  USAGE
1470
1470
  emailr domains delete <domain_id> [options]
@@ -1494,7 +1494,7 @@ WARNING
1494
1494
  This action is permanent and cannot be undone.
1495
1495
  - Emails from this domain will fail to send
1496
1496
  - DNS records can be removed from your DNS provider
1497
- - 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 _e(){let i=new Command("config").description(`Manage CLI configuration
1497
+ - Re-add the domain if you need to restore functionality`).option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=c();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).domains.delete(t),p(`Domain deleted: ${t}`);}catch(e){l(e instanceof Error?e.message:"Failed to delete domain"),process.exit(1);}}),i}function ee(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 xe(){let i=new Command("config").description(`Manage CLI configuration
1498
1498
 
1499
1499
  USAGE
1500
1500
  emailr config <subcommand> [options]
@@ -1587,7 +1587,7 @@ EXAMPLES
1587
1587
 
1588
1588
  NOTE
1589
1589
  Environment variables (EMAILR_API_KEY, EMAILR_BASE_URL) take precedence
1590
- over config file values at runtime.`).action(async(t,e)=>{try{let o=["api-key","base-url","format"],r=t.toLowerCase();o.includes(r)||(l(`Invalid config key: ${t}`),u(`Valid keys: ${o.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: ${j()}`);}catch(o){l(o instanceof Error?o.message:"Failed to save configuration"),process.exit(1);}}),i.command("get <key>").description(`Get a configuration value
1590
+ over config file values at runtime.`).action(async(t,e)=>{try{let o=["api-key","base-url","format"],s=t.toLowerCase();o.includes(s)||(l(`Invalid config key: ${t}`),f(`Valid keys: ${o.join(", ")}`),process.exit(1));let r={"api-key":"apiKey","base-url":"baseUrl",format:"format"}[s];s==="format"&&!["json","table"].includes(e)&&(l(`Invalid format value: ${e}`),f("Valid formats: json, table"),process.exit(1)),G({[r]:e}),p(`Configuration saved: ${t} = ${s==="api-key"?"***":e}`),f(`Config file: ${N()}`);}catch(o){l(o instanceof Error?o.message:"Failed to save configuration"),process.exit(1);}}),i.command("get <key>").description(`Get a configuration value
1591
1591
 
1592
1592
  USAGE
1593
1593
  emailr config get <key>
@@ -1615,7 +1615,7 @@ EXAMPLES
1615
1615
  emailr config get format
1616
1616
 
1617
1617
  SEE ALSO
1618
- emailr config list Show all configuration values`).action(async t=>{try{let e=["api-key","base-url","format"],o=t.toLowerCase();e.includes(o)||(l(`Invalid config key: ${t}`),u(`Valid keys: ${e.join(", ")}`),process.exit(1));let a={"api-key":"apiKey","base-url":"baseUrl",format:"format"}[o],n=ne(a);n?console.log(o==="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
1618
+ emailr config list Show all configuration values`).action(async t=>{try{let e=["api-key","base-url","format"],o=t.toLowerCase();e.includes(o)||(l(`Invalid config key: ${t}`),f(`Valid keys: ${e.join(", ")}`),process.exit(1));let a={"api-key":"apiKey","base-url":"baseUrl",format:"format"}[o],r=se(a);r?console.log(o==="api-key"?r.substring(0,8)+"..."+r.substring(r.length-4):r):f(`${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
1619
1619
 
1620
1620
  USAGE
1621
1621
  emailr config list [options]
@@ -1643,7 +1643,7 @@ EXAMPLES
1643
1643
 
1644
1644
  SEE ALSO
1645
1645
  emailr config get Get a single configuration value
1646
- emailr config path Show config file location`).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 r=Object.entries(o).map(([a,n])=>({Key:a,Value:n}));c(r,"table");}console.log(""),u(`Config file: ${j()}`);}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
1646
+ emailr config path Show config file location`).option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=c(),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")m(o,"json");else {let s=Object.entries(o).map(([a,r])=>({Key:a,Value:r}));m(s,"table");}console.log(""),f(`Config file: ${N()}`);}catch(e){e instanceof Error&&e.message.includes("No API key configured")?(f("No configuration found."),f("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
1647
1647
 
1648
1648
  USAGE
1649
1649
  emailr config path
@@ -1670,7 +1670,7 @@ EXAMPLES
1670
1670
  open $(emailr config path)
1671
1671
 
1672
1672
  # View config file contents
1673
- cat $(emailr config path)`).action(()=>{console.log(j());}),i.command("init").description(`Initialize configuration interactively
1673
+ cat $(emailr config path)`).action(()=>{console.log(N());}),i.command("init").description(`Initialize configuration interactively
1674
1674
 
1675
1675
  USAGE
1676
1676
  emailr config init [options]
@@ -1703,7 +1703,7 @@ ALTERNATIVE: ENVIRONMENT VARIABLES
1703
1703
 
1704
1704
  SEE ALSO
1705
1705
  emailr config set Set individual configuration values
1706
- 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: ${j()}`)):(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 Ne(){let i=new Command("broadcasts").description(`Manage broadcast campaigns
1706
+ 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}),p("Configuration initialized!"),f(`Config file: ${N()}`)):(f("Initialize your Emailr CLI configuration:"),console.log(""),f("Run with --api-key flag:"),console.log(" emailr config init --api-key <your-api-key>"),console.log(""),f("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 Ne(){let i=new Command("broadcasts").description(`Manage broadcast campaigns
1707
1707
 
1708
1708
  USAGE
1709
1709
  emailr broadcasts <subcommand> [options]
@@ -1853,7 +1853,7 @@ EXAMPLES
1853
1853
  emailr broadcasts list --format json
1854
1854
 
1855
1855
  # Pipe JSON to jq for processing
1856
- 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("--tags <tags>","Filter by tags (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}),r={status:t.status,limit:parseInt(t.limit)};if(t.tags){let n=t.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);n.length>0&&(r.tags=n.join(","));}let a=await o.broadcasts.list(r);if(t.format==="json")c(a,"json");else {if(a.length===0){console.log("No broadcasts found.");return}let n=a.map(s=>({ID:s.id,Name:s.name,Subject:s.subject.substring(0,30)+(s.subject.length>30?"...":""),Status:s.status,Tags:s.tags?.join(", ")||"-",Recipients:s.total_recipients||0,Sent:s.sent_count||0,Created:new Date(s.created_at).toLocaleDateString()}));c(n,"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
1856
+ 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("--tags <tags>","Filter by tags (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=c(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),s={status:t.status,limit:parseInt(t.limit)};if(t.tags){let r=t.tags.split(",").map(n=>n.trim().toLowerCase()).filter(Boolean);r.length>0&&(s.tags=r.join(","));}let a=await o.broadcasts.list(s);if(t.format==="json")m(a,"json");else {if(a.length===0){console.log("No broadcasts found.");return}let r=a.map(n=>({ID:n.id,Name:n.name,Subject:n.subject.substring(0,30)+(n.subject.length>30?"...":""),Status:n.status,Tags:n.tags?.join(", ")||"-",Recipients:n.total_recipients||0,Sent:n.sent_count||0,Created:new Date(n.created_at).toLocaleDateString()}));m(r,"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
1857
1857
 
1858
1858
  USAGE
1859
1859
  emailr broadcasts get <broadcast_id> [options]
@@ -1888,7 +1888,7 @@ EXAMPLES
1888
1888
  emailr broadcasts get brd_abc123 --format json
1889
1889
 
1890
1890
  # Pipe JSON to jq for processing
1891
- 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 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,"From Name":a.from_name||"-",Status:a.status,Tags:a.tags?.join(", ")||"-","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);}}),i.command("create").description(`Create a new broadcast
1891
+ 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 o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.get(t);e.format==="json"?m(a,"json"):m({ID:a.id,Name:a.name,Subject:a.subject,"From Email":a.from_email,"From Name":a.from_name||"-",Status:a.status,Tags:a.tags?.join(", ")||"-","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);}}),i.command("create").description(`Create a new broadcast
1892
1892
 
1893
1893
  USAGE
1894
1894
  emailr broadcasts create --name <name> --subject <subject> --from <email> [options]
@@ -1902,6 +1902,8 @@ OPTIONS
1902
1902
  --subject <subject_line> Email subject line (required)
1903
1903
  --from <email_address> Sender email address (required)
1904
1904
  --from-name <name> Sender display name (e.g. "Acme Inc")
1905
+ --reply-to <email> Reply-To email address
1906
+ --preview-text <text> Preview text (preheader) shown in email clients
1905
1907
  --template <template_id> Template ID to use for content
1906
1908
  --segment <segment_id> Segment ID to target recipients
1907
1909
  --html <html_content> Inline HTML content (alternative to --template)
@@ -1966,7 +1968,7 @@ EXAMPLES
1966
1968
 
1967
1969
  SEE ALSO
1968
1970
  emailr templates Create and manage email templates
1969
- emailr segments Create and manage contact segments`).requiredOption("--name <name>","Broadcast name").requiredOption("--subject <subject>","Email subject").requiredOption("--from <email>","Sender email address").option("--from-name <name>","Sender display name").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("--tags <tags>","Comma-separated tags").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r={name:t.name,subject:t.subject,from_email:t.from,from_name:t.fromName,template_id:t.template,segment_id:t.segment,html_content:t.html,text_content:t.text,scheduled_at:t.schedule};t.tags&&(r.tags=t.tags.split(",").map(n=>n.trim().toLowerCase()).filter(Boolean));let a=await o.broadcasts.create(r);t.format==="json"?c(a,"json"):(d("Broadcast created successfully!"),c({ID:a.id,Name:a.name,Status:a.status,Tags:a.tags?.join(", ")||"-","Scheduled At":a.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
1971
+ emailr segments Create and manage contact segments`).requiredOption("--name <name>","Broadcast name").requiredOption("--subject <subject>","Email subject").requiredOption("--from <email>","Sender email address").option("--from-name <name>","Sender display name").option("--reply-to <email>","Reply-To email address").option("--preview-text <text>","Preview text (preheader)").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("--tags <tags>","Comma-separated tags").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=c(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),s={name:t.name,subject:t.subject,from_email:t.from,from_name:t.fromName,reply_to:t.replyTo,preview_text:t.previewText,template_id:t.template,segment_id:t.segment,html_content:t.html,text_content:t.text,scheduled_at:t.schedule};t.tags&&(s.tags=t.tags.split(",").map(r=>r.trim().toLowerCase()).filter(Boolean));let a=await o.broadcasts.create(s);t.format==="json"?m(a,"json"):(p("Broadcast created successfully!"),m({ID:a.id,Name:a.name,Status:a.status,Tags:a.tags?.join(", ")||"-","Scheduled At":a.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
1970
1972
 
1971
1973
  USAGE
1972
1974
  emailr broadcasts send <broadcast_id> [options]
@@ -1999,7 +2001,7 @@ EXAMPLES
1999
2001
  NOTE
2000
2002
  Sending is asynchronous. The command returns when sending starts,
2001
2003
  not when all emails are delivered. Use 'emailr broadcasts get' to
2002
- check delivery progress.`).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);}}),i.command("schedule <broadcast_id>").description(`Schedule a broadcast for future delivery
2004
+ check delivery progress.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.send(t);e.format==="json"?m(a,"json"):(p("Broadcast sent successfully!"),m({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);}}),i.command("schedule <broadcast_id>").description(`Schedule a broadcast for future delivery
2003
2005
 
2004
2006
  USAGE
2005
2007
  emailr broadcasts schedule <broadcast_id> --at <datetime> [options]
@@ -2049,7 +2051,7 @@ EXAMPLES
2049
2051
  emailr broadcasts schedule brd_abc123 --at "2024-12-25T10:00:00Z" --format json
2050
2052
 
2051
2053
  SEE ALSO
2052
- 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 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);}}),i.command("cancel <broadcast_id>").description(`Cancel a scheduled broadcast
2054
+ 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 o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.schedule(t,e.at);e.format==="json"?m(a,"json"):(p("Broadcast scheduled successfully!"),m({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);}}),i.command("cancel <broadcast_id>").description(`Cancel a scheduled broadcast
2053
2055
 
2054
2056
  USAGE
2055
2057
  emailr broadcasts cancel <broadcast_id> [options]
@@ -2077,7 +2079,7 @@ EXAMPLES
2077
2079
  emailr broadcasts cancel brd_abc123 --format json
2078
2080
 
2079
2081
  NOTE
2080
- Broadcasts that are already 'sending' or 'sent' cannot be cancelled.`).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);}}),i.command("update <broadcast_id>").description(`Update a broadcast
2082
+ Broadcasts that are already 'sending' or 'sent' cannot be cancelled.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.cancel(t);e.format==="json"?m(a,"json"):p("Broadcast cancelled successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to cancel broadcast"),process.exit(1);}}),i.command("update <broadcast_id>").description(`Update a broadcast
2081
2083
 
2082
2084
  USAGE
2083
2085
  emailr broadcasts update <broadcast_id> [options]
@@ -2094,6 +2096,8 @@ OPTIONS
2094
2096
  --subject <subject> Update email subject line
2095
2097
  --from <email> Update sender email address
2096
2098
  --from-name <name> Update sender display name
2099
+ --reply-to <email> Update Reply-To email address
2100
+ --preview-text <text> Update preview text (preheader)
2097
2101
  --template <id> Update template ID (use "none" to clear)
2098
2102
  --segment <id> Update segment ID (use "none" to clear)
2099
2103
  --topic <id> Update topic ID (use "none" to clear)
@@ -2103,6 +2107,10 @@ OPTIONS
2103
2107
  --tags <tags> Update tags (comma-separated, replaces existing)
2104
2108
  --format <format> Output format: json | table (default: table)
2105
2109
 
2110
+ OUTPUT FORMATS
2111
+ --format json Full updated broadcast object with all fields
2112
+ --format table Summary table with ID, Name, Subject, Status, Tags, Scheduled At
2113
+
2106
2114
  EXAMPLES
2107
2115
  # Update broadcast name and subject
2108
2116
  emailr broadcasts update brd_abc123 --name "New Name" --subject "New Subject"
@@ -2123,7 +2131,7 @@ EXAMPLES
2123
2131
  emailr broadcasts update brd_abc123 --name "Updated" --format json
2124
2132
 
2125
2133
  NOTE
2126
- Only broadcasts in 'draft' or 'scheduled' status can be updated.`).option("--name <name>","Broadcast name").option("--subject <subject>","Email subject").option("--from <email>","Sender email address").option("--from-name <name>","Sender display name").option("--template <id>",'Template ID (use "none" to clear)').option("--segment <id>",'Segment ID (use "none" to clear)').option("--topic <id>",'Topic ID (use "none" to clear)').option("--html <html>","HTML content").option("--text <text>","Plain text content").option("--schedule <datetime>",'Schedule time ISO 8601 (use "none" to clear)').option("--tags <tags>","Comma-separated tags (replaces existing)").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),r=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={};e.name&&(a.name=e.name),e.subject&&(a.subject=e.subject),e.from&&(a.from_email=e.from),e.fromName&&(a.from_name=e.fromName),e.html&&(a.html_content=e.html),e.text&&(a.text_content=e.text),e.template&&(a.template_id=e.template==="none"?null:e.template),e.segment&&(a.segment_id=e.segment==="none"?null:e.segment),e.topic&&(a.topic_id=e.topic==="none"?null:e.topic),e.schedule&&(a.scheduled_at=e.schedule==="none"?null:e.schedule),e.tags&&(a.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean)),Object.keys(a).length===0&&(l("No update fields specified. Use --help to see available options."),process.exit(1));let n=await r.broadcasts.update(t,a);e.format==="json"?c(n,"json"):(d("Broadcast updated successfully!"),c({ID:n.id,Name:n.name,Subject:n.subject,Status:n.status,Tags:n.tags?.join(", ")||"-","Scheduled At":n.scheduled_at||"Not scheduled"},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update broadcast"),process.exit(1);}}),i.command("delete <broadcast_id>").description(`Delete a broadcast
2134
+ Only broadcasts in 'draft' or 'scheduled' status can be updated.`).option("--name <name>","Broadcast name").option("--subject <subject>","Email subject").option("--from <email>","Sender email address").option("--from-name <name>","Sender display name").option("--reply-to <email>","Reply-To email address").option("--preview-text <text>","Preview text (preheader)").option("--template <id>",'Template ID (use "none" to clear)').option("--segment <id>",'Segment ID (use "none" to clear)').option("--topic <id>",'Topic ID (use "none" to clear)').option("--html <html>","HTML content").option("--text <text>","Plain text content").option("--schedule <datetime>",'Schedule time ISO 8601 (use "none" to clear)').option("--tags <tags>","Comma-separated tags (replaces existing)").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),s=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a={};e.name&&(a.name=e.name),e.subject&&(a.subject=e.subject),e.from&&(a.from_email=e.from),e.fromName&&(a.from_name=e.fromName),e.replyTo&&(a.reply_to=e.replyTo),e.previewText&&(a.preview_text=e.previewText),e.html&&(a.html_content=e.html),e.text&&(a.text_content=e.text),e.template&&(a.template_id=e.template==="none"?null:e.template),e.segment&&(a.segment_id=e.segment==="none"?null:e.segment),e.topic&&(a.topic_id=e.topic==="none"?null:e.topic),e.schedule&&(a.scheduled_at=e.schedule==="none"?null:e.schedule),e.tags&&(a.tags=e.tags.split(",").map(n=>n.trim().toLowerCase()).filter(Boolean)),Object.keys(a).length===0&&(l("No update fields specified. Use --help to see available options."),process.exit(1));let r=await s.broadcasts.update(t,a);e.format==="json"?m(r,"json"):(p("Broadcast updated successfully!"),m({ID:r.id,Name:r.name,Subject:r.subject,Status:r.status,Tags:r.tags?.join(", ")||"-","Scheduled At":r.scheduled_at||"Not scheduled"},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update broadcast"),process.exit(1);}}),i.command("delete <broadcast_id>").description(`Delete a broadcast
2127
2135
 
2128
2136
  USAGE
2129
2137
  emailr broadcasts delete <broadcast_id> [options]
@@ -2151,7 +2159,7 @@ EXAMPLES
2151
2159
 
2152
2160
  WARNING
2153
2161
  This action is permanent and cannot be undone.
2154
- All delivery statistics will be lost.`).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);}}),i}function je(){let i=new Command("webhooks").description(`Manage webhooks
2162
+ All delivery statistics will be lost.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).broadcasts.delete(t);e.format==="json"?m(a,"json"):p("Broadcast deleted successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to delete broadcast"),process.exit(1);}}),i}function Ie(){let i=new Command("webhooks").description(`Manage webhooks
2155
2163
 
2156
2164
  USAGE
2157
2165
  emailr webhooks <subcommand> [options]
@@ -2304,7 +2312,7 @@ EXAMPLES
2304
2312
  emailr webhooks list --format json | jq '.[] | select(.active == true)'
2305
2313
 
2306
2314
  # Count webhooks
2307
- 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 a=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(a,"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
2315
+ emailr webhooks list --format json | jq 'length'`).option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=c(),s=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).webhooks.list();if(t.format==="json")m(s,"json");else {if(s.data.length===0){console.log("No webhooks found.");return}let a=s.data.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"}));m(a,"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
2308
2316
 
2309
2317
  USAGE
2310
2318
  emailr webhooks get <webhook_id> [options]
@@ -2335,7 +2343,7 @@ EXAMPLES
2335
2343
  emailr webhooks get whk_abc123 --format json
2336
2344
 
2337
2345
  # Extract just the secret
2338
- emailr webhooks get whk_abc123 --format json | jq -r '.secret'`).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);}}),i.command("create").description(`Create a new webhook
2346
+ emailr webhooks get whk_abc123 --format json | jq -r '.secret'`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.get(t);e.format==="json"?m(a,"json"):m({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);}}),i.command("create").description(`Create a new webhook
2339
2347
 
2340
2348
  USAGE
2341
2349
  emailr webhooks create --name <name> --url <url> --events <events> [options]
@@ -2409,7 +2417,7 @@ EXAMPLES
2409
2417
 
2410
2418
  NOTE
2411
2419
  Save the webhook secret returned by this command. You'll need it
2412
- 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(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),r=t.events.split(",").map(n=>n.trim()),a=await o.webhooks.create({name:t.name,url:t.url,events:r});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);}}),i.command("update <webhook_id>").description(`Update a webhook
2420
+ 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=c(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),s=t.events.split(",").map(r=>r.trim()),a=await o.webhooks.create({name:t.name,url:t.url,events:s});t.format==="json"?m(a,"json"):(p("Webhook created successfully!"),m({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);}}),i.command("update <webhook_id>").description(`Update a webhook
2413
2421
 
2414
2422
  USAGE
2415
2423
  emailr webhooks update <webhook_id> [options]
@@ -2454,7 +2462,7 @@ EXAMPLES
2454
2462
  --events "email.delivered,email.bounced"
2455
2463
 
2456
2464
  # Get JSON output
2457
- 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 o=m(),r=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 n=await r.webhooks.update(t,a);e.format==="json"?c(n,"json"):(d("Webhook updated successfully!"),c({ID:n.id,Name:n.name,URL:n.url},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update webhook"),process.exit(1);}}),i.command("enable <webhook_id>").description(`Enable a webhook
2465
+ 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 o=c(),s=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(n=>n.trim()));let r=await s.webhooks.update(t,a);e.format==="json"?m(r,"json"):(p("Webhook updated successfully!"),m({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);}}),i.command("enable <webhook_id>").description(`Enable a webhook
2458
2466
 
2459
2467
  USAGE
2460
2468
  emailr webhooks enable <webhook_id> [options]
@@ -2478,7 +2486,7 @@ EXAMPLES
2478
2486
  emailr webhooks enable whk_abc123
2479
2487
 
2480
2488
  # Get JSON output
2481
- emailr webhooks enable whk_abc123 --format json`).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);}}),i.command("disable <webhook_id>").description(`Disable a webhook
2489
+ emailr webhooks enable whk_abc123 --format json`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.enable(t);e.format==="json"?m(a,"json"):p("Webhook enabled successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to enable webhook"),process.exit(1);}}),i.command("disable <webhook_id>").description(`Disable a webhook
2482
2490
 
2483
2491
  USAGE
2484
2492
  emailr webhooks disable <webhook_id> [options]
@@ -2507,7 +2515,7 @@ EXAMPLES
2507
2515
 
2508
2516
  NOTE
2509
2517
  Events are not queued while webhook is disabled. If you need to
2510
- temporarily stop processing, consider handling this in your endpoint.`).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);}}),i.command("delete <webhook_id>").description(`Delete a webhook
2518
+ temporarily stop processing, consider handling this in your endpoint.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.disable(t);e.format==="json"?m(a,"json"):p("Webhook disabled successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to disable webhook"),process.exit(1);}}),i.command("delete <webhook_id>").description(`Delete a webhook
2511
2519
 
2512
2520
  USAGE
2513
2521
  emailr webhooks delete <webhook_id> [options]
@@ -2535,7 +2543,7 @@ EXAMPLES
2535
2543
 
2536
2544
  WARNING
2537
2545
  This action is permanent and cannot be undone.
2538
- Create a new webhook if you need to restore functionality.`).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);}}),i}function Ie(){let i=new Command("segments").description(`Manage contact segments
2546
+ Create a new webhook if you need to restore functionality.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).webhooks.delete(t);e.format==="json"?m(a,"json"):p("Webhook deleted successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to delete webhook"),process.exit(1);}}),i}function Ce(){let i=new Command("segments").description(`Manage contact segments
2539
2547
 
2540
2548
  USAGE
2541
2549
  emailr segments <subcommand> [options]
@@ -2675,7 +2683,7 @@ EXAMPLES
2675
2683
  emailr segments list --format json
2676
2684
 
2677
2685
  # Pipe JSON to jq for processing
2678
- emailr segments list --format json | jq '.[].name'`).option("--tags <tags>","Filter by tags (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}),r={};if(t.tags){let n=t.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);n.length>0&&(r.tags=n.join(","));}let a=await o.segments.list(r);if(t.format==="json")c(a,"json");else {if(a.length===0){console.log("No segments found.");return}let n=a.map(s=>({ID:s.id,Name:s.name,Description:(s.description||"").substring(0,30)+((s.description?.length||0)>30?"...":""),Tags:s.tags?.join(", ")||"-","Created At":new Date(s.created_at).toLocaleDateString()}));c(n,"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
2686
+ emailr segments list --format json | jq '.[].name'`).option("--tags <tags>","Filter by tags (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=c(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),s={};if(t.tags){let r=t.tags.split(",").map(n=>n.trim().toLowerCase()).filter(Boolean);r.length>0&&(s.tags=r.join(","));}let a=await o.segments.list(s);if(t.format==="json")m(a,"json");else {if(a.length===0){console.log("No segments found.");return}let r=a.map(n=>({ID:n.id,Name:n.name,Description:(n.description||"").substring(0,30)+((n.description?.length||0)>30?"...":""),Tags:n.tags?.join(", ")||"-","Created At":new Date(n.created_at).toLocaleDateString()}));m(r,"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
2679
2687
 
2680
2688
  USAGE
2681
2689
  emailr segments get <segment_id> [options]
@@ -2702,7 +2710,7 @@ EXAMPLES
2702
2710
  emailr segments get seg_abc123 --format json
2703
2711
 
2704
2712
  # Pipe JSON to jq to view conditions
2705
- emailr segments get seg_abc123 --format json | jq '.conditions'`).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),Tags:a.tags?.join(", ")||"-","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);}}),i.command("create").description(`Create a new segment
2713
+ emailr segments get seg_abc123 --format json | jq '.conditions'`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).segments.get(t);e.format==="json"?m(a,"json"):m({ID:a.id,Name:a.name,Description:a.description||"N/A",Conditions:JSON.stringify(a.conditions),Tags:a.tags?.join(", ")||"-","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);}}),i.command("create").description(`Create a new segment
2706
2714
 
2707
2715
  USAGE
2708
2716
  emailr segments create --name <segment_name> --conditions <json> [options]
@@ -2752,7 +2760,7 @@ EXAMPLES
2752
2760
  # Get JSON output for scripting
2753
2761
  emailr segments create --name "Test" \\
2754
2762
  --conditions '[{"field": "subscribed", "operator": "equals", "value": true}]' \\
2755
- --format json`).requiredOption("--name <name>","Segment name").requiredOption("--conditions <json>","Segment conditions as JSON").option("--description <description>","Segment description").option("--tags <tags>","Comma-separated tags").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=m(),o=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 a={name:t.name,description:t.description,conditions:r};t.tags&&(a.tags=t.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let n=await o.segments.create(a);t.format==="json"?c(n,"json"):(d("Segment created successfully!"),c({ID:n.id,Name:n.name,Tags:n.tags?.join(", ")||"-"},"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
2763
+ --format json`).requiredOption("--name <name>","Segment name").requiredOption("--conditions <json>","Segment conditions as JSON").option("--description <description>","Segment description").option("--tags <tags>","Comma-separated tags").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=c(),o=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),s;try{s=JSON.parse(t.conditions);}catch{l("Invalid JSON for --conditions"),process.exit(1);}let a={name:t.name,description:t.description,conditions:s};t.tags&&(a.tags=t.tags.split(",").map(n=>n.trim().toLowerCase()).filter(Boolean));let r=await o.segments.create(a);t.format==="json"?m(r,"json"):(p("Segment created successfully!"),m({ID:r.id,Name:r.name,Tags:r.tags?.join(", ")||"-"},"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
2756
2764
 
2757
2765
  USAGE
2758
2766
  emailr segments update <segment_id> [options]
@@ -2798,7 +2806,7 @@ EXAMPLES
2798
2806
  --conditions '[{"field": "metadata.plan", "operator": "equals", "value": "enterprise"}]'
2799
2807
 
2800
2808
  # Get JSON output
2801
- 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("--tags <tags>","Comma-separated tags").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=m(),r=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);}e.tags&&(a.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let n=await r.segments.update(t,a);e.format==="json"?c(n,"json"):(d("Segment updated successfully!"),c({ID:n.id,Name:n.name,Tags:n.tags?.join(", ")||"-"},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update segment"),process.exit(1);}}),i.command("delete <segment_id>").description(`Delete a segment
2809
+ 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("--tags <tags>","Comma-separated tags").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),s=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);}e.tags&&(a.tags=e.tags.split(",").map(n=>n.trim().toLowerCase()).filter(Boolean));let r=await s.segments.update(t,a);e.format==="json"?m(r,"json"):(p("Segment updated successfully!"),m({ID:r.id,Name:r.name,Tags:r.tags?.join(", ")||"-"},"table"));}catch(o){l(o instanceof Error?o.message:"Failed to update segment"),process.exit(1);}}),i.command("delete <segment_id>").description(`Delete a segment
2802
2810
 
2803
2811
  USAGE
2804
2812
  emailr segments delete <segment_id>
@@ -2823,7 +2831,7 @@ EXAMPLES
2823
2831
 
2824
2832
  WARNING
2825
2833
  This action is permanent and cannot be undone.
2826
- Any broadcasts targeting this segment will need to be updated.`).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);}}),i.command("count <segment_id>").description(`Get the number of contacts in a segment
2834
+ Any broadcasts targeting this segment will need to be updated.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).segments.delete(t);e.format==="json"?m(a,"json"):p("Segment deleted successfully!");}catch(o){l(o instanceof Error?o.message:"Failed to delete segment"),process.exit(1);}}),i.command("count <segment_id>").description(`Get the number of contacts in a segment
2827
2835
 
2828
2836
  USAGE
2829
2837
  emailr segments count <segment_id> [options]
@@ -2854,7 +2862,7 @@ EXAMPLES
2854
2862
 
2855
2863
  TIP
2856
2864
  Use this before sending broadcasts to verify your segment
2857
- targets the expected number of contacts.`).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);}}),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>
2865
+ targets the expected number of contacts.`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).segments.getContactsCount(t);e.format==="json"?m(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);}}),i}function pt(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 ut(){return `<!DOCTYPE html>
2858
2866
  <html lang="en">
2859
2867
  <head>
2860
2868
  <meta charset="UTF-8">
@@ -2900,7 +2908,7 @@ TIP
2900
2908
  <p>You can close this window and return to your terminal.</p>
2901
2909
  </div>
2902
2910
  </body>
2903
- </html>`}function ee(i){return `<!DOCTYPE html>
2911
+ </html>`}function te(i){return `<!DOCTYPE html>
2904
2912
  <html lang="en">
2905
2913
  <head>
2906
2914
  <meta charset="UTF-8">
@@ -2955,7 +2963,7 @@ TIP
2955
2963
  <p style="margin-top: 1rem;">Please close this window and try again.</p>
2956
2964
  </div>
2957
2965
  </body>
2958
- </html>`}function Ce(){let i=null,t=0,e=null,o=null,r=null;return {async start(){return new Promise((a,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,a({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(a,n){return r=a,new Promise(s=>{e=s,o=setTimeout(()=>{e&&e({success:false,error:"Login timed out. Please try again."});},n);})},async stop(){if(o&&(clearTimeout(o),o=null),i)return new Promise(a=>{i.close(()=>{i=null,a();});})}}}function ke(){return ct.randomBytes(32).toString("hex")}function Ue(i){return new Promise(t=>{let e=process.platform,o;switch(e){case "darwin":o=`open "${i}"`;break;case "win32":o=`start "" "${i}"`;break;default:o=`xdg-open "${i}"`;break}exec(o,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`,o=new URLSearchParams({state:i,callback_url:e});return `${pt}/consent/authorize?${o.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=Ce(),e=(i.timeout||$)*1e3;try{u("Starting authentication server...");let{port:o,url:r}=await t.start(),a=ke(),n=ut(a,o);console.log(""),u("Authorization URL:"),console.log(` ${n}`),console.log(""),i.noBrowser?u("Please open the URL above in your browser to continue."):await Ue(n)?u("Browser opened. Please complete authentication in your browser."):(I("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(a,e);s.success&&s.apiKey?(G({apiKey:s.apiKey}),console.log(""),d("Login successful!"),u(`API key saved to: ${j()}`),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(o){console.log(""),l(o instanceof Error?o.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=_.join(B.homedir(),".config","opencode","skills","emailr-cli"),wt=`---
2966
+ </html>`}function Ue(){let i=null,t=0,e=null,o=null,s=null;return {async start(){return new Promise((a,r)=>{i=Xe.createServer((n,d)=>{if(n.method!=="GET"||!n.url?.startsWith("/callback")){d.writeHead(404,{"Content-Type":"text/plain"}),d.end("Not Found");return}let h=pt(n.url);if(s&&h.state!==s){d.writeHead(400,{"Content-Type":"text/html"}),d.end(te("Security verification failed. State parameter mismatch.")),e&&e({success:false,error:"State parameter mismatch"});return}if(h.error){let u=h.message||h.error;d.writeHead(200,{"Content-Type":"text/html"}),d.end(te(u)),e&&e({success:false,error:u});return}let y=h.key||h.code;if(y){d.writeHead(200,{"Content-Type":"text/html"}),d.end(ut()),e&&e({success:true,apiKey:y});return}d.writeHead(400,{"Content-Type":"text/html"}),d.end(te("Invalid callback: missing required parameters.")),e&&e({success:false,error:"Invalid callback: missing required parameters"});}),i.listen(0,"127.0.0.1",()=>{let n=i.address();n&&typeof n=="object"?(t=n.port,a({port:t,url:`http://127.0.0.1:${t}/callback`})):r(new Error("Failed to get server address"));}),i.on("error",n=>{r(new Error(`Failed to start callback server: ${n.message}`));});})},async waitForCallback(a,r){return s=a,new Promise(n=>{e=n,o=setTimeout(()=>{e&&e({success:false,error:"Login timed out. Please try again."});},r);})},async stop(){if(o&&(clearTimeout(o),o=null),i)return new Promise(a=>{i.close(()=>{i=null,a();});})}}}function Pe(){return ft.randomBytes(32).toString("hex")}function ke(i){return new Promise(t=>{let e=process.platform,o;switch(e){case "darwin":o=`open "${i}"`;break;case "win32":o=`start "" "${i}"`;break;default:o=`xdg-open "${i}"`;break}exec(o,s=>{t(!s);});})}var B=120,gt=process.env.EMAILR_WEB_URL||"https://app.emailr.dev";function wt(i,t){let e=`http://127.0.0.1:${t}/callback`,o=new URLSearchParams({state:i,callback_url:e});return `${gt}/consent/authorize?${o.toString()}`}function Ae(){return new Command("login").description("Log in to Emailr via browser authentication").option("-t, --timeout <seconds>","Timeout in seconds",String(B)).option("--no-browser","Don't automatically open the browser").action(async t=>{await yt({timeout:parseInt(t.timeout,10)||B,noBrowser:t.browser===false});})}async function yt(i){let t=Ue(),e=(i.timeout||B)*1e3;try{f("Starting authentication server...");let{port:o,url:s}=await t.start(),a=Pe(),r=wt(a,o);console.log(""),f("Authorization URL:"),console.log(` ${r}`),console.log(""),i.noBrowser?f("Please open the URL above in your browser to continue."):await ke(r)?f("Browser opened. Please complete authentication in your browser."):(I("Could not open browser automatically."),f("Please open the URL above in your browser to continue.")),console.log(""),f(`Waiting for authentication (timeout: ${i.timeout||B}s)...`);let n=await t.waitForCallback(a,e);n.success&&n.apiKey?(G({apiKey:n.apiKey}),console.log(""),p("Login successful!"),f(`API key saved to: ${N()}`),f("You can now use the Emailr CLI.")):(console.log(""),l(n.error||"Authentication failed."),f("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."),f("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=_.join(V.homedir(),".config","opencode","skills","emailr-cli"),Ot=`---
2959
2967
  name: emailr-cli
2960
2968
  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.
2961
2969
  ---
@@ -2995,7 +3003,7 @@ IMPORTANT: Always use \`--background\` flag so the command returns immediately a
2995
3003
  - Create: \`emailr templates create --name "Name" --subject "Subject" --html-file ./template.html\`
2996
3004
  - Update: \`emailr templates update <id> --html-file ./template.html\`
2997
3005
  - Delete: \`emailr templates delete <id>\`
2998
- `;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=_.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
3006
+ `;function Et(){try{return execSync("which opencode",{stdio:"ignore"}),!0}catch{return false}}function _t(){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 jt(){O.existsSync(oe)||O.mkdirSync(oe,{recursive:true});let i=_.join(oe,"SKILL.md");O.writeFileSync(i,Ot,"utf-8");}function Me(){return new Command("agent").description(`Launch an AI agent with Emailr CLI expertise
2999
3007
 
3000
3008
  This opens OpenCode (opencode.ai), an AI coding agent that knows how to use all Emailr CLI commands.
3001
3009
  The agent can help you:
@@ -3003,10 +3011,185 @@ The agent can help you:
3003
3011
  - Manage contacts, broadcasts, and segments
3004
3012
  - Configure domains and webhooks
3005
3013
 
3006
- 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(`
3007
- 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){I(`Could not install skill: ${r instanceof Error?r.message:String(r)}`);}let e=[];t.model&&e.push("--model",t.model),console.log(`
3014
+ 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=>{Et()||(t.install?_t()||(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(`
3015
+ 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{jt(),p("Emailr CLI skill loaded");}catch(s){I(`Could not install skill: ${s instanceof Error?s.message:String(s)}`);}let e=[];t.model&&e.push("--model",t.model),console.log(`
3008
3016
  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.
3009
- `);let o=spawn("opencode",e,{stdio:"inherit",env:process.env});o.on("error",r=>{l(`Failed to start agent: ${r.message}`),process.exit(1);}),o.on("exit",r=>{process.exit(r??0);});})}var g=new Command;g.name("emailr").description(`Emailr CLI - Send emails and manage your email infrastructure
3017
+ `);let o=spawn("opencode",e,{stdio:"inherit",env:process.env});o.on("error",s=>{l(`Failed to start agent: ${s.message}`),process.exit(1);}),o.on("exit",s=>{process.exit(s??0);});})}function Fe(){let i=new Command("inbox").description(`Manage inbound emails (inbox)
3018
+
3019
+ USAGE
3020
+ emailr inbox <subcommand> [options]
3021
+
3022
+ DESCRIPTION
3023
+ View and manage received (inbound) emails. List your inbox, read individual
3024
+ emails, view conversation threads, reply to emails, and forward them.
3025
+
3026
+ SUBCOMMANDS
3027
+ list List received emails with pagination and search
3028
+ get <id> View a specific received email in detail
3029
+ thread <id> View the full conversation thread for an email
3030
+ reply <id> Reply to a received email
3031
+ forward <id> Forward a received email to other recipients
3032
+
3033
+ OPTIONS
3034
+ --format <format> Output format: json | table (default: table)
3035
+
3036
+ EXAMPLES
3037
+ # List all received emails
3038
+ emailr inbox list
3039
+
3040
+ # List with pagination
3041
+ emailr inbox list --limit 10 --page 2
3042
+
3043
+ # Search by sender email
3044
+ emailr inbox list --email sender@example.com
3045
+
3046
+ # View a specific email
3047
+ emailr inbox get <email-id>
3048
+
3049
+ # View conversation thread
3050
+ emailr inbox thread <email-id>
3051
+
3052
+ # Reply to an email
3053
+ emailr inbox reply <email-id> --html "<p>Thanks for your message!</p>"
3054
+
3055
+ # Reply from a file
3056
+ emailr inbox reply <email-id> --html-file ./reply.html
3057
+
3058
+ # Forward an email
3059
+ emailr inbox forward <email-id> --to recipient@example.com
3060
+
3061
+ # Forward with a message
3062
+ emailr inbox forward <email-id> --to recipient@example.com --message "FYI"
3063
+
3064
+ SEE ALSO
3065
+ emailr send Send individual emails
3066
+ emailr emails View all emails (sent and received)`);return i.command("list").description(`List received emails
3067
+
3068
+ USAGE
3069
+ emailr inbox list [options]
3070
+
3071
+ DESCRIPTION
3072
+ List all received (inbound) emails with optional search and pagination.
3073
+
3074
+ OPTIONS
3075
+ --limit <count> Number of emails to return (default: 20, max: 100)
3076
+ --page <number> Page number (default: 1)
3077
+ --email <address> Filter by sender email (partial match)
3078
+ --domain <domain> Filter by sender domain
3079
+ --format <format> Output format: json | table (default: table)
3080
+
3081
+ EXAMPLES
3082
+ emailr inbox list
3083
+ emailr inbox list --limit 50 --page 2
3084
+ emailr inbox list --email john@example.com
3085
+ emailr inbox list --domain example.com
3086
+ emailr inbox list --format json
3087
+
3088
+ OUTPUT FORMATS
3089
+ --format json Machine-readable JSON with data array and pagination
3090
+ --format table Human-readable table with ID, From, Subject, To, Date (default)`).option("--limit <count>","Number of emails to return","20").option("--page <number>","Page number","1").option("--email <address>","Filter by sender email (partial match)").option("--domain <domain>","Filter by sender domain").option("--format <format>","Output format (json|table)","table").action(async t=>{try{let e=c(),s=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).emails.list({status:"received",page:parseInt(t.page),limit:Math.min(parseInt(t.limit),100),email:t.email,domain:t.domain});if(t.format==="json")m(s,"json");else {if(!s.data||s.data.length===0){f("No emails in inbox");return}let r=s.data.map(n=>({ID:n.id,From:n.from_email,Subject:De(n.subject||"(no subject)",40),To:n.to_email,Date:re(n.created_at),Attachments:n.attachments?.length||0}));m(r,"table"),f(`Page ${s.pagination.page} of ${s.pagination.pages} (${s.pagination.total} total)`);}}catch(e){l(e instanceof Error?e.message:"Failed to list inbox"),process.exit(1);}}),i.command("get <id>").description(`View a received email
3091
+
3092
+ USAGE
3093
+ emailr inbox get <email-id> [options]
3094
+
3095
+ DESCRIPTION
3096
+ View the full details of a received email including headers, content,
3097
+ and attachment information.
3098
+
3099
+ OPTIONS
3100
+ --content <type> Content to display: preview | text | html (default: preview)
3101
+ --format <format> Output format: json | table (default: table)
3102
+
3103
+ EXAMPLES
3104
+ emailr inbox get abc-123-def
3105
+ emailr inbox get abc-123-def --content html
3106
+ emailr inbox get abc-123-def --content text
3107
+ emailr inbox get abc-123-def --format json
3108
+
3109
+ OUTPUT FORMATS
3110
+ --format json Full email object with all fields as JSON
3111
+ --format table Key-value table with email metadata and content preview (default)`).option("--content <type>","Content to display: preview | text | html","preview").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),a=await new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}).emails.get(t);if(e.format==="json")m(a,"json");else {if(m({ID:a.id,From:a.from_email,To:a.to_email,Subject:a.subject||"(no subject)",Date:re(a.created_at),Status:a.status,"Thread ID":a.thread_id||"-","Parent Email":a.parent_email_id||"-",Attachments:a.attachments?.length||0},"table"),console.log(""),e.content==="html")console.log(a.html_content||"(no HTML content)");else if(e.content==="text")console.log(a.text_content||"(no text content)");else {let n=a.text_content||a.html_content?.replace(/<[^>]*>/g,"")||"(no content)";console.log(n);}if(a.attachments&&a.attachments.length>0){console.log(""),f("Attachments:");let n=a.attachments.map(d=>({Filename:d.filename,Type:d.contentType||d.content_type||"-",Size:Nt(d.size||0)}));m(n,"table");}}}catch(o){l(o instanceof Error?o.message:"Failed to get email"),process.exit(1);}}),i.command("thread <id>").description(`View conversation thread for an email
3112
+
3113
+ USAGE
3114
+ emailr inbox thread <email-id> [options]
3115
+
3116
+ DESCRIPTION
3117
+ View the full conversation thread for a given email. Shows all emails
3118
+ in the thread ordered chronologically, including both sent and received
3119
+ messages.
3120
+
3121
+ OPTIONS
3122
+ --format <format> Output format: json | table (default: table)
3123
+
3124
+ EXAMPLES
3125
+ emailr inbox thread abc-123-def
3126
+ emailr inbox thread abc-123-def --format json
3127
+
3128
+ OUTPUT FORMATS
3129
+ --format json JSON array of all emails in the thread
3130
+ --format table Chronological conversation view with direction indicators (default)`).option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),s=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a=await s.emails.get(t),r=a.thread_id||a.id,n=[],d=new Set;if(n.push(a),d.add(a.id),a.thread_id&&a.thread_id!==a.id)try{let u=await s.emails.get(a.thread_id);d.has(u.id)||(n.push(u),d.add(u.id));}catch{}let h=await s.emails.list({limit:100});if(h.data)for(let u of h.data)d.has(u.id)||(u.thread_id===r||u.parent_email_id===a.id||u.id===a.parent_email_id)&&(n.push(u),d.add(u.id));if(n.sort((u,b)=>new Date(u.created_at).getTime()-new Date(b.created_at).getTime()),e.format==="json")m(n,"json");else {n.length<=1&&f("No other emails in this conversation"),f(`Conversation (${n.length} email${n.length!==1?"s":""}):`),console.log("");for(let u of n){let b=u.status==="received"?"\u2190 IN ":"\u2192 OUT",w=u.id===t?" \u25C0 (selected)":"";console.log(`${b} ${re(u.created_at)}${w}`),console.log(` From: ${u.from_email}`),console.log(` To: ${u.to_email}`),console.log(` Subject: ${u.subject||"(no subject)"}`);let j=u.text_content||u.html_content?.replace(/<[^>]*>/g,"")||"";j&&console.log(` ${De(j.trim(),120)}`),console.log("");}}await new Promise(u=>process.stdout.write("",()=>u())),process.exit(0);}catch(o){l(o instanceof Error?o.message:"Failed to load thread"),process.exit(1);}}),i.command("reply <id>").description(`Reply to a received email
3131
+
3132
+ USAGE
3133
+ emailr inbox reply <email-id> [options]
3134
+
3135
+ DESCRIPTION
3136
+ Send a reply to a received email. The reply is automatically threaded
3137
+ with the original conversation. The To, From, and Subject fields are
3138
+ pre-filled from the original email.
3139
+
3140
+ OPTIONS
3141
+ --html <content> HTML content for the reply body
3142
+ --text <content> Plain text content for the reply body
3143
+ --html-file <path> Read HTML content from a file
3144
+ --text-file <path> Read plain text content from a file
3145
+ --from <email> Override the sender address
3146
+ --cc <emails> CC recipients (comma-separated)
3147
+ --bcc <emails> BCC recipients (comma-separated)
3148
+ --subject <subject> Override the reply subject
3149
+ --format <format> Output format: json | table (default: table)
3150
+
3151
+ EXAMPLES
3152
+ # Reply with inline HTML
3153
+ emailr inbox reply abc-123 --html "<p>Thanks for reaching out!</p>"
3154
+
3155
+ # Reply with plain text
3156
+ emailr inbox reply abc-123 --text "Thanks for reaching out!"
3157
+
3158
+ # Reply from a file
3159
+ emailr inbox reply abc-123 --html-file ./reply.html
3160
+
3161
+ # Reply with CC
3162
+ emailr inbox reply abc-123 --html "<p>See reply</p>" --cc manager@example.com
3163
+
3164
+ OUTPUT FORMATS
3165
+ --format json Machine-readable JSON with message_id and status
3166
+ --format table Human-readable summary with Message ID, To, Subject, Status (default)`).option("--html <content>","HTML content for reply").option("--text <content>","Plain text content for reply").option("--html-file <path>","Read HTML content from file").option("--text-file <path>","Read plain text content from file").option("--from <email>","Override sender address").option("--cc <emails>","CC recipients (comma-separated)").option("--bcc <emails>","BCC recipients (comma-separated)").option("--subject <subject>","Override reply subject").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),s=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a=await s.emails.get(t),r=e.html,n=e.text;if(e.htmlFile)try{r=readFileSync(e.htmlFile,"utf-8");}catch{l(`Failed to read HTML file: ${e.htmlFile}`),process.exit(1);}if(e.textFile)try{n=readFileSync(e.textFile,"utf-8");}catch{l(`Failed to read text file: ${e.textFile}`),process.exit(1);}!r&&!n&&(l("Reply content is required. Use --html, --text, --html-file, or --text-file"),process.exit(1));let d=e.subject||(a.subject?.startsWith("Re:")?a.subject:`Re: ${a.subject||""}`),h={to:a.from_email,from:e.from||a.to_email,subject:d,html:r||void 0,text:n||r?.replace(/<[^>]*>/g,"")||void 0,replyTo:{in_reply_to:a.message_id||a.ses_message_id,thread_id:a.thread_id||a.id,parent_email_id:a.id}};if(e.cc){let b=e.cc.split(",").map(w=>w.trim());h.cc=b.length===1?b[0]:b;}if(e.bcc){let b=e.bcc.split(",").map(w=>w.trim());h.bcc=b.length===1?b[0]:b;}let y=await s.emails.send(h);e.format==="json"?m(y,"json"):(p("Reply sent successfully!"),m({"Message ID":y.message_id,To:a.from_email,Subject:d,Status:y.status},"table")),await new Promise(b=>process.stdout.write("",()=>b())),process.exit(0);}catch(o){l(o instanceof Error?o.message:"Failed to send reply"),process.exit(1);}}),i.command("forward <id>").description(`Forward a received email
3167
+
3168
+ USAGE
3169
+ emailr inbox forward <email-id> --to <recipients> [options]
3170
+
3171
+ DESCRIPTION
3172
+ Forward a received email to one or more recipients. The original email
3173
+ content is included in the forwarded message.
3174
+
3175
+ OPTIONS
3176
+ --to <emails> Recipient email addresses (comma-separated, required)
3177
+ --message <text> Optional message to include above the forwarded content
3178
+ --format <format> Output format: json | table (default: table)
3179
+
3180
+ EXAMPLES
3181
+ # Forward to a single recipient
3182
+ emailr inbox forward abc-123 --to colleague@example.com
3183
+
3184
+ # Forward to multiple recipients
3185
+ emailr inbox forward abc-123 --to "a@example.com,b@example.com"
3186
+
3187
+ # Forward with a note
3188
+ emailr inbox forward abc-123 --to colleague@example.com --message "FYI - see below"
3189
+
3190
+ OUTPUT FORMATS
3191
+ --format json Machine-readable JSON with message_id and status
3192
+ --format table Human-readable summary with Message ID, To, Recipients, Status (default)`).requiredOption("--to <emails>","Recipient email addresses (comma-separated)").option("--message <text>","Message to include with forwarded email").option("--format <format>","Output format (json|table)","table").action(async(t,e)=>{try{let o=c(),s=new Emailr({apiKey:o.apiKey,baseUrl:o.baseUrl}),a=e.to.split(",").map(d=>d.trim()).filter(Boolean),r=await s.emails.forward({email_id:t,to:a.length===1?a[0]:a,message:e.message});e.format==="json"?m(r,"json"):(p("Email forwarded successfully!"),m({"Message ID":r.message_id,To:a.join(", "),Recipients:r.recipients,Status:r.status},"table")),await new Promise(d=>process.stdout.write("",()=>d())),process.exit(0);}catch(o){l(o instanceof Error?o.message:"Failed to forward email"),process.exit(1);}}),i}function De(i,t){return i.length<=t?i:i.slice(0,t-1)+"\u2026"}function re(i){return new Date(i).toLocaleString()}function Nt(i){return i<1024?i+" B":i<1024*1024?(i/1024).toFixed(1)+" KB":(i/(1024*1024)).toFixed(1)+" MB"}var S=new Command;S.name("emailr").description(`Emailr CLI - Send emails and manage your email infrastructure
3010
3193
 
3011
3194
  USAGE
3012
3195
  emailr <command> [subcommand] [options]
@@ -3018,6 +3201,7 @@ DESCRIPTION
3018
3201
 
3019
3202
  COMMANDS
3020
3203
  send Send individual emails with HTML/text content or templates
3204
+ inbox View and manage received (inbound) emails
3021
3205
  templates Create, edit, preview, and publish email templates
3022
3206
  contacts Manage email contacts and their metadata
3023
3207
  segments Create and manage contact segments with conditions
@@ -3055,6 +3239,21 @@ EXAMPLES
3055
3239
  emailr send --to user@example.com --template tpl_abc123 \\
3056
3240
  --template-data '{"name": "John"}'
3057
3241
 
3242
+ # List received emails in inbox
3243
+ emailr inbox list
3244
+
3245
+ # View a received email
3246
+ emailr inbox get <email-id>
3247
+
3248
+ # View conversation thread
3249
+ emailr inbox thread <email-id>
3250
+
3251
+ # Reply to a received email
3252
+ emailr inbox reply <email-id> --html "<p>Thanks!</p>"
3253
+
3254
+ # Forward a received email
3255
+ emailr inbox forward <email-id> --to colleague@example.com
3256
+
3058
3257
  # List all templates
3059
3258
  emailr templates list
3060
3259
 
@@ -3092,4 +3291,4 @@ AGENTIC WORKFLOW
3092
3291
 
3093
3292
  MORE INFORMATION
3094
3293
  Run 'emailr <command> --help' for detailed help on any command.
3095
- Run 'emailr <command> <subcommand> --help' for subcommand details.`).version("1.7.7");g.addCommand(ce());g.addCommand(me());g.addCommand(Oe());g.addCommand(Ee());g.addCommand(Ne());g.addCommand(je());g.addCommand(Ie());g.addCommand(_e());g.addCommand(Pe());g.addCommand(xe());g.parse();
3294
+ Run 'emailr <command> <subcommand> --help' for subcommand details.`).version("1.8.0");S.addCommand(de());S.addCommand(Fe());S.addCommand(pe());S.addCommand(_e());S.addCommand(je());S.addCommand(Ne());S.addCommand(Ie());S.addCommand(Ce());S.addCommand(xe());S.addCommand(Ae());S.addCommand(Me());S.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emailr-cli",
3
- "version": "1.7.7",
3
+ "version": "1.8.0",
4
4
  "description": "Command-line interface for the Emailr email API",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,7 +23,7 @@
23
23
  "chalk": "^5.3.0",
24
24
  "cli-table3": "^0.6.3",
25
25
  "commander": "^12.0.0",
26
- "emailr": "^1.3.3",
26
+ "emailr": "^1.3.4",
27
27
  "open": "^11.0.0"
28
28
  },
29
29
  "devDependencies": {