emailr-cli 1.8.0 → 1.10.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 +615 -123
  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 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.
2
+ import {Command}from'commander';import {Emailr}from'emailr';import x,{readFileSync}from'fs';import V from'os';import E from'path';import de from'cli-table3';import T from'chalk';import at from'http';import {URL}from'url';import vt from'crypto';import {spawn,execSync,exec}from'child_process';var We=[E.join(V.homedir(),".emailrrc"),E.join(V.homedir(),".config","emailr","config.json")];function d(){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 o of We)if(x.existsSync(o))try{let a=x.readFileSync(o,"utf-8"),e=JSON.parse(a);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 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
10
+ Or run: emailr config set api-key <your-api-key>`)}function I(){let o=E.join(V.homedir(),".config","emailr");return E.join(o,"config.json")}function K(o){let a=I(),e=E.dirname(a);x.existsSync(e)||x.mkdirSync(e,{recursive:true});let t={};if(x.existsSync(a))try{t=JSON.parse(x.readFileSync(a,"utf-8"));}catch{}let n={...t,...o};x.writeFileSync(a,JSON.stringify(n,null,2)+`
11
+ `);}function ce(o){try{return d()[o]?.toString()}catch{return}}function m(o,a="table"){a==="json"?console.log(JSON.stringify(o,null,2)):Xe(o);}function Xe(o){Array.isArray(o)?Ve(o):typeof o=="object"&&o!==null?ze(o):console.log(o);}function Ve(o){if(o.length===0){console.log(T.gray("No results"));return}let a=o[0];if(typeof a!="object"||a===null){o.forEach(n=>console.log(n));return}let e=Object.keys(a),t=new de({head:e.map(n=>T.cyan(n)),style:{head:[],border:[]}});for(let n of o){let i=e.map(r=>{let s=n[r];return pe(s)});t.push(i);}console.log(t.toString());}function ze(o){let a=new de({style:{head:[],border:[]}});for(let[e,t]of Object.entries(o))a.push([T.cyan(e),pe(t)]);console.log(a.toString());}function pe(o){return o==null?T.gray("-"):typeof o=="boolean"?o?T.green("\u2713"):T.red("\u2717"):typeof o=="object"?JSON.stringify(o):String(o)}function p(o){console.log(T.green("\u2713"),o);}function l(o){console.error(T.red("\u2717"),o);}function N(o){console.warn(T.yellow("\u26A0"),o);}function u(o){console.log(T.blue("\u2139"),o);}function fe(){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=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
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 a=>{try{let e=d(),t=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n=a.to.split(",").map(s=>s.trim()),i={to:n.length===1?n[0]:n};if(a.from&&(i.from=a.from),a.subject&&(i.subject=a.subject),a.htmlFile)try{i.html=readFileSync(a.htmlFile,"utf-8");}catch{l(`Failed to read HTML file: ${a.htmlFile}`),process.exit(1);}else a.html&&(i.html=a.html);if(a.textFile)try{i.text=readFileSync(a.textFile,"utf-8");}catch{l(`Failed to read text file: ${a.textFile}`),process.exit(1);}else a.text&&(i.text=a.text);if(a.template&&(i.template_id=a.template),a.templateData)try{i.template_data=JSON.parse(a.templateData);}catch{l("Invalid JSON for --template-data"),process.exit(1);}if(a.cc){let s=a.cc.split(",").map(c=>c.trim());i.cc=s.length===1?s[0]:s;}if(a.bcc){let s=a.bcc.split(",").map(c=>c.trim());i.bcc=s.length===1?s[0]:s;}a.replyTo&&(i.reply_to_email=a.replyTo),a.schedule&&(i.scheduled_at=a.schedule);let r=await t.emails.send(i);a.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 be(){let o=new Command("contacts").description(`Manage contacts
160
160
 
161
161
  USAGE
162
162
  emailr contacts <subcommand> [options]
@@ -238,7 +238,7 @@ EXAMPLES
238
238
 
239
239
  SEE ALSO
240
240
  emailr segments Manage contact segments
241
- emailr broadcasts Send emails to contacts`);return i.command("list").description(`List all contacts
241
+ emailr broadcasts Send emails to contacts`);return o.command("list").description(`List all contacts
242
242
 
243
243
  USAGE
244
244
  emailr contacts list [options]
@@ -286,8 +286,8 @@ 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=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
- 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
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 a=>{try{let e=d(),t=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n={limit:parseInt(a.limit,10),offset:parseInt(a.offset,10)};if(a.subscribed&&(n.subscribed=!0),a.unsubscribed&&(n.subscribed=!1),a.search&&(n.search=a.search),a.tags){let r=a.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);r.length>0&&(n.tags=r.join(","));}let i=await t.contacts.list(n);if(a.format==="json")m(i,"json");else {let r=i.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}));m(r,"table"),console.log(`
290
+ Total: ${i.total}`);}}catch(e){l(e instanceof Error?e.message:"Failed to list contacts"),process.exit(1);}}),o.command("get <contact_id>").description(`Get a contact by ID
291
291
 
292
292
  USAGE
293
293
  emailr contacts get <contact_id> [options]
@@ -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=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
317
+ emailr contacts get con_abc123 --format json | jq '.metadata'`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).contacts.get(a);m(i,e.format);}catch(t){l(t instanceof Error?t.message:"Failed to get contact"),process.exit(1);}}),o.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=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
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 a=>{try{let e=d(),t=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n={email:a.email};if(a.firstName&&(n.first_name=a.firstName),a.lastName&&(n.last_name=a.lastName),a.subscribed!==void 0&&(n.subscribed=a.subscribed),a.metadata)try{n.metadata=JSON.parse(a.metadata);}catch{l("Invalid JSON for --metadata"),process.exit(1);}a.tags&&(n.tags=a.tags.split(",").map(r=>r.trim().toLowerCase()).filter(Boolean));let i=await t.contacts.create(n);a.format==="json"?m(i,"json"):(p(`Contact created: ${i.id}`),m(i,"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create contact"),process.exit(1);}}),o.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=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
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(a,e)=>{try{let t=d(),n=new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}),i={};if(e.email&&(i.email=e.email),e.firstName&&(i.first_name=e.firstName),e.lastName&&(i.last_name=e.lastName),e.subscribed&&(i.subscribed=!0),e.unsubscribed&&(i.subscribed=!1),e.metadata)try{i.metadata=JSON.parse(e.metadata);}catch{l("Invalid JSON for --metadata"),process.exit(1);}e.tags&&(i.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let r=await n.contacts.update(a,i);e.format==="json"?m(r,"json"):(p(`Contact updated: ${r.id}`),m(r,"table"));}catch(t){l(t instanceof Error?t.message:"Failed to update contact"),process.exit(1);}}),o.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=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>
426
+ This action is permanent and cannot be undone.`).action(async a=>{try{let e=d();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).contacts.delete(a),p(`Contact deleted: ${a}`);}catch(e){l(e instanceof Error?e.message:"Failed to delete contact"),process.exit(1);}}),o}function ge(){return E.join(V.homedir(),".config","emailr","templates")}function tt(){let o=ge();x.existsSync(o)||x.mkdirSync(o,{recursive:true});}function z(o){return E.join(ge(),`${o}.html`)}function Y(o,a){tt();let e=z(o);x.writeFileSync(e,a,"utf-8");}function we(o){let a=z(o);return x.existsSync(a)?x.readFileSync(a,"utf-8"):null}function ye(o){let a=z(o);return x.existsSync(a)}var v=null,k=null,q=false;function ve(o){return o.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}function it(o){return o===null||o.trim()===""}function ot(o){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">${we(i)}</div>
492
+ <div class="template-id">${ve(o)}</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 ge(i){return `<!DOCTYPE html>
499
+ </html>`}function Se(o){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">${we(i)}</div>
551
+ <div class="template-id">${ve(o)}</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 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=`
555
+ </html>`}function rt(o){let a=o.match(/^\/preview\/([^/]+)$/);return a?a[1]:null}function nt(){return (o,a)=>{if(o.method!=="GET"){a.writeHead(405,{"Content-Type":"text/plain"}),a.end("Method Not Allowed");return}let e=o.url||"/",t=rt(e);if(!t){a.writeHead(404,{"Content-Type":"text/plain"}),a.end("Not Found");return}if(!ye(t)){a.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),a.end(Se(t));return}let n=we(t);if(n===null){a.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),a.end(Se(t));return}if(it(n)){a.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),a.end(ot(t));return}a.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),a.end(n);}}var Te={async start(){return q&&k!==null?k:new Promise((o,a)=>{v=at.createServer(nt()),v.listen(0,"127.0.0.1",()=>{let e=v.address();e&&typeof e=="object"?(k=e.port,q=true,v.unref(),o(k)):a(new Error("Failed to get server address"));}),v.on("error",e=>{q=false,k=null,v=null,a(new Error(`Failed to start preview server: ${e.message}`));});})},getPort(){return k},isRunning(){return q},async stop(){if(v)return new Promise(o=>{v.close(()=>{v=null,k=null,q=false,o();});})}};function xe(){return Te}async function Z(o){try{return `http://127.0.0.1:${await Te.start()}/preview/${o}`}catch{return null}}function Oe(){v&&v.ref();}var P=null,Q=null,W=null,D=[],_e=`
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 at(i){return i.includes("</body>")?i.replace("</body>",`${Oe}</body>`):i+Oe}function ot(){M.forEach(i=>{try{i.write(`data: reload
569
+ `;function mt(o){return o.includes("</body>")?o.replace("</body>",`${_e}</body>`):o+_e}function ct(){D.forEach(o=>{try{o.write(`data: reload
570
570
 
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
571
+ `);}catch{}});}async function ee(o,a){let e=E.resolve(o);return new Promise((t,n)=>{P=at.createServer((i,r)=>{if(i.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
- `),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
573
+ `),D.push(r),i.on("close",()=>{D=D.filter(s=>s!==r);});return}if(i.method==="GET"){try{let s=x.readFileSync(e,"utf-8"),c=mt(s);r.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),r.end(c);}catch(s){r.writeHead(500,{"Content-Type":"text/plain"}),r.end(`Error reading file: ${s instanceof Error?s.message:String(s)}`);}return}r.writeHead(405,{"Content-Type":"text/plain"}),r.end("Method Not Allowed");}),P.listen(0,"127.0.0.1",()=>{let i=P.address();if(i&&typeof i=="object"){Q=i.port;let r=null;W=x.watch(e,s=>{s==="change"&&(r&&clearTimeout(r),r=setTimeout(()=>{ct(),a?.();},100));}),t(Q);}else n(new Error("Failed to get server address"));}),P.on("error",i=>{n(new Error(`Failed to start server: ${i.message}`));});})}async function te(){if(W&&(W.close(),W=null),D.forEach(o=>{try{o.end();}catch{}}),D=[],P)return new Promise(o=>{P.close(()=>{P=null,Q=null,o();});})}async function je(o){try{let a=o.html_content??"";Y(o.id,a);}catch(a){return N(`Could not save template for preview: ${a instanceof Error?a.message:String(a)}`),null}try{let a=await Z(o.id);return a===null?(N("Could not start preview server"),null):a}catch(a){return N(`Could not generate preview URL: ${a instanceof Error?a.message:String(a)}`),null}}function Ie(){let o=new Command("templates").description(`Manage email templates
574
574
 
575
575
  USAGE
576
576
  emailr templates <subcommand> [options]
@@ -641,7 +641,7 @@ AGENTIC WORKFLOW
641
641
 
642
642
  SEE ALSO
643
643
  emailr send Send emails using templates
644
- emailr broadcasts Send bulk emails to segments`);return i.command("list").description(`List all templates
644
+ emailr broadcasts Send bulk emails to segments`);return o.command("list").description(`List all templates
645
645
 
646
646
  USAGE
647
647
  emailr templates list [options]
@@ -671,8 +671,8 @@ 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=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
- 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
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 a=>{try{let e=d(),t=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n={limit:parseInt(a.limit,10),page:parseInt(a.page,10)};if(a.tags){let r=a.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);r.length>0&&(n.tags=r.join(","));}let i=await t.templates.list(n);if(a.format==="json")m(i,"json");else {let r=i.map(s=>({ID:s.id,Name:s.name,Subject:s.subject,Variables:s.variables?.join(", ")||"-",Tags:s.tags?.join(", ")||"-",Created:s.created_at}));m(r,"table"),console.log(`
675
+ Total: ${i.length}`);}}catch(e){l(e instanceof Error?e.message:"Failed to list templates"),process.exit(1);}}),o.command("get <template_id>").description(`Get a template by ID
676
676
 
677
677
  USAGE
678
678
  emailr templates get <template_id> [options]
@@ -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=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
703
+ emailr templates get tpl_abc123 --format json | jq '.html_content'`).option("--format <format>","Output format: json | table","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).templates.get(a),r=await je({id:i.id,html_content:i.html_content??void 0}),s=i.preview_html?`${t.baseUrl}/preview/${i.id}`:null;if(e.format==="json")m({...i,preview_url:s??r},"json");else {let c={ID:i.id,Name:i.name,Subject:i.subject,Variables:i.variables?.join(", ")||"-",Created:i.created_at};s?c["Preview URL"]=s:r&&(c["Local Preview"]=r),m(c,"table");}}catch(t){l(t instanceof Error?t.message:"Failed to get template"),process.exit(1);}}),o.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=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
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(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).templates.fetch(a,{preview:e.preview??!1});if(i||(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=E.resolve(e.output);x.writeFileSync(r,i,"utf-8"),p(`HTML saved to: ${r}`);}else console.log(i);}catch(t){l(t instanceof Error?t.message:"Failed to fetch template"),process.exit(1);}}),o.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>
@@ -778,9 +778,9 @@ AGENTIC WORKFLOW
778
778
  2. Edit locally or with AI assistance
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
- 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=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
- 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
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(a,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 t=d(),n=new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}),i;if(e.htmlFile){let c=E.resolve(e.htmlFile);try{i=x.readFileSync(c,"utf-8");}catch{l(`Failed to read file: ${c}`),process.exit(1);}}else i=e.html;let r=await n.templates.pushPreview(a,i),s="updated";e.format==="json"?m({template_id:a,preview_url:r.preview_url,status:s},"json"):(p("Preview uploaded successfully"),m({"Template ID":a,"Preview URL":r.preview_url,Status:s},"table"),console.log(`
783
+ Share this URL with your AI agent for feedback.`));}catch(t){l(t instanceof Error?t.message:"Failed to push preview"),process.exit(1);}}),o.command("create").description(`Create a new email template
784
784
 
785
785
  USAGE
786
786
  emailr templates create --name <template_name> --subject <subject_line> [options]
@@ -801,6 +801,7 @@ OPTIONS
801
801
  --from-name <name> Sender display name (e.g. "Acme Inc")
802
802
  --reply-to <email_address> Default reply-to email address
803
803
  --preview-text <text> Preview text shown in email clients
804
+ --inbox-id <inbox_id> Inbox ID for sender identity defaults
804
805
  --tags <tags> Comma-separated tags
805
806
  --format <format> Output format: json | table (default: table)
806
807
 
@@ -827,8 +828,8 @@ EXAMPLES
827
828
 
828
829
  TIP
829
830
  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=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
- 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
831
+ 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("--inbox-id <inbox_id>","Inbox ID for sender identity defaults").option("--tags <tags>","Comma-separated tags").option("--format <format>","Output format: json | table","table").action(async a=>{try{let e=d(),t=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n={name:a.name,subject:a.subject};if(a.htmlFile){let s=await import('fs');n.html_content=s.readFileSync(a.htmlFile,"utf-8");}else a.html&&(n.html_content=a.html);if(a.textFile){let s=await import('fs');n.text_content=s.readFileSync(a.textFile,"utf-8");}else a.text&&(n.text_content=a.text);a.from&&(n.from_email=a.from),a.fromName&&(n.from_name=a.fromName),a.replyTo&&(n.reply_to=a.replyTo),a.previewText&&(n.preview_text=a.previewText),a.inboxId&&(n.inbox_id=a.inboxId),a.tags&&(n.tags=a.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let i=await t.templates.create(n),r=await je({id:i.id,html_content:i.html_content??void 0});if(a.format==="json")m({...i,preview_url:r},"json");else {p(`Template created: ${i.id}`);let s={ID:i.id,Name:i.name,Subject:i.subject,Variables:i.variables?.join(", ")||"-",Tags:i.tags?.join(", ")||"-"};r&&(s["Preview URL"]=r),m(s,"table"),r&&console.log(`
832
+ 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);}}),o.command("update <template_id>").description(`Update an existing email template
832
833
 
833
834
  USAGE
834
835
  emailr templates update <template_id> [options]
@@ -852,6 +853,7 @@ OPTIONS
852
853
  --from-name <name> New sender display name
853
854
  --reply-to <email_address> New default reply-to email address
854
855
  --preview-text <text> New preview text
856
+ --inbox-id <inbox_id> Inbox ID for sender identity defaults (use "none" to clear)
855
857
  --tags <tags> Comma-separated tags (replaces existing)
856
858
  --format <format> Output format: json | table (default: table)
857
859
 
@@ -882,7 +884,7 @@ AGENTIC WORKFLOW
882
884
  2. Edit locally or with AI assistance
883
885
  3. Push preview: emailr templates push-preview <id> --html-file template.html
884
886
  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=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
887
+ 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("--inbox-id <inbox_id>",'Inbox ID for sender identity (use "none" to clear)').option("--tags <tags>","Comma-separated tags").option("--format <format>","Output format: json | table","table").action(async(a,e)=>{try{let t=d(),n=new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}),i={};if(e.name&&(i.name=e.name),e.subject&&(i.subject=e.subject),e.htmlFile){let s=await import('fs');i.html_content=s.readFileSync(e.htmlFile,"utf-8");}else e.html&&(i.html_content=e.html);if(e.textFile){let s=await import('fs');i.text_content=s.readFileSync(e.textFile,"utf-8");}else e.text&&(i.text_content=e.text);e.from&&(i.from_email=e.from),e.fromName&&(i.from_name=e.fromName),e.replyTo&&(i.reply_to=e.replyTo),e.previewText&&(i.preview_text=e.previewText),e.inboxId&&(i.inbox_id=e.inboxId==="none"?null:e.inboxId),e.tags&&(i.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let r=await n.templates.update(a,i);if(e.format==="json")m(r,"json");else {p(`Template updated: ${r.id}`);let s={ID:r.id,Name:r.name,Subject:r.subject,Variables:r.variables?.join(", ")||"-",Tags:r.tags?.join(", ")||"-",Updated:r.updated_at};m(s,"table");}}catch(t){l(t instanceof Error?t.message:"Failed to update template"),process.exit(1);}}),o.command("delete <template_id>").description(`Delete a template
886
888
 
887
889
  USAGE
888
890
  emailr templates delete <template_id>
@@ -899,7 +901,7 @@ EXAMPLES
899
901
  emailr templates delete tpl_abc123
900
902
 
901
903
  WARNING
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
904
+ This action is permanent and cannot be undone.`).action(async a=>{try{let e=d();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).templates.delete(a),p(`Template deleted: ${a}`);}catch(e){l(e instanceof Error?e.message:"Failed to delete template"),process.exit(1);}}),o.command("preview <template_id>").description(`Preview a template in the browser
903
905
 
904
906
  USAGE
905
907
  emailr templates preview <template_id> [options]
@@ -928,17 +930,17 @@ EXAMPLES
928
930
 
929
931
  SEE ALSO
930
932
  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=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
+ 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(a,e)=>{try{let t=d(),n=new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl});console.log(`Fetching template ${a}...`);let i=await n.templates.get(a),r=i.html_content??"";if(Y(i.id,r),e.foreground){let w=await Z(i.id);if(w||(l("Failed to start preview server"),process.exit(1)),Oe(),console.log(`
934
+ Template: ${i.name}`),console.log(`Preview URL: ${w}`),e.open!==!1)try{await(await import('open')).default(w),console.log(`
933
935
  Browser opened.`);}catch{console.log(`
934
936
  Could not open browser automatically. Open the URL above manually.`);}process.on("SIGINT",async()=>{console.log(`
935
937
 
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=`
938
+ Stopping preview server...`),await xe().stop(),console.log("Done."),process.exit(0);});return}let s=E.join(process.cwd(),`.template-preview-${i.id}.html`);x.writeFileSync(s,r,"utf-8");let{spawn:c}=await import('child_process'),b=await import('os'),g=E.join(b.tmpdir(),"emailr-preview.pid");try{let w=x.readFileSync(g,"utf-8").trim();process.kill(parseInt(w,10),"SIGTERM");}catch{}let f=`
937
939
  const http = require('http');
938
940
  const fs = require('fs');
939
941
  const path = require('path');
940
942
  const os = require('os');
941
- const filePath = ${JSON.stringify(n)};
943
+ const filePath = ${JSON.stringify(s)};
942
944
  const pidFile = path.join(os.tmpdir(), 'emailr-preview.pid');
943
945
  const portFile = path.join(os.tmpdir(), 'emailr-preview.port');
944
946
  const server = http.createServer((req, res) => {
@@ -958,9 +960,9 @@ Stopping preview server...`),await Se().stop(),console.log("Done."),process.exit
958
960
  console.log('PORT:' + port);
959
961
  });
960
962
  process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); fs.unlinkSync(filePath); } catch {} process.exit(0); });
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
963
+ `,h=c("node",["-e",f],{detached:!0,stdio:["ignore","pipe","ignore"]}),S="";await new Promise(w=>{h.stdout?.on("data",j=>{let B=j.toString().match(/PORT:(\d+)/);B&&(S=B[1],w());}),setTimeout(w,3e3);}),h.unref();let _=`http://127.0.0.1:${S}/`;if(console.log(`
964
+ Template: ${i.name}`),console.log(`Preview URL: ${_}`),console.log(`
965
+ To stop: emailr templates stop-preview`),e.open!==!1)try{await(await import('open')).default(_);}catch{}process.exit(0);}catch(t){l(t instanceof Error?t.message:"Failed to preview template"),process.exit(1);}}),o.command("edit <template_id>").description(`Start a live editing session for a template with hot-reload
964
966
 
965
967
  USAGE
966
968
  emailr templates edit <template_id> [options]
@@ -1000,17 +1002,17 @@ EXAMPLES
1000
1002
  SEE ALSO
1001
1003
  emailr templates draft Draft a new template with live preview
1002
1004
  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=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(`
1005
+ 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(a,e)=>{try{let t=d(),n=new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}),i=E.resolve(e.file);console.log(`Fetching template ${a}...`);let r=await n.templates.get(a),s=r.html_content??"";if(x.writeFileSync(i,s,"utf-8"),console.log(`Template saved to: ${i}`),e.foreground){let j=`http://127.0.0.1:${await ee(i,()=>{console.log("File changed - browser refreshed");})}/`;if(console.log(`
1006
+ Template: ${r.name}`),console.log(`Template ID: ${r.id}`),console.log(`Live Preview: ${j}`),console.log(`File: ${i}`),console.log(`
1007
+ Watching for changes... Edit the file and see live updates.`),console.log(`When done, run: emailr templates update ${a} --html-file ${e.file}`),e.open!==!1)try{await(await import('open')).default(j);}catch{}process.on("SIGINT",async()=>{console.log(`
1006
1008
 
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
+ Stopping live preview server...`),await te(),console.log("Done."),console.log(`
1010
+ To save changes: emailr templates update ${a} --html-file ${e.file}`),process.exit(0);});return}let{spawn:c}=await import('child_process'),b=await import('os'),g=E.join(b.tmpdir(),"emailr-preview.pid");try{let w=x.readFileSync(g,"utf-8").trim();process.kill(parseInt(w,10),"SIGTERM");}catch{}let f=`
1009
1011
  const http = require('http');
1010
1012
  const fs = require('fs');
1011
1013
  const path = require('path');
1012
1014
  const os = require('os');
1013
- const filePath = ${JSON.stringify(a)};
1015
+ const filePath = ${JSON.stringify(i)};
1014
1016
  const pidFile = path.join(os.tmpdir(), 'emailr-preview.pid');
1015
1017
  const portFile = path.join(os.tmpdir(), 'emailr-preview.port');
1016
1018
  let clients = [];
@@ -1045,10 +1047,10 @@ To save changes: emailr templates update ${t} --html-file ${e.file}`),process.ex
1045
1047
  });
1046
1048
  });
1047
1049
  process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); } catch {} process.exit(0); });
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
+ `,h=c("node",["-e",f],{detached:!0,stdio:["ignore","pipe","ignore"]}),S="";await new Promise(w=>{h.stdout?.on("data",j=>{let me=j.toString().match(/PORT:(\d+)/);me&&(S=me[1],w());}),setTimeout(w,3e3);}),h.unref();let _=`http://127.0.0.1:${S}/`;if(console.log(`
1051
+ Template: ${r.name}`),console.log(`Template ID: ${r.id}`),console.log(`Live Preview: ${_}`),console.log(`File: ${i}`),console.log(`
1050
1052
  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(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
1053
+ When done:`),console.log(` emailr templates update ${a} --html-file ${e.file}`),console.log(" emailr templates stop-preview"),e.open!==!1)try{await(await import('open')).default(_);}catch{}process.exit(0);}catch(t){l(t instanceof Error?t.message:"Failed to edit template"),process.exit(1);}}),o.command("draft").description(`Start a live drafting session for a new template with hot-reload
1052
1054
 
1053
1055
  USAGE
1054
1056
  emailr templates draft [options]
@@ -1090,7 +1092,7 @@ EXAMPLES
1090
1092
  SEE ALSO
1091
1093
  emailr templates edit Edit an existing template with live preview
1092
1094
  emailr templates create Create the template from your draft
1093
- emailr templates stop-preview Stop the background preview server`).option("--file <file_path>","Path to save the HTML file for drafting","./new-template.html").option("--no-open","Do not automatically open browser").option("--blank","Start with a blank file instead of starter template").option("--foreground","Keep process running in foreground (blocks terminal)").action(async t=>{try{let e=_.resolve(t.file),o;if(t.blank?o="":o=`<!DOCTYPE html>
1095
+ emailr templates stop-preview Stop the background preview server`).option("--file <file_path>","Path to save the HTML file for drafting","./new-template.html").option("--no-open","Do not automatically open browser").option("--blank","Start with a blank file instead of starter template").option("--foreground","Keep process running in foreground (blocks terminal)").action(async a=>{try{let e=E.resolve(a.file),t;if(a.blank?t="":t=`<!DOCTYPE html>
1094
1096
  <html>
1095
1097
  <head>
1096
1098
  <meta charset="UTF-8">
@@ -1125,13 +1127,13 @@ SEE ALSO
1125
1127
  <p><a href="{{unsubscribe_link}}">Unsubscribe</a></p>
1126
1128
  </div>
1127
1129
  </body>
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
+ </html>`,x.writeFileSync(e,t,"utf-8"),console.log(`Template draft created: ${e}`),a.foreground){let h=`http://127.0.0.1:${await ee(e,()=>{console.log("File changed - browser refreshed");})}/`;if(console.log(`
1131
+ Live Preview: ${h}`),console.log(`File: ${e}`),console.log(`
1130
1132
  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(b);}catch{}process.on("SIGINT",async()=>{console.log(`
1133
+ When done, create the template:`),console.log(` emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${a.file}`),a.open!==!1)try{await(await import('open')).default(h);}catch{}process.on("SIGINT",async()=>{console.log(`
1132
1134
 
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
+ Stopping live preview server...`),await te(),console.log("Done."),console.log(`
1136
+ To create template: emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${a.file}`),process.exit(0);});return}let{spawn:n}=await import('child_process'),i=await import('os'),r=E.join(i.tmpdir(),"emailr-preview.pid");try{let f=x.readFileSync(r,"utf-8").trim();process.kill(parseInt(f,10),"SIGTERM");}catch{}let s=`
1135
1137
  const http = require('http');
1136
1138
  const fs = require('fs');
1137
1139
  const path = require('path');
@@ -1171,10 +1173,10 @@ To create template: emailr templates create --name "Template Name" --subject "Em
1171
1173
  });
1172
1174
  });
1173
1175
  process.on('SIGTERM', () => { try { fs.unlinkSync(pidFile); fs.unlinkSync(portFile); } catch {} process.exit(0); });
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
+ `,c=n("node",["-e",s],{detached:!0,stdio:["ignore","pipe","ignore"]}),b="";await new Promise(f=>{c.stdout?.on("data",h=>{let S=h.toString().match(/PORT:(\d+)/);S&&(b=S[1],f());}),setTimeout(f,3e3);}),c.unref();let g=`http://127.0.0.1:${b}/`;if(console.log(`
1177
+ Live Preview: ${g}`),console.log(`File: ${e}`),console.log(`
1176
1178
  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(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
1179
+ When done:`),console.log(` emailr templates create --name "Template Name" --subject "Email Subject" --html-file ${a.file}`),console.log(" emailr templates stop-preview"),a.open!==!1)try{await(await import('open')).default(g);}catch{}process.exit(0);}catch(e){l(e instanceof Error?e.message:"Failed to start draft session"),process.exit(1);}}),o.command("stop-preview").description(`Stop any running background preview server
1178
1180
 
1179
1181
  USAGE
1180
1182
  emailr templates stop-preview
@@ -1191,7 +1193,7 @@ EXAMPLES
1191
1193
  SEE ALSO
1192
1194
  emailr templates edit Start live editing session
1193
1195
  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=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
1196
+ emailr templates preview Preview a template in browser`).action(async()=>{let a=await import('os'),e=E.join(a.tmpdir(),"emailr-preview.pid");try{let t=x.readFileSync(e,"utf-8").trim();process.kill(parseInt(t,10),"SIGTERM"),x.unlinkSync(e),p("Preview server stopped");}catch{p("No preview server running");}}),o}function Ne(){let o=new Command("domains").description(`Manage sending domains
1195
1197
 
1196
1198
  USAGE
1197
1199
  emailr domains <subcommand> [options]
@@ -1272,7 +1274,7 @@ VERIFICATION STATUS
1272
1274
 
1273
1275
  SEE ALSO
1274
1276
  emailr send Send emails using verified domains
1275
- emailr webhooks Receive domain verification events`);return i.command("list").description(`List all domains
1277
+ emailr webhooks Receive domain verification events`);return o.command("list").description(`List all domains
1276
1278
 
1277
1279
  USAGE
1278
1280
  emailr domains list [options]
@@ -1299,7 +1301,7 @@ EXAMPLES
1299
1301
  emailr domains list --format json | jq '.[] | select(.status == "verified")'
1300
1302
 
1301
1303
  # Count domains
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
1304
+ emailr domains list --format json | jq 'length'`).option("--format <format>","Output format (json|table)","table").action(async a=>{try{let e=d(),n=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).domains.list();if(a.format==="json")m(n,"json");else {let i=n.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(i,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list domains"),process.exit(1);}}),o.command("get <domain_id>").description(`Get domain details
1303
1305
 
1304
1306
  USAGE
1305
1307
  emailr domains get <domain_id> [options]
@@ -1326,7 +1328,7 @@ EXAMPLES
1326
1328
  emailr domains get dom_abc123 --format json
1327
1329
 
1328
1330
  # 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=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
1331
+ emailr domains get dom_abc123 --format json | jq '.dns_records'`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).domains.get(a);m(i,e.format);}catch(t){l(t instanceof Error?t.message:"Failed to get domain"),process.exit(1);}}),o.command("add <domain_name>").description(`Add a new domain
1330
1332
 
1331
1333
  USAGE
1332
1334
  emailr domains add <domain_name> [options]
@@ -1379,7 +1381,7 @@ NEXT STEPS
1379
1381
  1. Copy the DNS records shown in the output
1380
1382
  2. Add them to your DNS provider (Cloudflare, Route53, etc.)
1381
1383
  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=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
1384
+ 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(a,e)=>{try{let t=d(),n=new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}),i={domain:a};e.receivingSubdomain&&(i.receivingSubdomain=e.receivingSubdomain);let r=await n.domains.add(i);if(e.format==="json")m(r,"json");else {if(p(`Domain added: ${r.domain}`),u("Add the following DNS records to verify your domain:"),console.log(""),r.dns_records){let s=[{Type:"DKIM",...ae(r.dns_records.dkim)},{Type:"SPF",...ae(r.dns_records.spf)},{Type:"DMARC",...ae(r.dns_records.dmarc)}];m(s,"table");}console.log(""),u(`Run 'emailr domains verify ${r.id}' after adding DNS records`);}}catch(t){l(t instanceof Error?t.message:"Failed to add domain"),process.exit(1);}}),o.command("verify <domain_id>").description(`Verify a domain
1383
1385
 
1384
1386
  USAGE
1385
1387
  emailr domains verify <domain_id> [options]
@@ -1417,7 +1419,7 @@ TROUBLESHOOTING
1417
1419
  1. Run 'check-dns' to see which records are missing
1418
1420
  2. Verify records are correctly configured with your DNS provider
1419
1421
  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=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
1422
+ 4. Try verification again`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).domains.verify(a);e.format==="json"?m(i,"json"):i.verified?p("Domain verified successfully!"):(u(`Domain status: ${i.status}`),i.dkim_status&&u(`DKIM status: ${i.dkim_status}`));}catch(t){l(t instanceof Error?t.message:"Failed to verify domain"),process.exit(1);}}),o.command("check-dns <domain_id>").description(`Check DNS records for a domain
1421
1423
 
1422
1424
  USAGE
1423
1425
  emailr domains check-dns <domain_id> [options]
@@ -1464,7 +1466,7 @@ COMMON ISSUES
1464
1466
 
1465
1467
  Multiple records found:
1466
1468
  - 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=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
1469
+ - Keep only the Emailr-specific record`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).domains.checkDns(a);if(e.format==="json")m(i,"json");else {let r=Object.entries(i).map(([s,c])=>({Record:s,Verified:c.verified,Expected:c.expected||"-",Found:c.found?.join(", ")||"-"}));m(r,"table");}}catch(t){l(t instanceof Error?t.message:"Failed to check DNS"),process.exit(1);}}),o.command("delete <domain_id>").description(`Delete a domain
1468
1470
 
1469
1471
  USAGE
1470
1472
  emailr domains delete <domain_id> [options]
@@ -1494,7 +1496,7 @@ WARNING
1494
1496
  This action is permanent and cannot be undone.
1495
1497
  - Emails from this domain will fail to send
1496
1498
  - 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=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
1499
+ - Re-add the domain if you need to restore functionality`).option("--format <format>","Output format (json|table)","table").action(async a=>{try{let e=d();await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).domains.delete(a),p(`Domain deleted: ${a}`);}catch(e){l(e instanceof Error?e.message:"Failed to delete domain"),process.exit(1);}}),o}function ae(o){return {"Record Type":o.type,Name:o.name,Value:o.value.length>50?o.value.substring(0,47)+"...":o.value,...o.priority!==void 0&&{Priority:o.priority}}}function Ce(){let o=new Command("config").description(`Manage CLI configuration
1498
1500
 
1499
1501
  USAGE
1500
1502
  emailr config <subcommand> [options]
@@ -1557,7 +1559,7 @@ EXAMPLES
1557
1559
  emailr templates list
1558
1560
 
1559
1561
  SEE ALSO
1560
- emailr --help Show all available commands`);return i.command("set <key> <value>").description(`Set a configuration value
1562
+ emailr --help Show all available commands`);return o.command("set <key> <value>").description(`Set a configuration value
1561
1563
 
1562
1564
  USAGE
1563
1565
  emailr config set <key> <value>
@@ -1587,7 +1589,7 @@ EXAMPLES
1587
1589
 
1588
1590
  NOTE
1589
1591
  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"],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
1592
+ over config file values at runtime.`).action(async(a,e)=>{try{let t=["api-key","base-url","format"],n=a.toLowerCase();t.includes(n)||(l(`Invalid config key: ${a}`),u(`Valid keys: ${t.join(", ")}`),process.exit(1));let r={"api-key":"apiKey","base-url":"baseUrl",format:"format"}[n];n==="format"&&!["json","table"].includes(e)&&(l(`Invalid format value: ${e}`),u("Valid formats: json, table"),process.exit(1)),K({[r]:e}),p(`Configuration saved: ${a} = ${n==="api-key"?"***":e}`),u(`Config file: ${I()}`);}catch(t){l(t instanceof Error?t.message:"Failed to save configuration"),process.exit(1);}}),o.command("get <key>").description(`Get a configuration value
1591
1593
 
1592
1594
  USAGE
1593
1595
  emailr config get <key>
@@ -1615,7 +1617,7 @@ EXAMPLES
1615
1617
  emailr config get format
1616
1618
 
1617
1619
  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}`),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
1620
+ emailr config list Show all configuration values`).action(async a=>{try{let e=["api-key","base-url","format"],t=a.toLowerCase();e.includes(t)||(l(`Invalid config key: ${a}`),u(`Valid keys: ${e.join(", ")}`),process.exit(1));let i={"api-key":"apiKey","base-url":"baseUrl",format:"format"}[t],r=ce(i);r?console.log(t==="api-key"?r.substring(0,8)+"..."+r.substring(r.length-4):r):u(`${a} is not set`);}catch(e){l(e instanceof Error?e.message:"Failed to get configuration"),process.exit(1);}}),o.command("list").description(`List all configuration values
1619
1621
 
1620
1622
  USAGE
1621
1623
  emailr config list [options]
@@ -1643,7 +1645,7 @@ EXAMPLES
1643
1645
 
1644
1646
  SEE ALSO
1645
1647
  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=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
1648
+ emailr config path Show config file location`).option("--format <format>","Output format (json|table)","table").action(async a=>{try{let e=d(),t={"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(a.format==="json")m(t,"json");else {let n=Object.entries(t).map(([i,r])=>({Key:i,Value:r}));m(n,"table");}console.log(""),u(`Config file: ${I()}`);}catch(e){e instanceof Error&&e.message.includes("No API key configured")?(u("No configuration found."),u("Run 'emailr config set api-key <your-api-key>' to get started.")):(l(e instanceof Error?e.message:"Failed to list configuration"),process.exit(1));}}),o.command("path").description(`Show the configuration file path
1647
1649
 
1648
1650
  USAGE
1649
1651
  emailr config path
@@ -1670,7 +1672,7 @@ EXAMPLES
1670
1672
  open $(emailr config path)
1671
1673
 
1672
1674
  # View config file contents
1673
- cat $(emailr config path)`).action(()=>{console.log(N());}),i.command("init").description(`Initialize configuration interactively
1675
+ cat $(emailr config path)`).action(()=>{console.log(I());}),o.command("init").description(`Initialize configuration interactively
1674
1676
 
1675
1677
  USAGE
1676
1678
  emailr config init [options]
@@ -1703,7 +1705,7 @@ ALTERNATIVE: ENVIRONMENT VARIABLES
1703
1705
 
1704
1706
  SEE ALSO
1705
1707
  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}),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
1708
+ emailr config list View current configuration`).option("--api-key <key>","API key to use").option("--base-url <url>","Base URL for API").action(async a=>{try{a.apiKey?(K({apiKey:a.apiKey,baseUrl:a.baseUrl}),p("Configuration initialized!"),u(`Config file: ${I()}`)):(u("Initialize your Emailr CLI configuration:"),console.log(""),u("Run with --api-key flag:"),console.log(" emailr config init --api-key <your-api-key>"),console.log(""),u("Or set environment variable:"),console.log(" export EMAILR_API_KEY=<your-api-key>"));}catch(e){l(e instanceof Error?e.message:"Failed to initialize configuration"),process.exit(1);}}),o}function Ue(){let o=new Command("broadcasts").description(`Manage broadcast campaigns
1707
1709
 
1708
1710
  USAGE
1709
1711
  emailr broadcasts <subcommand> [options]
@@ -1808,7 +1810,7 @@ EXAMPLES
1808
1810
  SEE ALSO
1809
1811
  emailr templates Manage email templates
1810
1812
  emailr segments Manage contact segments
1811
- emailr contacts Manage individual contacts`);return i.command("list").description(`List all broadcasts
1813
+ emailr contacts Manage individual contacts`);return o.command("list").description(`List all broadcasts
1812
1814
 
1813
1815
  USAGE
1814
1816
  emailr broadcasts list [options]
@@ -1853,7 +1855,7 @@ EXAMPLES
1853
1855
  emailr broadcasts list --format json
1854
1856
 
1855
1857
  # 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=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
1858
+ 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 a=>{try{let e=d(),t=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n={status:a.status,limit:parseInt(a.limit)};if(a.tags){let r=a.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);r.length>0&&(n.tags=r.join(","));}let i=await t.broadcasts.list(n);if(a.format==="json")m(i,"json");else {if(i.length===0){console.log("No broadcasts found.");return}let r=i.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()}));m(r,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list broadcasts"),process.exit(1);}}),o.command("get <broadcast_id>").description(`Get broadcast details
1857
1859
 
1858
1860
  USAGE
1859
1861
  emailr broadcasts get <broadcast_id> [options]
@@ -1888,7 +1890,7 @@ EXAMPLES
1888
1890
  emailr broadcasts get brd_abc123 --format json
1889
1891
 
1890
1892
  # 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=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
1893
+ emailr broadcasts get brd_abc123 --format json | jq '{sent: .sent_count, opened: .opened_count}'`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).broadcasts.get(a);e.format==="json"?m(i,"json"):m({ID:i.id,Name:i.name,Subject:i.subject,"From Email":i.from_email,"From Name":i.from_name||"-",Status:i.status,Tags:i.tags?.join(", ")||"-","Total Recipients":i.total_recipients||0,"Sent Count":i.sent_count||0,Delivered:i.delivered_count||0,Opened:i.opened_count||0,Clicked:i.clicked_count||0,Bounced:i.bounced_count||0,"Scheduled At":i.scheduled_at||"N/A","Started At":i.started_at||"N/A","Completed At":i.completed_at||"N/A","Created At":i.created_at},"table");}catch(t){l(t instanceof Error?t.message:"Failed to get broadcast"),process.exit(1);}}),o.command("create").description(`Create a new broadcast
1892
1894
 
1893
1895
  USAGE
1894
1896
  emailr broadcasts create --name <name> --subject <subject> --from <email> [options]
@@ -1904,6 +1906,7 @@ OPTIONS
1904
1906
  --from-name <name> Sender display name (e.g. "Acme Inc")
1905
1907
  --reply-to <email> Reply-To email address
1906
1908
  --preview-text <text> Preview text (preheader) shown in email clients
1909
+ --inbox-id <inbox_id> Inbox ID for sender identity defaults
1907
1910
  --template <template_id> Template ID to use for content
1908
1911
  --segment <segment_id> Segment ID to target recipients
1909
1912
  --html <html_content> Inline HTML content (alternative to --template)
@@ -1968,7 +1971,7 @@ EXAMPLES
1968
1971
 
1969
1972
  SEE ALSO
1970
1973
  emailr templates Create and manage email templates
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
1974
+ 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("--inbox-id <inbox_id>","Inbox ID for sender identity defaults").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 a=>{try{let e=d(),t=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n={name:a.name,subject:a.subject,from_email:a.from,from_name:a.fromName,reply_to:a.replyTo,preview_text:a.previewText,inbox_id:a.inboxId,template_id:a.template,segment_id:a.segment,html_content:a.html,text_content:a.text,scheduled_at:a.schedule};a.tags&&(n.tags=a.tags.split(",").map(r=>r.trim().toLowerCase()).filter(Boolean));let i=await t.broadcasts.create(n);a.format==="json"?m(i,"json"):(p("Broadcast created successfully!"),m({ID:i.id,Name:i.name,Status:i.status,Tags:i.tags?.join(", ")||"-","Scheduled At":i.scheduled_at||"Not scheduled"},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create broadcast"),process.exit(1);}}),o.command("send <broadcast_id>").description(`Send a broadcast immediately
1972
1975
 
1973
1976
  USAGE
1974
1977
  emailr broadcasts send <broadcast_id> [options]
@@ -2001,7 +2004,7 @@ EXAMPLES
2001
2004
  NOTE
2002
2005
  Sending is asynchronous. The command returns when sending starts,
2003
2006
  not when all emails are delivered. Use 'emailr broadcasts get' to
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
2007
+ check delivery progress.`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).broadcasts.send(a);e.format==="json"?m(i,"json"):(p("Broadcast sent successfully!"),m({Success:i.success,Sent:i.sent,Total:i.total},"table"));}catch(t){l(t instanceof Error?t.message:"Failed to send broadcast"),process.exit(1);}}),o.command("schedule <broadcast_id>").description(`Schedule a broadcast for future delivery
2005
2008
 
2006
2009
  USAGE
2007
2010
  emailr broadcasts schedule <broadcast_id> --at <datetime> [options]
@@ -2051,7 +2054,7 @@ EXAMPLES
2051
2054
  emailr broadcasts schedule brd_abc123 --at "2024-12-25T10:00:00Z" --format json
2052
2055
 
2053
2056
  SEE ALSO
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
2057
+ emailr broadcasts cancel Cancel a scheduled broadcast`).requiredOption("--at <datetime>","Schedule time (ISO 8601)").option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).broadcasts.schedule(a,e.at);e.format==="json"?m(i,"json"):(p("Broadcast scheduled successfully!"),m({ID:i.id,Name:i.name,Status:i.status,"Scheduled At":i.scheduled_at},"table"));}catch(t){l(t instanceof Error?t.message:"Failed to schedule broadcast"),process.exit(1);}}),o.command("cancel <broadcast_id>").description(`Cancel a scheduled broadcast
2055
2058
 
2056
2059
  USAGE
2057
2060
  emailr broadcasts cancel <broadcast_id> [options]
@@ -2079,7 +2082,7 @@ EXAMPLES
2079
2082
  emailr broadcasts cancel brd_abc123 --format json
2080
2083
 
2081
2084
  NOTE
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
2085
+ Broadcasts that are already 'sending' or 'sent' cannot be cancelled.`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).broadcasts.cancel(a);e.format==="json"?m(i,"json"):p("Broadcast cancelled successfully!");}catch(t){l(t instanceof Error?t.message:"Failed to cancel broadcast"),process.exit(1);}}),o.command("update <broadcast_id>").description(`Update a broadcast
2083
2086
 
2084
2087
  USAGE
2085
2088
  emailr broadcasts update <broadcast_id> [options]
@@ -2098,6 +2101,7 @@ OPTIONS
2098
2101
  --from-name <name> Update sender display name
2099
2102
  --reply-to <email> Update Reply-To email address
2100
2103
  --preview-text <text> Update preview text (preheader)
2104
+ --inbox-id <inbox_id> Update inbox ID for sender identity defaults (use "none" to clear)
2101
2105
  --template <id> Update template ID (use "none" to clear)
2102
2106
  --segment <id> Update segment ID (use "none" to clear)
2103
2107
  --topic <id> Update topic ID (use "none" to clear)
@@ -2131,7 +2135,7 @@ EXAMPLES
2131
2135
  emailr broadcasts update brd_abc123 --name "Updated" --format json
2132
2136
 
2133
2137
  NOTE
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
2138
+ 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("--inbox-id <inbox_id>",'Inbox ID for sender identity (use "none" to clear)').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(a,e)=>{try{let t=d(),n=new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}),i={};e.name&&(i.name=e.name),e.subject&&(i.subject=e.subject),e.from&&(i.from_email=e.from),e.fromName&&(i.from_name=e.fromName),e.replyTo&&(i.reply_to=e.replyTo),e.previewText&&(i.preview_text=e.previewText),e.html&&(i.html_content=e.html),e.text&&(i.text_content=e.text),e.inboxId&&(i.inbox_id=e.inboxId==="none"?null:e.inboxId),e.template&&(i.template_id=e.template==="none"?null:e.template),e.segment&&(i.segment_id=e.segment==="none"?null:e.segment),e.topic&&(i.topic_id=e.topic==="none"?null:e.topic),e.schedule&&(i.scheduled_at=e.schedule==="none"?null:e.schedule),e.tags&&(i.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean)),Object.keys(i).length===0&&(l("No update fields specified. Use --help to see available options."),process.exit(1));let r=await n.broadcasts.update(a,i);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(t){l(t instanceof Error?t.message:"Failed to update broadcast"),process.exit(1);}}),o.command("delete <broadcast_id>").description(`Delete a broadcast
2135
2139
 
2136
2140
  USAGE
2137
2141
  emailr broadcasts delete <broadcast_id> [options]
@@ -2159,7 +2163,7 @@ EXAMPLES
2159
2163
 
2160
2164
  WARNING
2161
2165
  This action is permanent and cannot be undone.
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
2166
+ All delivery statistics will be lost.`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).broadcasts.delete(a);e.format==="json"?m(i,"json"):p("Broadcast deleted successfully!");}catch(t){l(t instanceof Error?t.message:"Failed to delete broadcast"),process.exit(1);}}),o}function ke(){let o=new Command("webhooks").description(`Manage webhooks
2163
2167
 
2164
2168
  USAGE
2165
2169
  emailr webhooks <subcommand> [options]
@@ -2285,7 +2289,7 @@ SIGNATURE VERIFICATION
2285
2289
 
2286
2290
  SEE ALSO
2287
2291
  emailr domains Manage sending domains
2288
- emailr send Send emails directly`);return i.command("list").description(`List all webhooks
2292
+ emailr send Send emails directly`);return o.command("list").description(`List all webhooks
2289
2293
 
2290
2294
  USAGE
2291
2295
  emailr webhooks list [options]
@@ -2312,7 +2316,7 @@ EXAMPLES
2312
2316
  emailr webhooks list --format json | jq '.[] | select(.active == true)'
2313
2317
 
2314
2318
  # Count webhooks
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
2319
+ emailr webhooks list --format json | jq 'length'`).option("--format <format>","Output format (json|table)","table").action(async a=>{try{let e=d(),n=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).webhooks.list();if(a.format==="json")m(n,"json");else {if(n.data.length===0){console.log("No webhooks found.");return}let i=n.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(i,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list webhooks"),process.exit(1);}}),o.command("get <webhook_id>").description(`Get webhook details
2316
2320
 
2317
2321
  USAGE
2318
2322
  emailr webhooks get <webhook_id> [options]
@@ -2343,7 +2347,7 @@ EXAMPLES
2343
2347
  emailr webhooks get whk_abc123 --format json
2344
2348
 
2345
2349
  # Extract just the secret
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
2350
+ emailr webhooks get whk_abc123 --format json | jq -r '.secret'`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).webhooks.get(a);e.format==="json"?m(i,"json"):m({ID:i.id,Name:i.name,URL:i.url,Events:i.events.join(", "),Active:i.active?"Yes":"No",Secret:i.secret,"Created At":i.created_at},"table");}catch(t){l(t instanceof Error?t.message:"Failed to get webhook"),process.exit(1);}}),o.command("create").description(`Create a new webhook
2347
2351
 
2348
2352
  USAGE
2349
2353
  emailr webhooks create --name <name> --url <url> --events <events> [options]
@@ -2417,7 +2421,7 @@ EXAMPLES
2417
2421
 
2418
2422
  NOTE
2419
2423
  Save the webhook secret returned by this command. You'll need it
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
2424
+ 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 a=>{try{let e=d(),t=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n=a.events.split(",").map(r=>r.trim()),i=await t.webhooks.create({name:a.name,url:a.url,events:n});a.format==="json"?m(i,"json"):(p("Webhook created successfully!"),m({ID:i.id,Name:i.name,URL:i.url,Secret:i.secret},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create webhook"),process.exit(1);}}),o.command("update <webhook_id>").description(`Update a webhook
2421
2425
 
2422
2426
  USAGE
2423
2427
  emailr webhooks update <webhook_id> [options]
@@ -2462,7 +2466,7 @@ EXAMPLES
2462
2466
  --events "email.delivered,email.bounced"
2463
2467
 
2464
2468
  # Get JSON output
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
2469
+ 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(a,e)=>{try{let t=d(),n=new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}),i={};e.name&&(i.name=e.name),e.url&&(i.url=e.url),e.events&&(i.events=e.events.split(",").map(s=>s.trim()));let r=await n.webhooks.update(a,i);e.format==="json"?m(r,"json"):(p("Webhook updated successfully!"),m({ID:r.id,Name:r.name,URL:r.url},"table"));}catch(t){l(t instanceof Error?t.message:"Failed to update webhook"),process.exit(1);}}),o.command("enable <webhook_id>").description(`Enable a webhook
2466
2470
 
2467
2471
  USAGE
2468
2472
  emailr webhooks enable <webhook_id> [options]
@@ -2486,7 +2490,7 @@ EXAMPLES
2486
2490
  emailr webhooks enable whk_abc123
2487
2491
 
2488
2492
  # Get JSON output
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
2493
+ emailr webhooks enable whk_abc123 --format json`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).webhooks.enable(a);e.format==="json"?m(i,"json"):p("Webhook enabled successfully!");}catch(t){l(t instanceof Error?t.message:"Failed to enable webhook"),process.exit(1);}}),o.command("disable <webhook_id>").description(`Disable a webhook
2490
2494
 
2491
2495
  USAGE
2492
2496
  emailr webhooks disable <webhook_id> [options]
@@ -2515,7 +2519,7 @@ EXAMPLES
2515
2519
 
2516
2520
  NOTE
2517
2521
  Events are not queued while webhook is disabled. If you need to
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
2522
+ temporarily stop processing, consider handling this in your endpoint.`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).webhooks.disable(a);e.format==="json"?m(i,"json"):p("Webhook disabled successfully!");}catch(t){l(t instanceof Error?t.message:"Failed to disable webhook"),process.exit(1);}}),o.command("delete <webhook_id>").description(`Delete a webhook
2519
2523
 
2520
2524
  USAGE
2521
2525
  emailr webhooks delete <webhook_id> [options]
@@ -2543,7 +2547,7 @@ EXAMPLES
2543
2547
 
2544
2548
  WARNING
2545
2549
  This action is permanent and cannot be undone.
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
2550
+ Create a new webhook if you need to restore functionality.`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).webhooks.delete(a);e.format==="json"?m(i,"json"):p("Webhook deleted successfully!");}catch(t){l(t instanceof Error?t.message:"Failed to delete webhook"),process.exit(1);}}),o}function Pe(){let o=new Command("segments").description(`Manage contact segments
2547
2551
 
2548
2552
  USAGE
2549
2553
  emailr segments <subcommand> [options]
@@ -2658,7 +2662,7 @@ EXAMPLES
2658
2662
 
2659
2663
  SEE ALSO
2660
2664
  emailr contacts Manage individual contacts
2661
- emailr broadcasts Send emails to segments`);return i.command("list").description(`List all segments
2665
+ emailr broadcasts Send emails to segments`);return o.command("list").description(`List all segments
2662
2666
 
2663
2667
  USAGE
2664
2668
  emailr segments list [options]
@@ -2683,7 +2687,7 @@ EXAMPLES
2683
2687
  emailr segments list --format json
2684
2688
 
2685
2689
  # Pipe JSON to jq for processing
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
2690
+ 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 a=>{try{let e=d(),t=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n={};if(a.tags){let r=a.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean);r.length>0&&(n.tags=r.join(","));}let i=await t.segments.list(n);if(a.format==="json")m(i,"json");else {if(i.length===0){console.log("No segments found.");return}let r=i.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()}));m(r,"table");}}catch(e){l(e instanceof Error?e.message:"Failed to list segments"),process.exit(1);}}),o.command("get <segment_id>").description(`Get segment details
2687
2691
 
2688
2692
  USAGE
2689
2693
  emailr segments get <segment_id> [options]
@@ -2710,7 +2714,7 @@ EXAMPLES
2710
2714
  emailr segments get seg_abc123 --format json
2711
2715
 
2712
2716
  # Pipe JSON to jq to view conditions
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
2717
+ emailr segments get seg_abc123 --format json | jq '.conditions'`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).segments.get(a);e.format==="json"?m(i,"json"):m({ID:i.id,Name:i.name,Description:i.description||"N/A",Conditions:JSON.stringify(i.conditions),Tags:i.tags?.join(", ")||"-","Created At":i.created_at,"Updated At":i.updated_at},"table");}catch(t){l(t instanceof Error?t.message:"Failed to get segment"),process.exit(1);}}),o.command("create").description(`Create a new segment
2714
2718
 
2715
2719
  USAGE
2716
2720
  emailr segments create --name <segment_name> --conditions <json> [options]
@@ -2760,7 +2764,7 @@ EXAMPLES
2760
2764
  # Get JSON output for scripting
2761
2765
  emailr segments create --name "Test" \\
2762
2766
  --conditions '[{"field": "subscribed", "operator": "equals", "value": true}]' \\
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
2767
+ --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 a=>{try{let e=d(),t=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n;try{n=JSON.parse(a.conditions);}catch{l("Invalid JSON for --conditions"),process.exit(1);}let i={name:a.name,description:a.description,conditions:n};a.tags&&(i.tags=a.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let r=await t.segments.create(i);a.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);}}),o.command("update <segment_id>").description(`Update a segment
2764
2768
 
2765
2769
  USAGE
2766
2770
  emailr segments update <segment_id> [options]
@@ -2806,7 +2810,7 @@ EXAMPLES
2806
2810
  --conditions '[{"field": "metadata.plan", "operator": "equals", "value": "enterprise"}]'
2807
2811
 
2808
2812
  # Get JSON output
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
2813
+ 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(a,e)=>{try{let t=d(),n=new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}),i={};if(e.name&&(i.name=e.name),e.description&&(i.description=e.description),e.conditions)try{i.conditions=JSON.parse(e.conditions);}catch{l("Invalid JSON for --conditions"),process.exit(1);}e.tags&&(i.tags=e.tags.split(",").map(s=>s.trim().toLowerCase()).filter(Boolean));let r=await n.segments.update(a,i);e.format==="json"?m(r,"json"):(p("Segment updated successfully!"),m({ID:r.id,Name:r.name,Tags:r.tags?.join(", ")||"-"},"table"));}catch(t){l(t instanceof Error?t.message:"Failed to update segment"),process.exit(1);}}),o.command("delete <segment_id>").description(`Delete a segment
2810
2814
 
2811
2815
  USAGE
2812
2816
  emailr segments delete <segment_id>
@@ -2831,7 +2835,7 @@ EXAMPLES
2831
2835
 
2832
2836
  WARNING
2833
2837
  This action is permanent and cannot be undone.
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
2838
+ Any broadcasts targeting this segment will need to be updated.`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).segments.delete(a);e.format==="json"?m(i,"json"):p("Segment deleted successfully!");}catch(t){l(t instanceof Error?t.message:"Failed to delete segment"),process.exit(1);}}),o.command("count <segment_id>").description(`Get the number of contacts in a segment
2835
2839
 
2836
2840
  USAGE
2837
2841
  emailr segments count <segment_id> [options]
@@ -2862,7 +2866,7 @@ EXAMPLES
2862
2866
 
2863
2867
  TIP
2864
2868
  Use this before sending broadcasts to verify your segment
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>
2869
+ targets the expected number of contacts.`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).segments.getContactsCount(a);e.format==="json"?m(i,"json"):console.log(`Segment contains ${i.count} contacts.`);}catch(t){l(t instanceof Error?t.message:"Failed to get segment count"),process.exit(1);}}),o}function yt(o){try{let a=o.startsWith("http")?o:`http://localhost${o}`,e=new URL(a);return {key:e.searchParams.get("key")??void 0,code:e.searchParams.get("code")??void 0,state:e.searchParams.get("state")??void 0,error:e.searchParams.get("error")??void 0,message:e.searchParams.get("message")??void 0}}catch{return {}}}function St(){return `<!DOCTYPE html>
2866
2870
  <html lang="en">
2867
2871
  <head>
2868
2872
  <meta charset="UTF-8">
@@ -2908,7 +2912,7 @@ TIP
2908
2912
  <p>You can close this window and return to your terminal.</p>
2909
2913
  </div>
2910
2914
  </body>
2911
- </html>`}function te(i){return `<!DOCTYPE html>
2915
+ </html>`}function ie(o){return `<!DOCTYPE html>
2912
2916
  <html lang="en">
2913
2917
  <head>
2914
2918
  <meta charset="UTF-8">
@@ -2959,24 +2963,65 @@ TIP
2959
2963
  <div class="icon">\u2717</div>
2960
2964
  <h1>Login Failed</h1>
2961
2965
  <p>Something went wrong during authentication.</p>
2962
- <div class="error-message">${i.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}</div>
2966
+ <div class="error-message">${o.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}</div>
2963
2967
  <p style="margin-top: 1rem;">Please close this window and try again.</p>
2964
2968
  </div>
2965
2969
  </body>
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=`---
2970
+ </html>`}function Ae(){let o=null,a=0,e=null,t=null,n=null;return {async start(){return new Promise((i,r)=>{o=at.createServer((s,c)=>{if(s.method!=="GET"||!s.url?.startsWith("/callback")){c.writeHead(404,{"Content-Type":"text/plain"}),c.end("Not Found");return}let b=yt(s.url);if(n&&b.state!==n){c.writeHead(400,{"Content-Type":"text/html"}),c.end(ie("Security verification failed. State parameter mismatch.")),e&&e({success:false,error:"State parameter mismatch"});return}if(b.error){let f=b.message||b.error;c.writeHead(200,{"Content-Type":"text/html"}),c.end(ie(f)),e&&e({success:false,error:f});return}let g=b.key||b.code;if(g){c.writeHead(200,{"Content-Type":"text/html"}),c.end(St()),e&&e({success:true,apiKey:g});return}c.writeHead(400,{"Content-Type":"text/html"}),c.end(ie("Invalid callback: missing required parameters.")),e&&e({success:false,error:"Invalid callback: missing required parameters"});}),o.listen(0,"127.0.0.1",()=>{let s=o.address();s&&typeof s=="object"?(a=s.port,i({port:a,url:`http://127.0.0.1:${a}/callback`})):r(new Error("Failed to get server address"));}),o.on("error",s=>{r(new Error(`Failed to start callback server: ${s.message}`));});})},async waitForCallback(i,r){return n=i,new Promise(s=>{e=s,t=setTimeout(()=>{e&&e({success:false,error:"Login timed out. Please try again."});},r);})},async stop(){if(t&&(clearTimeout(t),t=null),o)return new Promise(i=>{o.close(()=>{o=null,i();});})}}}function Re(){return vt.randomBytes(32).toString("hex")}function Me(o){return new Promise(a=>{let e=process.platform,t;switch(e){case "darwin":t=`open "${o}"`;break;case "win32":t=`start "" "${o}"`;break;default:t=`xdg-open "${o}"`;break}exec(t,n=>{a(!n);});})}var X=120,Ot=process.env.EMAILR_WEB_URL||"https://app.emailr.dev";function Et(o,a){let e=`http://127.0.0.1:${a}/callback`,t=new URLSearchParams({state:o,callback_url:e});return `${Ot}/consent/authorize?${t.toString()}`}function De(){return new Command("login").description("Log in to Emailr via browser authentication").option("-t, --timeout <seconds>","Timeout in seconds",String(X)).option("--no-browser","Don't automatically open the browser").action(async a=>{await _t({timeout:parseInt(a.timeout,10)||X,noBrowser:a.browser===false});})}async function _t(o){let a=Ae(),e=(o.timeout||X)*1e3;try{u("Starting authentication server...");let{port:t,url:n}=await a.start(),i=Re(),r=Et(i,t);console.log(""),u("Authorization URL:"),console.log(` ${r}`),console.log(""),o.noBrowser?u("Please open the URL above in your browser to continue."):await Me(r)?u("Browser opened. Please complete authentication in your browser."):(N("Could not open browser automatically."),u("Please open the URL above in your browser to continue.")),console.log(""),u(`Waiting for authentication (timeout: ${o.timeout||X}s)...`);let s=await a.waitForCallback(i,e);s.success&&s.apiKey?(K({apiKey:s.apiKey}),console.log(""),p("Login successful!"),u(`API key saved to: ${I()}`),u("You can now use the Emailr CLI.")):(console.log(""),l(s.error||"Authentication failed."),u("Please try again or use manual configuration:"),console.log(" emailr config set api-key <your-api-key>"),process.exit(1));}catch(t){console.log(""),l(t instanceof Error?t.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 a.stop();}}var re=E.join(V.homedir(),".config","opencode","skills","emailr-cli"),Ct=`---
2967
2971
  name: emailr-cli
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.
2972
+ description: Operate the Emailr CLI to send emails, reply to threads, manage contacts, templates, inboxes, domains, broadcasts, webhooks, and segments. Includes thread reply auto-resolution and live preview editing for templates.
2969
2973
  ---
2970
2974
  # Emailr CLI
2971
2975
 
2972
2976
  ## Quick start
2973
2977
  - Ensure auth: \`emailr login\` (browser-based) or \`emailr config set api-key <token>\`.
2974
2978
  - Prefer machine-readable output with \`--format json\` on listing/get commands.
2979
+ - Run \`emailr skill\` for the full agent skill guide.
2980
+
2981
+ ## Thread Reply (KEY FEATURE)
2982
+ Reply to any thread with just the thread ID and HTML \u2014 from, to, subject, reply-to are all auto-resolved from the thread's inbox:
2983
+ \`\`\`bash
2984
+ emailr threads reply <thread-id> --html "<p>Thanks!</p>" --format json
2985
+ emailr threads reply <thread-id> --html "<p>Done</p>" --cc "boss@co.com" --format json
2986
+ \`\`\`
2987
+
2988
+ ## Thread Drafts
2989
+ \`\`\`bash
2990
+ emailr threads draft save <thread-id> --html "<p>WIP</p>"
2991
+ emailr threads draft get <thread-id> --format json
2992
+ emailr threads draft delete <thread-id>
2993
+ \`\`\`
2994
+
2995
+ ## Thread Management
2996
+ \`\`\`bash
2997
+ emailr threads list --format json
2998
+ emailr threads list --label sent --inbox-id <id>
2999
+ emailr threads get <thread-id> --format json
3000
+ emailr threads label <thread-id> --add starred
3001
+ emailr threads label <thread-id> --remove spam,trash
3002
+ \`\`\`
3003
+ Labels: inbox, sent, starred, spam, trash, archived, all
3004
+
3005
+ ## Inbox Management
3006
+ \`\`\`bash
3007
+ emailr inboxes list --format json
3008
+ emailr inboxes create --name "Support" --username support --domain example.com
3009
+ emailr inboxes get <id> --format json
3010
+ emailr inboxes update <id> --name "New Name"
3011
+ emailr inboxes delete <id>
3012
+ \`\`\`
3013
+
3014
+ ## Inbound Emails
3015
+ \`\`\`bash
3016
+ emailr inbox list --format json
3017
+ emailr inbox get <email-id> --format json
3018
+ emailr inbox thread <email-id> --format json
3019
+ emailr inbox reply <email-id> --html "<p>Reply</p>"
3020
+ emailr inbox forward <email-id> --to other@x.com
3021
+ \`\`\`
2975
3022
 
2976
3023
  ## LIVE TEMPLATE EDITING (Agentic Workflow)
2977
3024
 
2978
- IMPORTANT: Always use \`--background\` flag so the command returns immediately and you can continue editing.
2979
-
2980
3025
  ### Edit existing template with live preview
2981
3026
  1. Start live editing: \`emailr templates edit <template_id> --file ./template.html --background\`
2982
3027
  2. Edit the file at ./template.html - browser auto-refreshes on every save
@@ -2989,21 +3034,56 @@ IMPORTANT: Always use \`--background\` flag so the command returns immediately a
2989
3034
  3. When satisfied: \`emailr templates create --name "Template Name" --subject "Subject" --html-file ./new-template.html\`
2990
3035
  4. Stop server: \`emailr templates stop-preview\`
2991
3036
 
3037
+ ### Template preview sharing
3038
+ 1. Fetch: \`emailr templates fetch <id> --output template.html\`
3039
+ 2. Edit locally
3040
+ 3. Push preview: \`emailr templates push-preview <id> --html-file template.html\`
3041
+ 4. Share preview URL for feedback
3042
+ 5. Publish: \`emailr templates update <id> --html-file template.html\`
3043
+
2992
3044
  ## Common commands
2993
3045
 
2994
3046
  ### Sending emails
2995
- - Plain text: \`emailr send --to user@example.com --from no-reply@example.com --subject "Hi" --text "Hello"\`
2996
- - HTML: \`emailr send --to user@example.com --from no-reply@example.com --subject "Hi" --html "<h1>Hello</h1>"\`
2997
- - With template: \`emailr send --to user@example.com --from no-reply@example.com --template tmpl_xxx --template-data '{"name":"Ada"}'\`
2998
-
2999
- ### Templates
3000
- - List: \`emailr templates list --format json\`
3001
- - Get: \`emailr templates get <id> --format json\`
3002
- - Preview: \`emailr templates preview <id>\`
3003
- - Create: \`emailr templates create --name "Name" --subject "Subject" --html-file ./template.html\`
3004
- - Update: \`emailr templates update <id> --html-file ./template.html\`
3005
- - Delete: \`emailr templates delete <id>\`
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
3047
+ \`\`\`bash
3048
+ emailr send --to user@x.com --subject "Hi" --html "<h1>Hello</h1>"
3049
+ emailr send --to user@x.com --template tmpl_xxx --template-data '{"name":"Ada"}'
3050
+ emailr send --to user@x.com --html-file ./email.html --cc "other@x.com"
3051
+ \`\`\`
3052
+
3053
+ ### Contacts
3054
+ \`\`\`bash
3055
+ emailr contacts list --format json
3056
+ emailr contacts create --email user@x.com --first-name "Jo" --metadata '{"plan":"pro"}'
3057
+ emailr contacts update <id> --tags "vip,active"
3058
+ emailr contacts delete <id>
3059
+ \`\`\`
3060
+
3061
+ ### Segments
3062
+ \`\`\`bash
3063
+ emailr segments create --name "Active" --conditions '[{"field":"subscribed","operator":"equals","value":true}]'
3064
+ emailr segments count <id>
3065
+ \`\`\`
3066
+
3067
+ ### Broadcasts
3068
+ \`\`\`bash
3069
+ emailr broadcasts create --name "Newsletter" --subject "News" --from news@x.com --template tpl_abc --segment seg_xyz
3070
+ emailr broadcasts send <id>
3071
+ emailr broadcasts schedule <id> --at "2025-12-25T10:00:00Z"
3072
+ \`\`\`
3073
+
3074
+ ### Webhooks
3075
+ \`\`\`bash
3076
+ emailr webhooks create --name "Tracker" --url "https://x.com/hook" --events "email.delivered,email.bounced"
3077
+ emailr webhooks list --format json
3078
+ \`\`\`
3079
+
3080
+ ### Domains
3081
+ \`\`\`bash
3082
+ emailr domains add example.com
3083
+ emailr domains verify example.com
3084
+ emailr domains list
3085
+ \`\`\`
3086
+ `;function Ut(){try{return execSync("which opencode",{stdio:"ignore"}),!0}catch{return false}}function kt(){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 Pt(){x.existsSync(re)||x.mkdirSync(re,{recursive:true});let o=E.join(re,"SKILL.md");x.writeFileSync(o,Ct,"utf-8");}function Fe(){return new Command("agent").description(`Launch an AI agent with Emailr CLI expertise
3007
3087
 
3008
3088
  This opens OpenCode (opencode.ai), an AI coding agent that knows how to use all Emailr CLI commands.
3009
3089
  The agent can help you:
@@ -3011,10 +3091,10 @@ The agent can help you:
3011
3091
  - Manage contacts, broadcasts, and segments
3012
3092
  - Configure domains and webhooks
3013
3093
 
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(`
3094
+ 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 a=>{Ut()||(a.install?kt()||(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(`
3095
+ 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{Pt(),p("Emailr CLI skill loaded");}catch(n){N(`Could not install skill: ${n instanceof Error?n.message:String(n)}`);}let e=[];a.model&&e.push("--model",a.model),console.log(`
3016
3096
  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.
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)
3097
+ `);let t=spawn("opencode",e,{stdio:"inherit",env:process.env});t.on("error",n=>{l(`Failed to start agent: ${n.message}`),process.exit(1);}),t.on("exit",n=>{process.exit(n??0);});})}function He(){let o=new Command("inbox").description(`Manage inbound emails (inbox)
3018
3098
 
3019
3099
  USAGE
3020
3100
  emailr inbox <subcommand> [options]
@@ -3043,6 +3123,9 @@ EXAMPLES
3043
3123
  # Search by sender email
3044
3124
  emailr inbox list --email sender@example.com
3045
3125
 
3126
+ # Filter by inbox
3127
+ emailr inbox list --inbox-id <inbox-uuid>
3128
+
3046
3129
  # View a specific email
3047
3130
  emailr inbox get <email-id>
3048
3131
 
@@ -3063,7 +3146,7 @@ EXAMPLES
3063
3146
 
3064
3147
  SEE ALSO
3065
3148
  emailr send Send individual emails
3066
- emailr emails View all emails (sent and received)`);return i.command("list").description(`List received emails
3149
+ emailr emails View all emails (sent and received)`);return o.command("list").description(`List received emails
3067
3150
 
3068
3151
  USAGE
3069
3152
  emailr inbox list [options]
@@ -3076,6 +3159,7 @@ OPTIONS
3076
3159
  --page <number> Page number (default: 1)
3077
3160
  --email <address> Filter by sender email (partial match)
3078
3161
  --domain <domain> Filter by sender domain
3162
+ --inbox-id <id> Filter by inbox ID
3079
3163
  --format <format> Output format: json | table (default: table)
3080
3164
 
3081
3165
  EXAMPLES
@@ -3085,9 +3169,12 @@ EXAMPLES
3085
3169
  emailr inbox list --domain example.com
3086
3170
  emailr inbox list --format json
3087
3171
 
3172
+ # Filter by inbox
3173
+ emailr inbox list --inbox-id <inbox-uuid>
3174
+
3088
3175
  OUTPUT FORMATS
3089
3176
  --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
3177
+ --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("--inbox-id <inbox_id>","Filter by inbox ID").option("--format <format>","Output format (json|table)","table").action(async a=>{try{let e=d(),t=new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}),n={status:"received",page:parseInt(a.page),limit:Math.min(parseInt(a.limit),100),email:a.email,domain:a.domain};a.inboxId&&(n.inbox_id=a.inboxId);let i=await t.emails.list(n);if(a.format==="json")m(i,"json");else {if(!i.data||i.data.length===0){u("No emails in inbox");return}let s=i.data.map(c=>({ID:c.id,From:c.from_email,Subject:Ke(c.subject||"(no subject)",40),To:c.to_email,Date:se(c.created_at),Attachments:c.attachments?.length||0}));m(s,"table"),u(`Page ${i.pagination.page} of ${i.pagination.pages} (${i.pagination.total} total)`);}}catch(e){l(e instanceof Error?e.message:"Failed to list inbox"),process.exit(1);}}),o.command("get <id>").description(`View a received email
3091
3178
 
3092
3179
  USAGE
3093
3180
  emailr inbox get <email-id> [options]
@@ -3108,7 +3195,7 @@ EXAMPLES
3108
3195
 
3109
3196
  OUTPUT FORMATS
3110
3197
  --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
3198
+ --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(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).emails.get(a);if(e.format==="json")m(i,"json");else {if(m({ID:i.id,From:i.from_email,To:i.to_email,Subject:i.subject||"(no subject)",Date:se(i.created_at),Status:i.status,"Thread ID":i.thread_id||"-","Parent Email":i.parent_email_id||"-",Attachments:i.attachments?.length||0},"table"),console.log(""),e.content==="html")console.log(i.html_content||"(no HTML content)");else if(e.content==="text")console.log(i.text_content||"(no text content)");else {let s=i.text_content||i.html_content?.replace(/<[^>]*>/g,"")||"(no content)";console.log(s);}if(i.attachments&&i.attachments.length>0){console.log(""),u("Attachments:");let s=i.attachments.map(c=>({Filename:c.filename,Type:c.contentType||c.content_type||"-",Size:Rt(c.size||0)}));m(s,"table");}}}catch(t){l(t instanceof Error?t.message:"Failed to get email"),process.exit(1);}}),o.command("thread <id>").description(`View conversation thread for an email
3112
3199
 
3113
3200
  USAGE
3114
3201
  emailr inbox thread <email-id> [options]
@@ -3127,7 +3214,7 @@ EXAMPLES
3127
3214
 
3128
3215
  OUTPUT FORMATS
3129
3216
  --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
3217
+ --format table Chronological conversation view with direction indicators (default)`).option("--format <format>","Output format (json|table)","table").action(async(a,e)=>{try{let t=d(),n=new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}),i=await n.emails.get(a),r=i.thread_id||i.id,s=[],c=new Set;if(s.push(i),c.add(i.id),i.thread_id&&i.thread_id!==i.id)try{let f=await n.emails.get(i.thread_id);c.has(f.id)||(s.push(f),c.add(f.id));}catch{}let b=await n.emails.list({limit:100});if(b.data)for(let f of b.data)c.has(f.id)||(f.thread_id===r||f.parent_email_id===i.id||f.id===i.parent_email_id)&&(s.push(f),c.add(f.id));if(s.sort((f,h)=>new Date(f.created_at).getTime()-new Date(h.created_at).getTime()),e.format==="json")m(s,"json");else {s.length<=1&&u("No other emails in this conversation"),u(`Conversation (${s.length} email${s.length!==1?"s":""}):`),console.log("");for(let f of s){let h=f.status==="received"?"\u2190 IN ":"\u2192 OUT",S=f.id===a?" \u25C0 (selected)":"";console.log(`${h} ${se(f.created_at)}${S}`),console.log(` From: ${f.from_email}`),console.log(` To: ${f.to_email}`),console.log(` Subject: ${f.subject||"(no subject)"}`);let _=f.text_content||f.html_content?.replace(/<[^>]*>/g,"")||"";_&&console.log(` ${Ke(_.trim(),120)}`),console.log("");}}await new Promise(f=>process.stdout.write("",()=>f())),process.exit(0);}catch(t){l(t instanceof Error?t.message:"Failed to load thread"),process.exit(1);}}),o.command("reply <id>").description(`Reply to a received email
3131
3218
 
3132
3219
  USAGE
3133
3220
  emailr inbox reply <email-id> [options]
@@ -3163,7 +3250,7 @@ EXAMPLES
3163
3250
 
3164
3251
  OUTPUT FORMATS
3165
3252
  --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
3253
+ --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(a,e)=>{try{let t=d(),n=new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}),i=await n.emails.get(a),r=e.html,s=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{s=readFileSync(e.textFile,"utf-8");}catch{l(`Failed to read text file: ${e.textFile}`),process.exit(1);}!r&&!s&&(l("Reply content is required. Use --html, --text, --html-file, or --text-file"),process.exit(1));let c=e.subject||(i.subject?.startsWith("Re:")?i.subject:`Re: ${i.subject||""}`),b={to:i.from_email,from:e.from||i.to_email,subject:c,html:r||void 0,text:s||r?.replace(/<[^>]*>/g,"")||void 0,replyTo:{in_reply_to:i.message_id||i.ses_message_id,thread_id:i.thread_id||i.id,parent_email_id:i.id}};if(e.cc){let h=e.cc.split(",").map(S=>S.trim());b.cc=h.length===1?h[0]:h;}if(e.bcc){let h=e.bcc.split(",").map(S=>S.trim());b.bcc=h.length===1?h[0]:h;}let g=await n.emails.send(b);e.format==="json"?m(g,"json"):(p("Reply sent successfully!"),m({"Message ID":g.message_id,To:i.from_email,Subject:c,Status:g.status},"table")),await new Promise(h=>process.stdout.write("",()=>h())),process.exit(0);}catch(t){l(t instanceof Error?t.message:"Failed to send reply"),process.exit(1);}}),o.command("forward <id>").description(`Forward a received email
3167
3254
 
3168
3255
  USAGE
3169
3256
  emailr inbox forward <email-id> --to <recipients> [options]
@@ -3189,7 +3276,399 @@ EXAMPLES
3189
3276
 
3190
3277
  OUTPUT FORMATS
3191
3278
  --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
3279
+ --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(a,e)=>{try{let t=d(),n=new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}),i=e.to.split(",").map(c=>c.trim()).filter(Boolean),r=await n.emails.forward({email_id:a,to:i.length===1?i[0]:i,message:e.message});e.format==="json"?m(r,"json"):(p("Email forwarded successfully!"),m({"Message ID":r.message_id,To:i.join(", "),Recipients:r.recipients,Status:r.status},"table")),await new Promise(c=>process.stdout.write("",()=>c())),process.exit(0);}catch(t){l(t instanceof Error?t.message:"Failed to forward email"),process.exit(1);}}),o}function Ke(o,a){return o.length<=a?o:o.slice(0,a-1)+"\u2026"}function se(o){return new Date(o).toLocaleString()}function Rt(o){return o<1024?o+" B":o<1024*1024?(o/1024).toFixed(1)+" KB":(o/(1024*1024)).toFixed(1)+" MB"}function qe(){let o=new Command("inboxes").description(`Manage inboxes
3280
+
3281
+ USAGE
3282
+ emailr inboxes <subcommand> [options]
3283
+
3284
+ DESCRIPTION
3285
+ Create, list, get, update, and delete inboxes. Each inbox represents
3286
+ a distinct email identity with a name, username, and domain.
3287
+
3288
+ SUBCOMMANDS
3289
+ create Create a new inbox
3290
+ list List all inboxes
3291
+ get <id> Get an inbox by ID
3292
+ update <id> Update an inbox
3293
+ delete <id> Delete an inbox
3294
+
3295
+ EXAMPLES
3296
+ # Create an inbox
3297
+ emailr inboxes create --name "Support" --username support --domain example.com
3298
+
3299
+ # List all inboxes
3300
+ emailr inboxes list
3301
+
3302
+ # Get inbox details
3303
+ emailr inboxes get inb_abc123
3304
+
3305
+ # Update inbox name
3306
+ emailr inboxes update inb_abc123 --name "Customer Support"
3307
+
3308
+ # Delete an inbox
3309
+ emailr inboxes delete inb_abc123`);return o.command("create").description(`Create a new inbox
3310
+
3311
+ USAGE
3312
+ emailr inboxes create --name <name> --username <username> --domain <domain> [options]
3313
+
3314
+ DESCRIPTION
3315
+ Creates a new inbox with the specified name, username, and domain.
3316
+ The domain must be verified for your organization.
3317
+
3318
+ OPTIONS
3319
+ --name <name> Display name for the inbox (required)
3320
+ --username <username> Username part of the email address (required)
3321
+ --domain <domain> Domain for the email address (required)
3322
+ --format <format> Output format: json | table (default: table)
3323
+
3324
+ EXAMPLES
3325
+ # Create a support inbox
3326
+ emailr inboxes create --name "Support" --username support --domain example.com
3327
+
3328
+ # Create and get JSON output
3329
+ emailr inboxes create --name "Sales" --username sales --domain example.com --format json`).requiredOption("--name <name>","Inbox display name").requiredOption("--username <username>","Username for the email address").requiredOption("--domain <domain>","Domain for the email address").option("--format <format>","Output format: json | table","table").action(async a=>{try{let e=d(),n=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).inboxes.create({name:a.name,username:a.username,domain:a.domain});a.format==="json"?m(n,"json"):(p(`Inbox created: ${n.id}`),m({ID:n.id,Name:n.name,"From Address":n.from_address,"Inbound Address":n.inbound_address,Created:n.created_at},"table"));}catch(e){l(e instanceof Error?e.message:"Failed to create inbox"),process.exit(1);}}),o.command("list").description(`List all inboxes
3330
+
3331
+ USAGE
3332
+ emailr inboxes list [options]
3333
+
3334
+ DESCRIPTION
3335
+ Retrieves all inboxes for your organization.
3336
+
3337
+ OPTIONS
3338
+ --format <format> Output format: json | table (default: table)
3339
+
3340
+ EXAMPLES
3341
+ # List all inboxes
3342
+ emailr inboxes list
3343
+
3344
+ # Get JSON output
3345
+ emailr inboxes list --format json`).option("--format <format>","Output format: json | table","table").action(async a=>{try{let e=d(),n=await new Emailr({apiKey:e.apiKey,baseUrl:e.baseUrl}).inboxes.list();if(a.format==="json")m(n,"json");else {if(n.length===0){console.log("No inboxes found.");return}let i=n.map(r=>({ID:r.id,Name:r.name,"From Address":r.from_address,"Inbound Address":r.inbound_address,Created:new Date(r.created_at).toLocaleDateString()}));m(i,"table"),console.log(`
3346
+ Total: ${n.length}`);}}catch(e){l(e instanceof Error?e.message:"Failed to list inboxes"),process.exit(1);}}),o.command("get <inbox_id>").description(`Get an inbox by ID
3347
+
3348
+ USAGE
3349
+ emailr inboxes get <inbox_id> [options]
3350
+
3351
+ DESCRIPTION
3352
+ Retrieves detailed information about a specific inbox.
3353
+
3354
+ ARGUMENTS
3355
+ <inbox_id> The unique inbox identifier
3356
+
3357
+ OPTIONS
3358
+ --format <format> Output format: json | table (default: table)
3359
+
3360
+ EXAMPLES
3361
+ # Get inbox details
3362
+ emailr inboxes get inb_abc123
3363
+
3364
+ # Get JSON output
3365
+ emailr inboxes get inb_abc123 --format json`).option("--format <format>","Output format: json | table","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).inboxes.get(a);e.format==="json"?m(i,"json"):m({ID:i.id,Name:i.name,Username:i.username,Domain:i.domain,"From Address":i.from_address,"Inbound Address":i.inbound_address,Created:i.created_at,Updated:i.updated_at},"table");}catch(t){l(t instanceof Error?t.message:"Failed to get inbox"),process.exit(1);}}),o.command("update <inbox_id>").description(`Update an inbox
3366
+
3367
+ USAGE
3368
+ emailr inboxes update <inbox_id> --name <name> [options]
3369
+
3370
+ DESCRIPTION
3371
+ Updates an inbox's display name. Username and domain are immutable
3372
+ after creation.
3373
+
3374
+ ARGUMENTS
3375
+ <inbox_id> The unique inbox identifier
3376
+
3377
+ OPTIONS
3378
+ --name <name> New display name for the inbox
3379
+ --format <format> Output format: json | table (default: table)
3380
+
3381
+ EXAMPLES
3382
+ # Update inbox name
3383
+ emailr inboxes update inb_abc123 --name "Customer Support"
3384
+
3385
+ # Get JSON output
3386
+ emailr inboxes update inb_abc123 --name "New Name" --format json`).option("--name <name>","New inbox display name").option("--format <format>","Output format: json | table","table").action(async(a,e)=>{try{e.name||(l("No update fields specified. Use --name to update the inbox name."),process.exit(1));let t=d(),n=new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}),i={};e.name&&(i.name=e.name);let r=await n.inboxes.update(a,i);e.format==="json"?m(r,"json"):(p(`Inbox updated: ${r.id}`),m({ID:r.id,Name:r.name,"From Address":r.from_address,"Inbound Address":r.inbound_address,Updated:r.updated_at},"table"));}catch(t){l(t instanceof Error?t.message:"Failed to update inbox"),process.exit(1);}}),o.command("delete <inbox_id>").description(`Delete an inbox
3387
+
3388
+ USAGE
3389
+ emailr inboxes delete <inbox_id> [options]
3390
+
3391
+ DESCRIPTION
3392
+ Permanently deletes an inbox. Existing emails associated with this
3393
+ inbox will have their inbox reference cleared but are preserved.
3394
+
3395
+ ARGUMENTS
3396
+ <inbox_id> The unique inbox identifier
3397
+
3398
+ OPTIONS
3399
+ --format <format> Output format: json | table (default: table)
3400
+
3401
+ EXAMPLES
3402
+ # Delete an inbox
3403
+ emailr inboxes delete inb_abc123
3404
+
3405
+ # Get JSON output
3406
+ emailr inboxes delete inb_abc123 --format json
3407
+
3408
+ WARNING
3409
+ This action is permanent and cannot be undone.`).option("--format <format>","Output format: json | table","table").action(async(a,e)=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).inboxes.delete(a);e.format==="json"?m(i,"json"):p(`Inbox deleted: ${a}`);}catch(t){l(t instanceof Error?t.message:"Failed to delete inbox"),process.exit(1);}}),o}function Je(){let o=new Command("threads").description(`Manage email threads, labels, replies, and drafts
3410
+
3411
+ USAGE
3412
+ emailr threads <subcommand> [options]
3413
+
3414
+ DESCRIPTION
3415
+ List, view, reply to, and manage email threads. Threads group related
3416
+ emails into conversations. Use labels to organize threads.
3417
+
3418
+ SUBCOMMANDS
3419
+ list List threads filtered by label
3420
+ get <id> View a thread with all messages
3421
+ label <id> Add or remove labels from a thread
3422
+ reply <id> Reply to a thread (auto-resolves from/to/reply-to)
3423
+ draft save <id> Save a draft reply for a thread
3424
+ draft get <id> Get the saved draft for a thread
3425
+ draft delete <id> Delete the draft for a thread
3426
+
3427
+ EXAMPLES
3428
+ # Reply to a thread (from/to/subject auto-resolved from inbox)
3429
+ emailr threads reply <thread-id> --html "<p>Thanks!</p>"
3430
+
3431
+ # Reply with explicit CC
3432
+ emailr threads reply <thread-id> --html "<p>Done</p>" --cc "boss@co.com"
3433
+
3434
+ # Save a draft
3435
+ emailr threads draft save <thread-id> --html "<p>WIP reply</p>"
3436
+
3437
+ # List inbox threads
3438
+ emailr threads list
3439
+
3440
+ # Star a thread
3441
+ emailr threads label <thread-id> --add starred`);o.command("list").description("List threads filtered by label").option("--label <label>","Filter by label (inbox, sent, starred, spam, trash, archived, all)","inbox").option("--limit <count>","Number of threads to return","20").option("--page <number>","Page number","1").option("--search <query>","Search by subject or sender").option("--inbox-id <id>","Filter by inbox ID").option("--format <format>","Output format (json|table)","table").action(async e=>{try{let t=d(),i=await new Emailr({apiKey:t.apiKey,baseUrl:t.baseUrl}).threads.list({label:e.label,page:parseInt(e.page),limit:Math.min(parseInt(e.limit),100),search:e.search,inbox_id:e.inboxId});if(e.format==="json")m(i,"json");else {if(!i.data||i.data.length===0){u(`No threads in ${e.label}`);return}let s=i.data.map(c=>({"Thread ID":c.thread_id.slice(0,8)+"\u2026",Subject:le(c.subject||"(no subject)",40),From:c.from_email,Messages:c.message_count,Labels:(c.labels||[]).join(", "),Updated:$e(c.updated_at)}));m(s,"table"),u(`Page ${i.pagination.page} of ${i.pagination.pages} (${i.pagination.total} total)`);}}catch(t){l(t instanceof Error?t.message:"Failed to list threads"),process.exit(1);}}),o.command("get <id>").description("View a thread with all messages").option("--format <format>","Output format (json|table)","table").action(async(e,t)=>{try{let n=d(),r=await new Emailr({apiKey:n.apiKey,baseUrl:n.baseUrl}).threads.get(e);if(t.format==="json")m(r,"json");else {u(`Thread: ${r.subject||"(no subject)"}`),u(`Labels: ${r.labels.join(", ")||"none"}`),u(`Messages: ${r.messages.length}`),console.log("");for(let c of r.messages){let b=c.status==="received"?"\u2190 IN ":"\u2192 OUT";console.log(`${b} ${$e(c.created_at)}`),console.log(` From: ${c.from_email}`),console.log(` To: ${c.to_email}`),c.cc_emails?.length&&console.log(` Cc: ${c.cc_emails.join(", ")}`),console.log(` Labels: ${c.labels.join(", ")||"none"}`);let g=c.text_content||c.html_content?.replace(/<[^>]*>/g,"")||"";g&&console.log(` ${le(g.trim(),120)}`),console.log("");}}await new Promise(c=>process.stdout.write("",()=>c())),process.exit(0);}catch(n){l(n instanceof Error?n.message:"Failed to get thread"),process.exit(1);}}),o.command("label <id>").description("Add or remove labels from a thread").option("--add <labels>","Labels to add (comma-separated)").option("--remove <labels>","Labels to remove (comma-separated)").option("--format <format>","Output format (json|table)","table").action(async(e,t)=>{try{!t.add&&!t.remove&&(l("Specify --add and/or --remove labels"),process.exit(1));let n=d(),i=new Emailr({apiKey:n.apiKey,baseUrl:n.baseUrl}),r=t.add?t.add.split(",").map(g=>g.trim()):void 0,s=t.remove?t.remove.split(",").map(g=>g.trim()):void 0,c=await i.threads.updateLabels(e,{add:r,remove:s});t.format==="json"?m(c,"json"):(p(`Updated ${c.updated} email(s) in thread`),r&&u(`Added: ${r.join(", ")}`),s&&u(`Removed: ${s.join(", ")}`)),await new Promise(g=>process.stdout.write("",()=>g())),process.exit(0);}catch(n){l(n instanceof Error?n.message:"Failed to update labels"),process.exit(1);}}),o.command("reply <id>").description("Reply to a thread (from/to/subject auto-resolved from inbox)").option("--html <content>","HTML content for the reply").option("--text <content>","Plain text content for the reply").option("--to <email>","Override recipient (auto-resolved if omitted)").option("--cc <emails>","CC recipients (comma-separated)").option("--bcc <emails>","BCC recipients (comma-separated)").option("--from <email>","Override sender (auto-resolved from inbox if omitted)").option("--from-name <name>","Override sender name (auto-resolved from inbox if omitted)").option("--reply-to <email>","Override Reply-To (auto-resolved from inbox if omitted)").option("--format <format>","Output format (json|table)","table").action(async(e,t)=>{try{!t.html&&!t.text&&(l("Provide --html or --text for the reply body"),process.exit(1));let n=d(),i=new Emailr({apiKey:n.apiKey,baseUrl:n.baseUrl}),r={};t.html&&(r.html=t.html),t.text&&(r.text=t.text),t.to&&(r.to=t.to),t.cc&&(r.cc=t.cc.split(",").map(b=>b.trim())),t.bcc&&(r.bcc=t.bcc.split(",").map(b=>b.trim())),t.from&&(r.from=t.from),t.fromName&&(r.from_name=t.fromName),t.replyTo&&(r.reply_to_email=t.replyTo);let s=await i.threads.reply(e,r);t.format==="json"?m(s,"json"):(p(`Reply sent to ${s.recipients} recipient(s)`),u(`Message ID: ${s.message_id}`)),await new Promise(b=>process.stdout.write("",()=>b())),process.exit(0);}catch(n){l(n instanceof Error?n.message:"Failed to send reply"),process.exit(1);}});let a=o.command("draft").description("Manage draft replies for threads");return a.command("save <id>").description("Save a draft reply for a thread").option("--html <content>","HTML content").option("--text <content>","Plain text content").option("--to <email>","Recipient").option("--cc <emails>","CC recipients").option("--bcc <emails>","BCC recipients").option("--from <email>","Sender").option("--reply-to <email>","Reply-To address").option("--format <format>","Output format (json|table)","table").action(async(e,t)=>{try{let n=d(),i=new Emailr({apiKey:n.apiKey,baseUrl:n.baseUrl}),r={};t.html&&(r.html=t.html),t.text&&(r.text=t.text),t.to&&(r.to=t.to),t.cc&&(r.cc=t.cc),t.bcc&&(r.bcc=t.bcc),t.from&&(r.from=t.from),t.replyTo&&(r.reply_to_email=t.replyTo);let s=await i.threads.saveDraft(e,r);t.format==="json"?m(s,"json"):(p(`Draft saved for thread ${e}`),u(`Draft ID: ${s.id}`)),await new Promise(b=>process.stdout.write("",()=>b())),process.exit(0);}catch(n){l(n instanceof Error?n.message:"Failed to save draft"),process.exit(1);}}),a.command("get <id>").description("Get the saved draft for a thread").option("--format <format>","Output format (json|table)","table").action(async(e,t)=>{try{let n=d(),r=await new Emailr({apiKey:n.apiKey,baseUrl:n.baseUrl}).threads.getDraft(e);t.format==="json"?m(r,"json"):(u(`Draft for thread: ${e}`),u(`To: ${r.to_email||"(auto)"}`),u(`From: ${r.from_email||"(auto)"}`),u(`Subject: ${r.subject||"(auto)"}`),r.html_content&&console.log(`
3442
+ Content:
3443
+ ${le(r.html_content.replace(/<[^>]*>/g,""),200)}`)),await new Promise(c=>process.stdout.write("",()=>c())),process.exit(0);}catch(n){l(n instanceof Error?n.message:"No draft found"),process.exit(1);}}),a.command("delete <id>").description("Delete the draft for a thread").option("--format <format>","Output format (json|table)","table").action(async(e,t)=>{try{let n=d();await new Emailr({apiKey:n.apiKey,baseUrl:n.baseUrl}).threads.deleteDraft(e),t.format==="json"?m({success:!0},"json"):p(`Draft deleted for thread ${e}`),await new Promise(s=>process.stdout.write("",()=>s())),process.exit(0);}catch(n){l(n instanceof Error?n.message:"Failed to delete draft"),process.exit(1);}}),o}function le(o,a){return o.length<=a?o:o.slice(0,a-1)+"\u2026"}function $e(o){return new Date(o).toLocaleString()}var Ft=`# Emailr CLI \u2014 Agent Skill Guide
3444
+
3445
+ ## Setup
3446
+
3447
+ \`\`\`bash
3448
+ emailr config set api-key <your_api_key>
3449
+ emailr config list # verify
3450
+ \`\`\`
3451
+
3452
+ Or use environment variables: EMAILR_API_KEY, EMAILR_BASE_URL.
3453
+
3454
+ ## Key Principle
3455
+
3456
+ Always use \`--format json\` for machine-readable output. Pipe through \`jq\` for field extraction.
3457
+
3458
+ ---
3459
+
3460
+ ## Commands Reference
3461
+
3462
+ ### send \u2014 Send transactional emails
3463
+ \`\`\`bash
3464
+ emailr send --to user@example.com --subject "Hello" --html "<p>Hi!</p>"
3465
+ emailr send --to user@example.com --template tpl_abc --template-data '{"name":"Jo"}'
3466
+ emailr send --to user@example.com --html-file ./email.html --text-file ./email.txt
3467
+ emailr send --to "a@x.com,b@x.com" --subject "Team" --html "<p>Update</p>" --cc "c@x.com"
3468
+ emailr send --to user@example.com --subject "Later" --html "<p>Hi</p>" --schedule "2025-12-25T10:00:00Z"
3469
+ \`\`\`
3470
+ Options: --to, --from, --subject, --html, --text, --html-file, --text-file, --template, --template-data, --cc, --bcc, --reply-to, --schedule, --format
3471
+
3472
+ ### threads \u2014 Manage email threads (conversations)
3473
+ \`\`\`bash
3474
+ emailr threads list # list inbox threads
3475
+ emailr threads list --label sent --inbox-id <id> # filter by label/inbox
3476
+ emailr threads get <thread-id> # view thread with all messages
3477
+ emailr threads get <thread-id> --format json # machine-readable
3478
+ emailr threads label <thread-id> --add starred # add labels
3479
+ emailr threads label <thread-id> --remove spam,trash # remove labels
3480
+ \`\`\`
3481
+ Labels: inbox, sent, starred, spam, trash, archived, all
3482
+
3483
+ #### Thread Reply (KEY FEATURE for agents)
3484
+ \`\`\`bash
3485
+ emailr threads reply <thread-id> --html "<p>Thanks!</p>"
3486
+ emailr threads reply <thread-id> --html "<p>Done</p>" --cc "boss@co.com"
3487
+ \`\`\`
3488
+ **Auto-resolution**: The API automatically resolves from, from_name, reply_to, to, and subject from the thread's inbox. You only need to provide the thread ID and HTML content. Override with --from, --from-name, --reply-to, --to if needed.
3489
+
3490
+ #### Thread Drafts
3491
+ \`\`\`bash
3492
+ emailr threads draft save <thread-id> --html "<p>WIP</p>"
3493
+ emailr threads draft get <thread-id>
3494
+ emailr threads draft delete <thread-id>
3495
+ \`\`\`
3496
+
3497
+ ### inbox \u2014 View and manage inbound emails
3498
+ \`\`\`bash
3499
+ emailr inbox list # list received emails
3500
+ emailr inbox list --email sender@x.com --inbox-id <id> # filter
3501
+ emailr inbox get <email-id> # view email details
3502
+ emailr inbox get <email-id> --content html # view HTML content
3503
+ emailr inbox thread <email-id> # view conversation
3504
+ emailr inbox reply <email-id> --html "<p>Reply</p>" # reply to email
3505
+ emailr inbox forward <email-id> --to other@x.com # forward email
3506
+ \`\`\`
3507
+
3508
+ ### inboxes \u2014 Manage email inboxes
3509
+ \`\`\`bash
3510
+ emailr inboxes list
3511
+ emailr inboxes create --name "Support" --username support --domain example.com
3512
+ emailr inboxes get <inbox-id>
3513
+ emailr inboxes update <inbox-id> --name "New Name"
3514
+ emailr inboxes delete <inbox-id>
3515
+ \`\`\`
3516
+
3517
+ ### contacts \u2014 Manage email contacts
3518
+ \`\`\`bash
3519
+ emailr contacts list --format json
3520
+ emailr contacts list --subscribed --limit 50
3521
+ emailr contacts get <contact-id>
3522
+ emailr contacts create --email user@x.com --first-name "Jo" --metadata '{"plan":"pro"}'
3523
+ emailr contacts update <contact-id> --tags "vip,active"
3524
+ emailr contacts delete <contact-id>
3525
+ \`\`\`
3526
+
3527
+ ### templates \u2014 Create, edit, preview email templates
3528
+ \`\`\`bash
3529
+ emailr templates list --format json
3530
+ emailr templates get <id>
3531
+ emailr templates create --name "Welcome" --subject "Hi {{name}}" --html-file welcome.html
3532
+ emailr templates update <id> --html-file updated.html
3533
+ emailr templates delete <id>
3534
+ emailr templates fetch <id> --output template.html # download HTML
3535
+ emailr templates push-preview <id> --html-file t.html # upload preview for review
3536
+ emailr templates edit <id> --file ./t.html # live edit with hot-reload
3537
+ emailr templates draft --file ./new.html # draft new with live preview
3538
+ emailr templates stop-preview # stop preview server
3539
+ \`\`\`
3540
+
3541
+ ### segments \u2014 Manage contact segments
3542
+ \`\`\`bash
3543
+ emailr segments list
3544
+ emailr segments create --name "Active" --conditions '[{"field":"subscribed","operator":"equals","value":true}]'
3545
+ emailr segments get <id>
3546
+ emailr segments count <id> # count matching contacts
3547
+ emailr segments update <id> --conditions '[...]'
3548
+ emailr segments delete <id>
3549
+ \`\`\`
3550
+ Operators: equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than, is_set, is_not_set
3551
+ Fields: email, first_name, last_name, subscribed, created_at, metadata.*
3552
+
3553
+ ### broadcasts \u2014 Send bulk email campaigns
3554
+ \`\`\`bash
3555
+ emailr broadcasts list --status draft
3556
+ emailr broadcasts create --name "Newsletter" --subject "Weekly" --from news@x.com --template tpl_abc --segment seg_xyz
3557
+ emailr broadcasts send <id> # send immediately
3558
+ emailr broadcasts schedule <id> --at "2025-12-25T10:00:00Z"
3559
+ emailr broadcasts cancel <id>
3560
+ emailr broadcasts get <id> # view delivery stats
3561
+ emailr broadcasts update <id> --subject "New Subject"
3562
+ emailr broadcasts delete <id>
3563
+ \`\`\`
3564
+
3565
+ ### webhooks \u2014 Configure event notifications
3566
+ \`\`\`bash
3567
+ emailr webhooks list
3568
+ emailr webhooks create --name "Tracker" --url "https://x.com/hook" --events "email.delivered,email.bounced"
3569
+ emailr webhooks get <id> # includes secret
3570
+ emailr webhooks update <id> --events "email.delivered"
3571
+ emailr webhooks enable <id>
3572
+ emailr webhooks disable <id>
3573
+ emailr webhooks delete <id>
3574
+ \`\`\`
3575
+ Events: email.sent, email.delivered, email.bounced, email.complained, email.opened, email.clicked, email.failed, email.delivery_delayed, contact.created, contact.updated, contact.deleted, domain.verified, domain.verification_failed
3576
+
3577
+ ### domains \u2014 Manage sending domains
3578
+ \`\`\`bash
3579
+ emailr domains add example.com
3580
+ emailr domains verify example.com
3581
+ emailr domains list
3582
+ \`\`\`
3583
+
3584
+ ### config \u2014 CLI configuration
3585
+ \`\`\`bash
3586
+ emailr config set api-key <key>
3587
+ emailr config set base-url https://custom.api.example.com
3588
+ emailr config list
3589
+ emailr config get api-key
3590
+ emailr config path
3591
+ emailr config init --api-key <key>
3592
+ \`\`\`
3593
+
3594
+ ### login \u2014 Browser-based authentication
3595
+ \`\`\`bash
3596
+ emailr login
3597
+ \`\`\`
3598
+
3599
+ ---
3600
+
3601
+ ## Agent Workflows
3602
+
3603
+ ### 1. Reply to a customer email (simplest)
3604
+ \`\`\`bash
3605
+ # Everything auto-resolved from thread's inbox
3606
+ emailr threads reply <thread-id> --html "<p>Thanks for reaching out!</p>" --format json
3607
+ \`\`\`
3608
+
3609
+ ### 2. Send a transactional email
3610
+ \`\`\`bash
3611
+ emailr send --to customer@x.com --subject "Order Confirmed" \\
3612
+ --html "<h1>Order #123</h1><p>Your order is confirmed.</p>" --format json
3613
+ \`\`\`
3614
+
3615
+ ### 3. Template editing workflow
3616
+ \`\`\`bash
3617
+ emailr templates fetch <id> --output template.html
3618
+ # edit template.html
3619
+ emailr templates push-preview <id> --html-file template.html
3620
+ # review preview URL, iterate
3621
+ emailr templates update <id> --html-file template.html
3622
+ \`\`\`
3623
+
3624
+ ### 4. Broadcast campaign
3625
+ \`\`\`bash
3626
+ emailr segments create --name "Active" --conditions '[{"field":"subscribed","operator":"equals","value":true}]' --format json
3627
+ # capture segment ID
3628
+ emailr broadcasts create --name "Campaign" --subject "News" --from news@x.com --template tpl_abc --segment seg_xyz --format json
3629
+ # capture broadcast ID
3630
+ emailr broadcasts send <broadcast-id> --format json
3631
+ \`\`\`
3632
+
3633
+ ### 5. Monitor inbox and respond
3634
+ \`\`\`bash
3635
+ emailr threads list --format json # get threads
3636
+ emailr threads get <thread-id> --format json # read conversation
3637
+ emailr threads reply <thread-id> --html "<p>Reply</p>" --format json
3638
+ \`\`\`
3639
+
3640
+ ---
3641
+
3642
+ ## Tips
3643
+
3644
+ - All list/get commands support \`--format json\` for structured output
3645
+ - Thread reply auto-resolves from/to/subject \u2014 just provide thread ID + HTML
3646
+ - Use \`--format json | jq '.field'\` to extract specific fields
3647
+ - Drafts auto-save in the UI; use \`emailr threads draft save\` from CLI
3648
+ - Labels: inbox, sent, starred, spam, trash, archived
3649
+ - Broadcasts need a segment (audience) and template or inline HTML
3650
+ `;function Be(){return new Command("skill").description(`Output the Emailr CLI skill guide for AI agents
3651
+
3652
+ USAGE
3653
+ emailr skill
3654
+
3655
+ DESCRIPTION
3656
+ Prints a comprehensive markdown guide to stdout that teaches AI agents
3657
+ how to use all Emailr CLI commands effectively. This includes every
3658
+ command, key options, common workflows, and tips for machine-readable
3659
+ output.
3660
+
3661
+ Pipe to a file or feed directly to an AI agent's context.
3662
+
3663
+ EXAMPLES
3664
+ # Print skill to stdout
3665
+ emailr skill
3666
+
3667
+ # Save to file
3668
+ emailr skill > emailr-skill.md
3669
+
3670
+ # Feed to an AI agent context
3671
+ emailr skill | pbcopy`).action(()=>{process.stdout.write(Ft);})}var y=new Command;y.name("emailr").description(`Emailr CLI - Send emails and manage your email infrastructure
3193
3672
 
3194
3673
  USAGE
3195
3674
  emailr <command> [subcommand] [options]
@@ -3202,6 +3681,8 @@ DESCRIPTION
3202
3681
  COMMANDS
3203
3682
  send Send individual emails with HTML/text content or templates
3204
3683
  inbox View and manage received (inbound) emails
3684
+ inboxes Create and manage email inboxes
3685
+ threads List, view, reply to, and manage email threads and drafts
3205
3686
  templates Create, edit, preview, and publish email templates
3206
3687
  contacts Manage email contacts and their metadata
3207
3688
  segments Create and manage contact segments with conditions
@@ -3211,6 +3692,7 @@ COMMANDS
3211
3692
  config Configure CLI settings (API key, base URL)
3212
3693
  login Authenticate with your Emailr account
3213
3694
  agent Run the AI agent for automated email tasks
3695
+ skill Output the CLI skill guide for AI agents
3214
3696
 
3215
3697
  GETTING STARTED
3216
3698
  1. Set your API key:
@@ -3254,6 +3736,12 @@ EXAMPLES
3254
3736
  # Forward a received email
3255
3737
  emailr inbox forward <email-id> --to colleague@example.com
3256
3738
 
3739
+ # Reply to a thread (from/to/subject auto-resolved from inbox)
3740
+ emailr threads reply <thread-id> --html "<p>Thanks!</p>"
3741
+
3742
+ # Save a draft reply
3743
+ emailr threads draft save <thread-id> --html "<p>WIP</p>"
3744
+
3257
3745
  # List all templates
3258
3746
  emailr templates list
3259
3747
 
@@ -3280,8 +3768,12 @@ EXAMPLES
3280
3768
  emailr contacts list --format json | jq '.[].email'
3281
3769
 
3282
3770
  AGENTIC WORKFLOW
3283
- For collaborating with AI agents on email templates:
3771
+ For AI agents: run 'emailr skill' to get a comprehensive guide.
3772
+
3773
+ Quick thread reply (from/to/subject auto-resolved):
3774
+ emailr threads reply <thread-id> --html "<p>Reply content</p>" --format json
3284
3775
 
3776
+ Template editing with AI agents:
3285
3777
  1. Fetch template: emailr templates fetch <id> --output template.html
3286
3778
  2. Edit locally or with AI assistance
3287
3779
  3. Push preview: emailr templates push-preview <id> --html-file template.html
@@ -3291,4 +3783,4 @@ AGENTIC WORKFLOW
3291
3783
 
3292
3784
  MORE INFORMATION
3293
3785
  Run 'emailr <command> --help' for detailed help on any command.
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();
3786
+ Run 'emailr <command> <subcommand> --help' for subcommand details.`).version("1.10.0");y.addCommand(fe());y.addCommand(He());y.addCommand(qe());y.addCommand(Je());y.addCommand(be());y.addCommand(Ie());y.addCommand(Ne());y.addCommand(Ue());y.addCommand(ke());y.addCommand(Pe());y.addCommand(Ce());y.addCommand(De());y.addCommand(Fe());y.addCommand(Be());y.parse();