@myapihq/cli 2.21.0 → 2.22.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.
- package/dist/commands/account.d.ts +1 -1
- package/dist/commands/account.js +58 -0
- package/dist/commands/audience.js +10 -5
- package/dist/commands/company.js +7 -2
- package/dist/commands/email/campaign.d.ts +5 -0
- package/dist/commands/email/campaign.js +238 -0
- package/dist/commands/email/index.js +13 -0
- package/dist/commands/people.js +7 -2
- package/dist/completion.js +1 -1
- package/dist/exposes.test.js +1 -0
- package/dist/index.js +2 -0
- package/dist/skills/my-api-hq/SKILL.md +4 -3
- package/dist/skills/my-email-api/SKILL.md +48 -2
- package/dist/skills/my-email-verify-api/SKILL.md +2 -1
- package/package.json +2 -2
|
@@ -3,7 +3,7 @@ import type { Flags } from '../helpers.js';
|
|
|
3
3
|
import type { Exposes } from '../exposes.js';
|
|
4
4
|
export declare const EXPOSES: Exposes;
|
|
5
5
|
export declare const SCHEMA: FlagSchema;
|
|
6
|
-
export declare const HELP = "Usage: myapi account <subcommand>\n\nSubcommands:\n api-keys Manage API keys \u00B7 list / create / revoke\n config Manage CLI defaults (org, funnel, domain) \u00B7 supports set-org / set-funnel / set-domain\n import-key Import an existing API key non-interactively\n install-skills DEPRECATED \u2014 use `myapi install-skills` instead\n keys Alias for api-keys\n link [email] Upgrade anonymous account to registered (or add a second session)\n Use myapi account setup to create a completely new account\n login Sign in via your browser \u2014 Google or email code (preview: --mock)\n mailing-address Get or set the account's mailing address (CAN-SPAM)\n registrant Manage stored WHOIS contact info for domain registration \u00B7 set / get / clear\n setup Configure your account\n switch [index] Switch active account by index or email\n whoami Show current account \u00B7 supports --json";
|
|
6
|
+
export declare const HELP = "Usage: myapi account <subcommand>\n\nSubcommands:\n api-keys Manage API keys \u00B7 list / create / revoke\n config Manage CLI defaults (org, funnel, domain) \u00B7 supports set-org / set-funnel / set-domain\n import-key Import an existing API key non-interactively\n install-skills DEPRECATED \u2014 use `myapi install-skills` instead\n keys Alias for api-keys\n link [email] Upgrade anonymous account to registered (or add a second session)\n Use myapi account setup to create a completely new account\n login Sign in via your browser \u2014 Google or email code (preview: --mock)\n mailing-address Get or set the account's mailing address (CAN-SPAM)\n registrant Manage stored WHOIS contact info for domain registration \u00B7 set / get / clear\n resume-sending Turn email sending back on after an automatic pause\n sending Whether this account can send email, and how close it is to the limits\n setup Configure your account\n switch [index] Switch active account by index or email\n whoami Show current account \u00B7 supports --json";
|
|
7
7
|
export declare const INSTALL_SKILLS_HELP = "Usage: myapi install-skills\n\nInstalls the MyAPI skills pack for AI coding agents (Claude, Codex, Gemini, \u2026).\n\nThis command writes skill definition files to:\n ~/.agents/skills/myapi/\n\nAnd creates symlinks in the appropriate agent config directories:\n ~/.claude/ (Claude)\n ~/.gemini/ (Gemini)\n ~/.cursor/ (Cursor, if detected)\n\nThese files teach agents how to use the MyAPI CLI and API directly.\nRun this command again to update existing skills to the latest version.\n\nNote: `myapi account install-skills` is deprecated and will be removed in the next minor.\nUse `myapi install-skills` going forward.";
|
|
8
8
|
export declare const SUBCOMMAND_USAGE: Record<string, string>;
|
|
9
9
|
export declare function link(flags?: Flags, emailArg?: string): Promise<void>;
|
package/dist/commands/account.js
CHANGED
|
@@ -17,6 +17,8 @@ export const EXPOSES = [
|
|
|
17
17
|
'POST /hq/account/verify-code',
|
|
18
18
|
'GET /hq/billing/balance',
|
|
19
19
|
'GET /hq/account/free-tier',
|
|
20
|
+
'GET /hq/account/sending',
|
|
21
|
+
'POST /hq/account/resume-sending',
|
|
20
22
|
'GET /hq/account/mailing-address',
|
|
21
23
|
'PATCH /hq/account/mailing-address',
|
|
22
24
|
];
|
|
@@ -54,6 +56,8 @@ Subcommands:
|
|
|
54
56
|
login Sign in via your browser — Google or email code (preview: --mock)
|
|
55
57
|
mailing-address Get or set the account's mailing address (CAN-SPAM)
|
|
56
58
|
registrant Manage stored WHOIS contact info for domain registration · set / get / clear
|
|
59
|
+
resume-sending Turn email sending back on after an automatic pause
|
|
60
|
+
sending Whether this account can send email, and how close it is to the limits
|
|
57
61
|
setup Configure your account
|
|
58
62
|
switch [index] Switch active account by index or email
|
|
59
63
|
whoami Show current account · supports --json`;
|
|
@@ -116,6 +120,23 @@ Subcommands:
|
|
|
116
120
|
set Interactive prompt for all fields. Or pass --registrant-json
|
|
117
121
|
'<json>' to set non-interactively (agent-friendly).`;
|
|
118
122
|
export const SUBCOMMAND_USAGE = {
|
|
123
|
+
sending: `myapi account sending [--json]
|
|
124
|
+
|
|
125
|
+
Whether this account can send email, and how close it is to the limits.
|
|
126
|
+
|
|
127
|
+
The platform pauses sending when too much of the last 24 hours bounces.
|
|
128
|
+
The pause message is a photograph of the moment it happened — this is the
|
|
129
|
+
live picture, and the two can disagree: an account paused yesterday may be
|
|
130
|
+
well within the limits today and simply need resuming.
|
|
131
|
+
|
|
132
|
+
Under 100 sends in 24h the ratios are not judged, and the reply says so.`,
|
|
133
|
+
'resume-sending': `myapi account resume-sending [--json]
|
|
134
|
+
|
|
135
|
+
Turn sending back on after an automatic pause.
|
|
136
|
+
|
|
137
|
+
Refused with STILL_OVER_THRESHOLD while the last 24 hours are still over
|
|
138
|
+
the limit — the numbers come back with the refusal, so you can see how far
|
|
139
|
+
off you are. Removing the addresses that bounce is what moves them.`,
|
|
119
140
|
'mailing-address': `myapi account mailing-address Show current value (null if unset)
|
|
120
141
|
myapi account mailing-address "<address>" Set the mailing address
|
|
121
142
|
|
|
@@ -410,6 +431,41 @@ async function mailingAddress(args, flags) {
|
|
|
410
431
|
}
|
|
411
432
|
success(`Mailing address set: ${res.mailing_address}`);
|
|
412
433
|
}
|
|
434
|
+
// The pause is invisible until a send fails, so both verbs lead with the one
|
|
435
|
+
// fact that decides what to do next: can this account send right now.
|
|
436
|
+
async function sendingStatus(flags) {
|
|
437
|
+
const config = requireConfig();
|
|
438
|
+
const res = await hq.getSendingStatus(config.api_key);
|
|
439
|
+
if (flags.json) {
|
|
440
|
+
printJson(res);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
info(res.sending_paused ? 'Sending is PAUSED.' : 'Sending is on.');
|
|
444
|
+
info(`Last 24h: ${res.sends_24h} sent · ${res.bounces_24h} bounced · ${res.complaints_24h} complaints`);
|
|
445
|
+
info(`Limits: ${(res.bounce_threshold * 100).toFixed(1)}% bounces · ${(res.complaint_threshold * 100).toFixed(2)}% complaints`);
|
|
446
|
+
if (res.note)
|
|
447
|
+
info(res.note);
|
|
448
|
+
if (res.sending_paused) {
|
|
449
|
+
if (res.paused_reason_when_paused) {
|
|
450
|
+
// Labelled as history, because the live numbers above may disagree with
|
|
451
|
+
// it — and reading the frozen reason as current is the whole confusion.
|
|
452
|
+
info(`Paused because (at the time): ${res.paused_reason_when_paused}`);
|
|
453
|
+
}
|
|
454
|
+
info(res.can_resume_now
|
|
455
|
+
? 'Within the limits now — run: myapi account resume-sending'
|
|
456
|
+
: 'Still over the limit. Remove the addresses that bounce, then resume.');
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
async function resumeSending(flags) {
|
|
460
|
+
const config = requireConfig();
|
|
461
|
+
const res = await hq.resumeSending(config.api_key);
|
|
462
|
+
if (flags.json) {
|
|
463
|
+
printJson(res);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
success(res.message);
|
|
467
|
+
info(`Last 24h: ${res.sends} sent · ${res.bounces} bounced`);
|
|
468
|
+
}
|
|
413
469
|
export async function run(subcommand, args, flags) {
|
|
414
470
|
if (!subcommand || (flags.help && !subcommand)) {
|
|
415
471
|
info(HELP);
|
|
@@ -424,6 +480,8 @@ export async function run(subcommand, args, flags) {
|
|
|
424
480
|
}
|
|
425
481
|
switch (subcommand) {
|
|
426
482
|
case 'mailing-address': return mailingAddress(args, flags);
|
|
483
|
+
case 'sending': return sendingStatus(flags);
|
|
484
|
+
case 'resume-sending': return resumeSending(flags);
|
|
427
485
|
default: error(`Unknown subcommand: ${subcommand}. Run "myapi account --help" for the list.`);
|
|
428
486
|
}
|
|
429
487
|
}
|
|
@@ -2,6 +2,11 @@ import { audience as sdkAudience } from '@myapihq/sdk';
|
|
|
2
2
|
import { requireConfig } from '../config.js';
|
|
3
3
|
import { success, error, printTable, info, printJson } from '../output.js';
|
|
4
4
|
import { requireOrg, requireArg } from '../helpers.js';
|
|
5
|
+
import { retryFunds } from '../utils.js';
|
|
6
|
+
// Billable: the backend reserves funds before serving these, so an empty
|
|
7
|
+
// wallet with an auto-recharge in flight answers 402 in_flight with a
|
|
8
|
+
// retry hint. retryFunds waits it out instead of killing an unattended
|
|
9
|
+
// run. Only create/update/members/refresh gate — list, get and delete are free and stay unwrapped.
|
|
5
10
|
export const EXPOSES = [
|
|
6
11
|
'POST /audience/orgs/{org_id}/audiences',
|
|
7
12
|
'GET /audience/orgs/{org_id}/audiences',
|
|
@@ -59,9 +64,9 @@ async function create(nameArg, flags) {
|
|
|
59
64
|
}
|
|
60
65
|
const filter = parseFilter(flags.filter);
|
|
61
66
|
const description = typeof flags.description === 'string' ? flags.description : undefined;
|
|
62
|
-
const res = await sdkAudience.createAudience(config.api_key, orgId, {
|
|
67
|
+
const res = await retryFunds(() => sdkAudience.createAudience(config.api_key, orgId, {
|
|
63
68
|
name, source: source, filter, description,
|
|
64
|
-
});
|
|
69
|
+
}));
|
|
65
70
|
if (flags.json) {
|
|
66
71
|
printJson(res);
|
|
67
72
|
return;
|
|
@@ -102,7 +107,7 @@ async function update(id, flags) {
|
|
|
102
107
|
if (Object.keys(patch).length === 0) {
|
|
103
108
|
error('Pass at least one of --name / --description / --filter to update.');
|
|
104
109
|
}
|
|
105
|
-
const res = await sdkAudience.updateAudience(config.api_key, orgId, id, patch);
|
|
110
|
+
const res = await retryFunds(() => sdkAudience.updateAudience(config.api_key, orgId, id, patch));
|
|
106
111
|
if (flags.json) {
|
|
107
112
|
printJson(res);
|
|
108
113
|
return;
|
|
@@ -125,7 +130,7 @@ async function members(id, flags) {
|
|
|
125
130
|
opts.limit = flags.limit;
|
|
126
131
|
if (typeof flags.offset === 'number')
|
|
127
132
|
opts.offset = flags.offset;
|
|
128
|
-
const res = await sdkAudience.getAudienceMembers(config.api_key, orgId, id, opts);
|
|
133
|
+
const res = await retryFunds(() => sdkAudience.getAudienceMembers(config.api_key, orgId, id, opts));
|
|
129
134
|
if (flags.json) {
|
|
130
135
|
printJson(res);
|
|
131
136
|
return;
|
|
@@ -161,7 +166,7 @@ async function refresh(id, flags) {
|
|
|
161
166
|
const config = requireConfig();
|
|
162
167
|
const orgId = requireOrg(flags, config, 'myapi audience refresh <id> [--org <id>]');
|
|
163
168
|
requireArg(id, 'id', 'myapi audience refresh <id> [--org <id>]');
|
|
164
|
-
const res = await sdkAudience.refreshAudience(config.api_key, orgId, id);
|
|
169
|
+
const res = await retryFunds(() => sdkAudience.refreshAudience(config.api_key, orgId, id));
|
|
165
170
|
if (flags.json) {
|
|
166
171
|
printJson(res);
|
|
167
172
|
return;
|
package/dist/commands/company.js
CHANGED
|
@@ -2,6 +2,11 @@ import { company as sdkCompany } from '@myapihq/sdk';
|
|
|
2
2
|
import { requireConfig } from '../config.js';
|
|
3
3
|
import { error, printTable, info, printJson } from '../output.js';
|
|
4
4
|
import { requireOrg, requireArg } from '../helpers.js';
|
|
5
|
+
import { retryFunds } from '../utils.js';
|
|
6
|
+
// Billable: the backend reserves funds before serving these, so an empty
|
|
7
|
+
// wallet with an auto-recharge in flight answers 402 in_flight with a
|
|
8
|
+
// retry hint. retryFunds waits it out instead of killing an unattended
|
|
9
|
+
// run. Both handlers gate; there is no free company call.
|
|
5
10
|
export const EXPOSES = [
|
|
6
11
|
'POST /company/orgs/{org_id}/search',
|
|
7
12
|
'GET /company/orgs/{org_id}/{company_id}',
|
|
@@ -72,7 +77,7 @@ async function search(flags) {
|
|
|
72
77
|
const config = requireConfig();
|
|
73
78
|
const orgId = requireOrg(flags, config, 'myapi company search [filters...] [--org <id>]');
|
|
74
79
|
const options = buildSearch(flags);
|
|
75
|
-
const res = await sdkCompany.searchCompanies(config.api_key, orgId, options);
|
|
80
|
+
const res = await retryFunds(() => sdkCompany.searchCompanies(config.api_key, orgId, options));
|
|
76
81
|
if (flags.json) {
|
|
77
82
|
printJson(res);
|
|
78
83
|
return;
|
|
@@ -94,7 +99,7 @@ async function get(companyId, flags) {
|
|
|
94
99
|
const orgId = requireOrg(flags, config, 'myapi company get <company_id> [--include-people N] [--org <id>]');
|
|
95
100
|
requireArg(companyId, 'company_id', 'myapi company get <company_id> [--include-people N] [--org <id>]');
|
|
96
101
|
const includePeople = typeof flags['include-people'] === 'number' ? flags['include-people'] : undefined;
|
|
97
|
-
const res = await sdkCompany.getCompany(config.api_key, orgId, companyId, includePeople);
|
|
102
|
+
const res = await retryFunds(() => sdkCompany.getCompany(config.api_key, orgId, companyId, includePeople));
|
|
98
103
|
printJson(res);
|
|
99
104
|
}
|
|
100
105
|
export const SUBCOMMAND_USAGE = {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type Flags } from '../../helpers.js';
|
|
2
|
+
import type { Exposes } from '../../exposes.js';
|
|
3
|
+
export declare const EXPOSES: Exposes;
|
|
4
|
+
export declare const HELP = "Usage: myapi email campaign <subcommand>\n\n create Create a draft (--template, --from, and a source)\n list Campaigns in this org\n get <id> One campaign\n update <id> Edit a DRAFT's name or source\n resolve <id> Freeze who it reaches; reports the count and cost, sends nothing\n start <id> Begin sending a resolved campaign\n pause <id> Stop after the message in flight\n resume <id> Continue a paused campaign\n cancel <id> End it for good; queued recipients are dropped\n recipients <id> Who it resolved to, and what happened to each\n stats <id> Counts by state, sent today, and the daily limit\n\nA campaign drains at its per-day limit rather than sending at once, so it is\ncommonly still active tomorrow \u2014 that is the design, not a stall.\n\nRecipients come from a SOURCE, not a list you upload:\n --crm-stage / --crm-origin / --crm-max-days filter CRM contacts\n --addresses a@x.com,b@y.com an explicit short list";
|
|
5
|
+
export declare function run(sub: string | undefined, args: string[], flags: Flags): Promise<void>;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { email as sdkEmail } from '@myapihq/sdk';
|
|
2
|
+
import { requireConfig } from '../../config.js';
|
|
3
|
+
import { success, error, info, printTable, printJson } from '../../output.js';
|
|
4
|
+
import { retryFunds } from '../../utils.js';
|
|
5
|
+
import { requireOrg, requireArg } from '../../helpers.js';
|
|
6
|
+
export const EXPOSES = [
|
|
7
|
+
'POST /email/orgs/{org_id}/campaigns',
|
|
8
|
+
'GET /email/orgs/{org_id}/campaigns',
|
|
9
|
+
'GET /email/orgs/{org_id}/campaigns/{campaign_id}',
|
|
10
|
+
'PATCH /email/orgs/{org_id}/campaigns/{campaign_id}',
|
|
11
|
+
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/resolve',
|
|
12
|
+
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/start',
|
|
13
|
+
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/pause',
|
|
14
|
+
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/resume',
|
|
15
|
+
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/cancel',
|
|
16
|
+
'GET /email/orgs/{org_id}/campaigns/{campaign_id}/recipients',
|
|
17
|
+
'GET /email/orgs/{org_id}/campaigns/{campaign_id}/stats',
|
|
18
|
+
];
|
|
19
|
+
export const HELP = `Usage: myapi email campaign <subcommand>
|
|
20
|
+
|
|
21
|
+
create Create a draft (--template, --from, and a source)
|
|
22
|
+
list Campaigns in this org
|
|
23
|
+
get <id> One campaign
|
|
24
|
+
update <id> Edit a DRAFT's name or source
|
|
25
|
+
resolve <id> Freeze who it reaches; reports the count and cost, sends nothing
|
|
26
|
+
start <id> Begin sending a resolved campaign
|
|
27
|
+
pause <id> Stop after the message in flight
|
|
28
|
+
resume <id> Continue a paused campaign
|
|
29
|
+
cancel <id> End it for good; queued recipients are dropped
|
|
30
|
+
recipients <id> Who it resolved to, and what happened to each
|
|
31
|
+
stats <id> Counts by state, sent today, and the daily limit
|
|
32
|
+
|
|
33
|
+
A campaign drains at its per-day limit rather than sending at once, so it is
|
|
34
|
+
commonly still active tomorrow — that is the design, not a stall.
|
|
35
|
+
|
|
36
|
+
Recipients come from a SOURCE, not a list you upload:
|
|
37
|
+
--crm-stage / --crm-origin / --crm-max-days filter CRM contacts
|
|
38
|
+
--addresses a@x.com,b@y.com an explicit short list`;
|
|
39
|
+
// Building the source from flags is the one place the CLI has an opinion: a
|
|
40
|
+
// campaign names where its people come from, and mixing two sources in one
|
|
41
|
+
// command is a request nobody can mean.
|
|
42
|
+
function sourceFromFlags(flags) {
|
|
43
|
+
const addrs = typeof flags.addresses === 'string' && flags.addresses
|
|
44
|
+
? flags.addresses.split(',').map(s => s.trim()).filter(Boolean)
|
|
45
|
+
: [];
|
|
46
|
+
const crm = {};
|
|
47
|
+
if (typeof flags['crm-stage'] === 'string' && flags['crm-stage'])
|
|
48
|
+
crm.stage = flags['crm-stage'];
|
|
49
|
+
if (typeof flags['crm-origin'] === 'string' && flags['crm-origin'])
|
|
50
|
+
crm.origin = flags['crm-origin'];
|
|
51
|
+
if (typeof flags['crm-company'] === 'string' && flags['crm-company'])
|
|
52
|
+
crm.company_id = flags['crm-company'];
|
|
53
|
+
if (typeof flags['crm-max-days'] === 'number')
|
|
54
|
+
crm.max_last_engagement_days = flags['crm-max-days'];
|
|
55
|
+
if (typeof flags['crm-min-days'] === 'number')
|
|
56
|
+
crm.min_last_engagement_days = flags['crm-min-days'];
|
|
57
|
+
const hasCRM = Object.keys(crm).length > 0;
|
|
58
|
+
if (addrs.length && hasCRM) {
|
|
59
|
+
error('Pass either --addresses or the --crm-* filters, not both — a campaign draws from one source.');
|
|
60
|
+
}
|
|
61
|
+
if (addrs.length)
|
|
62
|
+
return { source_kind: 'list', source_ref: { addresses: addrs } };
|
|
63
|
+
if (hasCRM)
|
|
64
|
+
return { source_kind: 'crm_query', source_ref: crm };
|
|
65
|
+
// An unfiltered CRM query is every contact in the org. That is a legitimate
|
|
66
|
+
// campaign and a very easy accident, so it has to be asked for by name.
|
|
67
|
+
if (flags['all-contacts'])
|
|
68
|
+
return { source_kind: 'crm_query', source_ref: {} };
|
|
69
|
+
error('No source. Use --addresses <csv>, a --crm-* filter, or --all-contacts to mean every CRM contact.');
|
|
70
|
+
throw new Error('unreachable');
|
|
71
|
+
}
|
|
72
|
+
function fmtCents(c) {
|
|
73
|
+
if (c == null)
|
|
74
|
+
return '—';
|
|
75
|
+
return c < 100 ? `${c.toFixed(2)}¢` : `$${(c / 100).toFixed(2)}`;
|
|
76
|
+
}
|
|
77
|
+
async function create(flags) {
|
|
78
|
+
const config = requireConfig();
|
|
79
|
+
const orgId = requireOrg(flags, config, 'myapi email campaign create --name <n> --template <id> --from <addr> [--addresses <csv> | --crm-stage <s>]');
|
|
80
|
+
const name = typeof flags.name === 'string' ? flags.name : '';
|
|
81
|
+
const template = typeof flags.template === 'string' ? flags.template : '';
|
|
82
|
+
const from = typeof flags.from === 'string' ? flags.from : '';
|
|
83
|
+
if (!name || !template || !from) {
|
|
84
|
+
error('Missing required flags: --name, --template <template-id>, --from <mailbox address>.');
|
|
85
|
+
}
|
|
86
|
+
const src = sourceFromFlags(flags);
|
|
87
|
+
const c = await sdkEmail.createCampaign(config.api_key, orgId, {
|
|
88
|
+
name, template_id: template, from_address: from, ...src,
|
|
89
|
+
});
|
|
90
|
+
if (flags.json) {
|
|
91
|
+
printJson(c);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
success(`Draft campaign ${c.id}`);
|
|
95
|
+
info(`Next: myapi email campaign resolve ${c.id} # who it reaches and what it costs — sends nothing`);
|
|
96
|
+
}
|
|
97
|
+
async function list(flags) {
|
|
98
|
+
const config = requireConfig();
|
|
99
|
+
const orgId = requireOrg(flags, config, 'myapi email campaign list [--org <id>]');
|
|
100
|
+
const rows = await sdkEmail.listCampaigns(config.api_key, orgId);
|
|
101
|
+
if (flags.json) {
|
|
102
|
+
printJson(rows);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
printTable(rows.map(c => ({
|
|
106
|
+
id: c.id,
|
|
107
|
+
name: c.name,
|
|
108
|
+
status: c.status,
|
|
109
|
+
source: c.source_kind,
|
|
110
|
+
recipients: c.recipient_count ?? '—',
|
|
111
|
+
est: fmtCents(c.estimated_cost_cents),
|
|
112
|
+
})), { flags, empty: 'No campaigns yet. Create one with: myapi email campaign create --name ... --template ... --from ...' });
|
|
113
|
+
}
|
|
114
|
+
async function get(id, flags) {
|
|
115
|
+
const config = requireConfig();
|
|
116
|
+
const orgId = requireOrg(flags, config, 'myapi email campaign get <id>');
|
|
117
|
+
const c = await sdkEmail.getCampaign(config.api_key, orgId, id);
|
|
118
|
+
if (flags.json) {
|
|
119
|
+
printJson(c);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
info(`${c.name} [${c.status}]`);
|
|
123
|
+
info(`from ${c.from_address} · template ${c.template_id} · source ${c.source_kind}`);
|
|
124
|
+
info(`recipients ${c.recipient_count ?? '—'} · estimated ${fmtCents(c.estimated_cost_cents)}`);
|
|
125
|
+
if (c.resolved_at)
|
|
126
|
+
info(`resolved ${c.resolved_at}`);
|
|
127
|
+
}
|
|
128
|
+
async function update(id, flags) {
|
|
129
|
+
const config = requireConfig();
|
|
130
|
+
const orgId = requireOrg(flags, config, 'myapi email campaign update <id> [--name <n>] [--addresses <csv>]');
|
|
131
|
+
const patch = {};
|
|
132
|
+
if (typeof flags.name === 'string' && flags.name)
|
|
133
|
+
patch.name = flags.name;
|
|
134
|
+
const wantsSource = flags.addresses || flags['crm-stage'] || flags['crm-origin']
|
|
135
|
+
|| flags['crm-company'] || flags['crm-max-days'] || flags['crm-min-days'] || flags['all-contacts'];
|
|
136
|
+
if (wantsSource)
|
|
137
|
+
Object.assign(patch, sourceFromFlags(flags));
|
|
138
|
+
if (Object.keys(patch).length === 0)
|
|
139
|
+
error('Nothing to change. Pass --name or a source flag.');
|
|
140
|
+
const c = await sdkEmail.updateCampaign(config.api_key, orgId, id, patch);
|
|
141
|
+
if (flags.json) {
|
|
142
|
+
printJson(c);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
success(`Updated ${c.id}`);
|
|
146
|
+
}
|
|
147
|
+
async function resolve(id, flags) {
|
|
148
|
+
const config = requireConfig();
|
|
149
|
+
const orgId = requireOrg(flags, config, 'myapi email campaign resolve <id>');
|
|
150
|
+
const r = await sdkEmail.resolveCampaign(config.api_key, orgId, id);
|
|
151
|
+
if (flags.json) {
|
|
152
|
+
printJson(r);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
success(`${r.recipients} recipient(s) · estimated ${fmtCents(r.estimated_cost_cents)}`);
|
|
156
|
+
const excluded = Object.entries(r.excluded ?? {});
|
|
157
|
+
if (excluded.length) {
|
|
158
|
+
// Saying WHY the number shrank is the difference between a product and a
|
|
159
|
+
// black box — the source had more rows than this, and the caller is owed
|
|
160
|
+
// the reason.
|
|
161
|
+
info(`From ${r.source_rows} source row(s); excluded ${excluded.map(([k, n]) => `${n} ${k}`).join(', ')}.`);
|
|
162
|
+
}
|
|
163
|
+
info('Nothing has been sent. Start it with: myapi email campaign start ' + id);
|
|
164
|
+
}
|
|
165
|
+
async function transition(verb, id, flags) {
|
|
166
|
+
const config = requireConfig();
|
|
167
|
+
const orgId = requireOrg(flags, config, `myapi email campaign ${verb} <id>`);
|
|
168
|
+
const fn = {
|
|
169
|
+
start: sdkEmail.startCampaign,
|
|
170
|
+
pause: sdkEmail.pauseCampaign,
|
|
171
|
+
resume: sdkEmail.resumeCampaign,
|
|
172
|
+
cancel: sdkEmail.cancelCampaign,
|
|
173
|
+
}[verb];
|
|
174
|
+
// start is the billable one: it commits the account to the resolved cost.
|
|
175
|
+
const c = verb === 'start'
|
|
176
|
+
? await retryFunds(() => fn(config.api_key, orgId, id))
|
|
177
|
+
: await fn(config.api_key, orgId, id);
|
|
178
|
+
if (flags.json) {
|
|
179
|
+
printJson(c);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
success(`${c.name} is now ${c.status}`);
|
|
183
|
+
if (verb === 'start') {
|
|
184
|
+
info(`It sends up to its daily limit and continues tomorrow — check with: myapi email campaign stats ${id}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function recipients(id, flags) {
|
|
188
|
+
const config = requireConfig();
|
|
189
|
+
const orgId = requireOrg(flags, config, 'myapi email campaign recipients <id> [--state queued|sent|failed|excluded]');
|
|
190
|
+
const state = typeof flags.state === 'string' ? flags.state : undefined;
|
|
191
|
+
const rows = await sdkEmail.listCampaignRecipients(config.api_key, orgId, id, state);
|
|
192
|
+
if (flags.json) {
|
|
193
|
+
printJson(rows);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
printTable(rows.map(r => ({
|
|
197
|
+
address: r.address,
|
|
198
|
+
state: r.state,
|
|
199
|
+
reason: r.excluded_reason ?? r.error ?? '',
|
|
200
|
+
sent_at: r.sent_at ?? '',
|
|
201
|
+
})), { flags, empty: 'No recipients. Resolve the campaign first.' });
|
|
202
|
+
}
|
|
203
|
+
async function stats(id, flags) {
|
|
204
|
+
const config = requireConfig();
|
|
205
|
+
const orgId = requireOrg(flags, config, 'myapi email campaign stats <id>');
|
|
206
|
+
const s = await sdkEmail.getCampaignStats(config.api_key, orgId, id);
|
|
207
|
+
if (flags.json) {
|
|
208
|
+
printJson(s);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
info(`status ${s.status} · ${s.sent_today} sent today · limit ${s.per_day_limit}/day`);
|
|
212
|
+
const parts = Object.entries(s.by_state ?? {}).map(([k, n]) => `${n} ${k}`);
|
|
213
|
+
info(parts.length ? parts.join(' · ') : 'no recipients resolved yet');
|
|
214
|
+
if (s.status === 'paused_insufficient_funds') {
|
|
215
|
+
info('Paused because the account could not pay. Top up or enable auto-recharge, then: myapi email campaign resume ' + id);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
export async function run(sub, args, flags) {
|
|
219
|
+
if (!sub || (flags.help && !sub)) {
|
|
220
|
+
info(HELP);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const need = (usage) => requireArg(args[0], '<id>', usage);
|
|
224
|
+
switch (sub) {
|
|
225
|
+
case 'create': return create(flags);
|
|
226
|
+
case 'list': return list(flags);
|
|
227
|
+
case 'get': return get(need('myapi email campaign get <id>'), flags);
|
|
228
|
+
case 'update': return update(need('myapi email campaign update <id>'), flags);
|
|
229
|
+
case 'resolve': return resolve(need('myapi email campaign resolve <id>'), flags);
|
|
230
|
+
case 'start': return transition('start', need('myapi email campaign start <id>'), flags);
|
|
231
|
+
case 'pause': return transition('pause', need('myapi email campaign pause <id>'), flags);
|
|
232
|
+
case 'resume': return transition('resume', need('myapi email campaign resume <id>'), flags);
|
|
233
|
+
case 'cancel': return transition('cancel', need('myapi email campaign cancel <id>'), flags);
|
|
234
|
+
case 'recipients': return recipients(need('myapi email campaign recipients <id>'), flags);
|
|
235
|
+
case 'stats': return stats(need('myapi email campaign stats <id>'), flags);
|
|
236
|
+
default: error(`Unknown subcommand: email campaign ${sub}. Run "myapi email campaign --help".`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
@@ -11,7 +11,18 @@ import * as message from './message.js';
|
|
|
11
11
|
import * as warmup from './warmup.js';
|
|
12
12
|
import * as template from './template.js';
|
|
13
13
|
import * as verify from './verify.js';
|
|
14
|
+
import * as campaign from './campaign.js';
|
|
14
15
|
export const SCHEMA = {
|
|
16
|
+
// campaigns (`from` is declared below, shared with `message send`)
|
|
17
|
+
template: 'string',
|
|
18
|
+
addresses: 'string',
|
|
19
|
+
'crm-stage': 'string',
|
|
20
|
+
'crm-origin': 'string',
|
|
21
|
+
'crm-company': 'string',
|
|
22
|
+
'crm-max-days': 'number',
|
|
23
|
+
'crm-min-days': 'number',
|
|
24
|
+
'all-contacts': 'boolean',
|
|
25
|
+
state: 'string',
|
|
15
26
|
org: 'string',
|
|
16
27
|
domain: 'string',
|
|
17
28
|
username: 'string',
|
|
@@ -60,6 +71,7 @@ async function dispatchNamespace(ns, sub, restArgs, flags) {
|
|
|
60
71
|
case 'warmup': return warmup.run(sub, restArgs, flags);
|
|
61
72
|
case 'template': return template.run(sub, restArgs, flags);
|
|
62
73
|
case 'verify': return verify.run(sub, restArgs, flags);
|
|
74
|
+
case 'campaign': return campaign.run(sub, restArgs, flags);
|
|
63
75
|
default: error(`Unknown namespace: ${ns}. Run "myapi email --help" for the list.`);
|
|
64
76
|
}
|
|
65
77
|
}
|
|
@@ -68,6 +80,7 @@ export async function run(subcommand, args, flags) {
|
|
|
68
80
|
info(`Usage: myapi email <namespace> <subcommand>
|
|
69
81
|
|
|
70
82
|
Namespaces:
|
|
83
|
+
campaign Scheduled bulk send (org-scoped) — resolve, start, pause, stats; drains at a daily limit
|
|
71
84
|
mailbox Mailboxes (account-scoped) — create, list, activate-sending
|
|
72
85
|
message Send & read mail (account-scoped) — send, status, sent, inbox, outbox, get
|
|
73
86
|
template Email templates (org-scoped) — generate, list, edit, send-test, delete
|
package/dist/commands/people.js
CHANGED
|
@@ -2,6 +2,11 @@ import { people as sdkPeople } from '@myapihq/sdk';
|
|
|
2
2
|
import { requireConfig } from '../config.js';
|
|
3
3
|
import { error, printTable, info, printJson } from '../output.js';
|
|
4
4
|
import { requireOrg, requireArg } from '../helpers.js';
|
|
5
|
+
import { retryFunds } from '../utils.js';
|
|
6
|
+
// Billable: the backend reserves funds before serving these, so an empty
|
|
7
|
+
// wallet with an auto-recharge in flight answers 402 in_flight with a
|
|
8
|
+
// retry hint. retryFunds waits it out instead of killing an unattended
|
|
9
|
+
// run. Both handlers gate; there is no free people call.
|
|
5
10
|
export const EXPOSES = [
|
|
6
11
|
'POST /people/orgs/{org_id}/search',
|
|
7
12
|
'GET /people/orgs/{org_id}/{person_id}',
|
|
@@ -61,7 +66,7 @@ async function search(flags) {
|
|
|
61
66
|
const config = requireConfig();
|
|
62
67
|
const orgId = requireOrg(flags, config, 'myapi people search [filters...] [--org <id>]');
|
|
63
68
|
const filter = buildFilter(flags);
|
|
64
|
-
const res = await sdkPeople.searchPeople(config.api_key, orgId, filter);
|
|
69
|
+
const res = await retryFunds(() => sdkPeople.searchPeople(config.api_key, orgId, filter));
|
|
65
70
|
if (flags.json) {
|
|
66
71
|
printJson(res);
|
|
67
72
|
return;
|
|
@@ -80,7 +85,7 @@ async function get(personId, flags) {
|
|
|
80
85
|
const config = requireConfig();
|
|
81
86
|
const orgId = requireOrg(flags, config, 'myapi people get <person_id> [--org <id>]');
|
|
82
87
|
requireArg(personId, 'person_id', 'myapi people get <person_id> [--org <id>]');
|
|
83
|
-
const res = await sdkPeople.getPerson(config.api_key, orgId, personId);
|
|
88
|
+
const res = await retryFunds(() => sdkPeople.getPerson(config.api_key, orgId, personId));
|
|
84
89
|
printJson(res);
|
|
85
90
|
}
|
|
86
91
|
export const SUBCOMMAND_USAGE = {
|
package/dist/completion.js
CHANGED
|
@@ -35,7 +35,7 @@ export const COMMANDS = [
|
|
|
35
35
|
// command → subcommands, for `myapi <command> <TAB>`. Mirrors each
|
|
36
36
|
// command's dispatcher; commands absent here take no subcommand.
|
|
37
37
|
export const SUBCOMMANDS = {
|
|
38
|
-
account: ['setup', 'import-key', 'whoami', 'login', 'link', 'switch', 'install-skills', 'config', 'registrant', 'api-keys', 'keys', 'mailing-address'],
|
|
38
|
+
account: ['setup', 'import-key', 'whoami', 'login', 'link', 'switch', 'install-skills', 'config', 'registrant', 'api-keys', 'keys', 'mailing-address', 'sending', 'resume-sending'],
|
|
39
39
|
// The end-user auth product. Operator/account commands live under `account`.
|
|
40
40
|
auth: ['tenant', 'client', 'usage', 'domain'],
|
|
41
41
|
org: ['create', 'delete', 'get', 'import', 'list', 'sync-brand', 'update'],
|
package/dist/exposes.test.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -411,6 +411,8 @@ async function dispatchAccount(subcommand, restArgs, flags) {
|
|
|
411
411
|
}
|
|
412
412
|
switch (subcommand) {
|
|
413
413
|
case 'mailing-address': return accountCmd.run('mailing-address', restArgs, flags);
|
|
414
|
+
case 'sending': return accountCmd.run('sending', restArgs, flags);
|
|
415
|
+
case 'resume-sending': return accountCmd.run('resume-sending', restArgs, flags);
|
|
414
416
|
case 'setup': return setupCmd.setup(flags);
|
|
415
417
|
case 'import-key': return setupCmd.importKey(restArgs[0], flags);
|
|
416
418
|
case 'whoami': return accountCmd.whoami(flags);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: my-api-hq
|
|
3
|
-
version: 1.
|
|
3
|
+
version: 1.3.0
|
|
4
4
|
description: >
|
|
5
5
|
Auth, organizations, and billing hub. Start here to get an api_key and org_id — every other service depends on both.
|
|
6
6
|
triggers: [api key, account, organization, org, billing, balance, topup, credits, setup, defaults, brand, sync brand, doctor, health check, is my org healthy]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-fd266da15b05dee12ce1e683612b76e0f5554247f255a9c8db9be006a403a8ff
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyApiHQ
|
|
@@ -26,7 +26,7 @@ An anonymous account can upgrade at any time via `myapi account link <email>`
|
|
|
26
26
|
|
|
27
27
|
### Health check
|
|
28
28
|
|
|
29
|
-
`myapi doctor` runs an org-wide consistency check across every slot
|
|
29
|
+
`myapi doctor` runs an org-wide consistency check across every slot and layers on customer-perspective DNS/HTTP probes from your machine. It returns per-section findings (`✓` pass / `⚠` warning / `✗` critical) with remediation hints; add `--json` for machine output. The exit code is non-zero **only** on customer-actionable criticals — platform-side issues the MyAPI team is already handling are surfaced with an `ℹ` marker but don't fail the run. Run it to self-check before building (is the org set up?) and after (did everything wire up?).
|
|
30
30
|
<!-- llm:end -->
|
|
31
31
|
|
|
32
32
|
## Commands
|
|
@@ -53,6 +53,7 @@ An anonymous account can upgrade at any time via `myapi account link <email>`
|
|
|
53
53
|
| `myapi billing usage [--period month|30d]` | Spend rolled up by service (month or trailing 30d) |
|
|
54
54
|
| `myapi billing spend-cap [<amount> | clear] [--period month|day]` | Set/show/clear the account-level spend ceiling |
|
|
55
55
|
| `myapi billing auto-recharge [show \| set \| disable]` | Keep the wallet funded — off-session refill when balance drops below a threshold, capped monthly |
|
|
56
|
+
| `myapi account sending` / `myapi account resume-sending` | Is sending paused (bounces), and turn it on |
|
|
56
57
|
| `myapi account mailing-address ["<address>"]` | Get or set the account's CAN-SPAM mailing address (required for email send) |
|
|
57
58
|
| `myapi config set-org <id>` / `set-funnel <id>` / `set-domain <name>` | Set CLI defaults |
|
|
58
59
|
| `myapi install-skills` | Install agent skills into ~/.claude/, ~/.gemini/, ~/.cursor/ |
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: my-email-api
|
|
3
|
-
version: 1.0
|
|
3
|
+
version: 1.1.0
|
|
4
4
|
description: >
|
|
5
5
|
Send transactional and bulk email from your own domain. Create mailboxes, send/receive messages, generate AI templates, and manage warmup.
|
|
6
6
|
triggers: [email, mailbox, send email, transactional email, template, warmup, inbox, outbox, ses, sender reputation]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-17c2dfb50676262f2fe38688f5e7d9cfce3504354c17c06c3dd1dd1b8e39fa5e
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyEmailAPI
|
|
@@ -28,6 +28,7 @@ A registered domain via **mydomainapi** is the prerequisite — mailboxes need a
|
|
|
28
28
|
| `email message` | `send`, `status`, `sent`, `inbox`, `outbox`, `get` | Transactional send + read |
|
|
29
29
|
| `email warmup` | `start`, `stats`, `pause`, `resume`, `stop` | IP/domain warmup for sending reputation |
|
|
30
30
|
| `email template` | `generate`, `list`, `get`, `preview`, `edit`, `send-test`, `delete` | AI-generated HTML templates |
|
|
31
|
+
| `email campaign` | `create`, `list`, `get`, `update`, `resolve`, `start`, `pause`, `resume`, `cancel`, `recipients`, `stats` | Scheduled bulk send that drains at a daily limit |
|
|
31
32
|
<!-- generated:end -->
|
|
32
33
|
|
|
33
34
|
## Examples
|
|
@@ -72,10 +73,54 @@ myapi email warmup stats --address hello@yourdomain.com
|
|
|
72
73
|
| `--per-day` | `warmup start` | Cap the daily volume the ramp climbs to |
|
|
73
74
|
| `--emails`, `--quick` | `verify bulk` | Addresses inline rather than on stdin; skip the catch-all probe |
|
|
74
75
|
|
|
76
|
+
| `--addresses <csv>` | `campaign create` | An explicit short recipient list |
|
|
77
|
+
| `--crm-stage`, `--crm-origin`, `--crm-company` | `campaign create` | Draw recipients from CRM instead |
|
|
78
|
+
| `--crm-max-days`, `--crm-min-days` | `campaign create` | Engaged more than / within N days ago |
|
|
79
|
+
| `--all-contacts` | `campaign create` | Every CRM contact — the unfiltered query, asked for by name |
|
|
80
|
+
| `--state` | `campaign recipients` | Filter by `queued`, `sent`, `failed` or `excluded` |
|
|
81
|
+
|
|
75
82
|
Forwarding keeps the original: `set-forwarding <user@domain> <forward-to@domain>`
|
|
76
83
|
copies every inbound message to an external address and leaves it in the mailbox.
|
|
77
84
|
`clear-forwarding <user@domain>` stops it.
|
|
78
85
|
|
|
86
|
+
## Campaigns — a scheduled send, not a bulk one
|
|
87
|
+
|
|
88
|
+
A campaign is scheduling on top of `message send`. It picks a **source**, freezes
|
|
89
|
+
it, then drains at a daily limit.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Draw from CRM: qualified contacts not engaged in 30+ days
|
|
93
|
+
myapi email campaign create --name "Q3 re-engage" \
|
|
94
|
+
--template <template-id> --from you@yourdomain.com \
|
|
95
|
+
--crm-stage qualified --crm-max-days 30
|
|
96
|
+
|
|
97
|
+
myapi email campaign resolve <id> # who it reaches + what it costs. SENDS NOTHING.
|
|
98
|
+
myapi email campaign start <id> # begins; consent to the cost
|
|
99
|
+
myapi email campaign stats <id> # counts by state, sent today, daily limit
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Four things that decide how you use it:
|
|
103
|
+
|
|
104
|
+
- **`resolve` before `start`, always.** Resolve freezes the recipient set and
|
|
105
|
+
reports the count and the estimated cost without sending. It is the only way
|
|
106
|
+
to see what a start commits to, and `start` refuses an unresolved campaign.
|
|
107
|
+
- **It does NOT send all at once.** A campaign spends `per_day_limit` per day,
|
|
108
|
+
spread across the day, and continues tomorrow. A campaign still `active` the
|
|
109
|
+
next morning is working, not stuck — bursting a list is the surest way to get
|
|
110
|
+
an account's sending paused.
|
|
111
|
+
- **Recipients come from a source, not an upload.** `--crm-*` filters CRM
|
|
112
|
+
contacts (and sends land on their timeline); `--addresses` is for a short
|
|
113
|
+
ad-hoc list. There is no file upload: import into CRM first, and suppression,
|
|
114
|
+
unsubscribes and history then apply to those people like everyone else.
|
|
115
|
+
- **Exclusions are reported, not hidden.** Resolve returns counts by reason —
|
|
116
|
+
suppressed, duplicate, invalid — so a set smaller than the source is
|
|
117
|
+
explainable. Suppression is checked AGAIN at send time, so someone who
|
|
118
|
+
unsubscribes mid-campaign is dropped rather than mailed.
|
|
119
|
+
|
|
120
|
+
`pause` stops after the message in flight; `resume` continues; `cancel` ends it
|
|
121
|
+
and drops what was queued. Status `paused_insufficient_funds` means the account
|
|
122
|
+
could not pay — top up or enable auto-recharge, then `resume`.
|
|
123
|
+
|
|
79
124
|
## Notes
|
|
80
125
|
|
|
81
126
|
- A mailbox is uniquely identified by its address (`username@domain`).
|
|
@@ -111,6 +156,7 @@ reply { "success": true, "data": …, "error": null, "meta": {…} }
|
|
|
111
156
|
|
|
112
157
|
- **Per-slot host** — do not assume one host serves every slot.
|
|
113
158
|
- **Account-scoped, not org-scoped** — no `{org_id}` segment; the key identifies the account.
|
|
159
|
+
- **Lists page** — one page is not the whole list; check `meta.has_more`.
|
|
114
160
|
<!-- http:end -->
|
|
115
161
|
|
|
116
162
|
Run `myapi email --help` or `myapi email <namespace> --help` for full flag reference.
|
|
@@ -4,7 +4,7 @@ version: 1.0.0
|
|
|
4
4
|
description: >
|
|
5
5
|
Synchronous single-address email verification — syntax + DNS + Microsoft GetCredentialType probe. Returns a verdict in <1s for ~50% of inputs; the rest get verdict='unknown' with smtp_recommended=true. The pre-send quality gate for any outbound campaign.
|
|
6
6
|
triggers: [email verify, email validation, deliverability, smtp, syntax check, dns mx, microsoft, mx lookup, bounce prevention]
|
|
7
|
-
checksum: sha256-
|
|
7
|
+
checksum: sha256-0a654a6674bb40863d9bbffb3e6862e10f7e7603695e995416779f0a6b312865
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MyEmailVerifyAPI
|
|
@@ -103,6 +103,7 @@ reply { "success": true, "data": …, "error": null, "meta": {…} }
|
|
|
103
103
|
|
|
104
104
|
- **Per-slot host** — do not assume one host serves every slot.
|
|
105
105
|
- **Org id goes in the PATH** — there is no `X-Org-Id` header.
|
|
106
|
+
- **Lists page** — one page is not the whole list; check `meta.has_more`.
|
|
106
107
|
<!-- http:end -->
|
|
107
108
|
|
|
108
109
|
Run `myapi email verify --help` for inline reference.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myapihq/cli",
|
|
3
3
|
"license": "Apache-2.0",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.22.0",
|
|
5
5
|
"description": "MyAPI command-line interface",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"lint:skills:strict": "node scripts/copy-skills.js && node scripts/lint-skills.js --strict"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@myapihq/sdk": "^2.
|
|
49
|
+
"@myapihq/sdk": "^2.22.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/node": "^25.6.0",
|