mailery 0.3.0 → 0.3.1
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/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -121,7 +121,7 @@ async function setupSendgrid(opts) {
|
|
|
121
121
|
info("SendGrid \u2192 checking Signed Event Webhook\u2026");
|
|
122
122
|
const signed = await sg.getSignedWebhookSettings();
|
|
123
123
|
let webhookKey;
|
|
124
|
-
if (signed.
|
|
124
|
+
if (signed.public_key) {
|
|
125
125
|
info(" = signing already enabled; reusing existing public key");
|
|
126
126
|
webhookKey = signed.public_key;
|
|
127
127
|
} else {
|
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/setup-sendgrid.ts","../src/cli/interactive.ts","../src/cli/index.ts"],"names":["readline","input","output"],"mappings":"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","file":"cli.cjs","sourcesContent":["/**\n * `mailery setup-sendgrid` — one-shot wiring of SendGrid for a domain.\n *\n * Idempotent. Re-running converges: existing domain auths are reused, existing\n * webhook config is preserved unless `--force` is passed, existing Cloudflare\n * DNS records with the correct values are left alone.\n *\n * npx mailery setup-sendgrid \\\n * --domain news.example.com \\\n * --webhook-url https://example.com/m/webhooks/sendgrid \\\n * --cloudflare\n *\n * Required env:\n * SENDGRID_API_KEY — full-access (or at minimum Mail Settings + Sender Auth)\n * CLOUDFLARE_API_TOKEN — only if --cloudflare; needs Zone:Read + DNS:Edit on the zone\n */\n\nexport interface SetupSendgridOpts {\n /** One or more domains to authenticate, e.g. [\"news.example.com\", \"mail.example.com\"]. */\n domains: string[]\n /** Sub-label SendGrid uses for the link branding CNAME (default 'em'). */\n subdomain?: string\n /** Where SendGrid should POST event webhooks. Account-level — only configured once regardless of domain count. */\n webhookUrl: string\n /** Publish DNS records via Cloudflare API. Requires CLOUDFLARE_API_TOKEN. */\n cloudflare?: boolean\n /** Override the parent zone if any domain isn't a subdomain of its eTLD+1. */\n cloudflareZone?: string\n /** Replace an existing webhook URL if one is already set. */\n force?: boolean\n /** Logger; defaults to console. Pass {} to silence. */\n logger?: { log?: (...a: unknown[]) => void; warn?: (...a: unknown[]) => void }\n /** Override fetch (for tests). */\n fetchFn?: typeof fetch\n /** Override env (for tests). */\n env?: Record<string, string | undefined>\n}\n\nexport interface DomainSetupResult {\n domain: string\n domainAuthId: number\n alreadyValidated: boolean\n dnsRecords: Array<{ type: string; host: string; data: string }>\n cloudflarePushed: number\n}\n\nexport interface SetupSendgridResult {\n domains: DomainSetupResult[]\n webhookKey: string\n webhookUrl: string\n envSnippet: string\n}\n\ninterface SgDomainAuth {\n id: number\n domain: string\n subdomain: string\n username: string\n valid: boolean\n dns: Record<string, { host: string; type: string; data: string; valid: boolean }>\n}\n\nexport async function setupSendgrid(opts: SetupSendgridOpts): Promise<SetupSendgridResult> {\n const env = opts.env ?? process.env\n const log = opts.logger ?? console\n const info = (m: string) => log.log?.(m)\n const warn = (m: string) => log.warn?.(m)\n const f = opts.fetchFn ?? globalThis.fetch\n\n const sgKey = env.SENDGRID_API_KEY\n if (!sgKey) {\n throw new Error(\n 'SENDGRID_API_KEY env var is required.\\n' +\n ' Create one at: https://app.sendgrid.com/settings/api_keys\\n' +\n ' Needs at least: \"Mail Settings\" (Full Access) and \"Sender Authentication\" (Full Access).\\n' +\n ' Then add to your shell rc (e.g. ~/.zshrc): export SENDGRID_API_KEY=\"SG.xxx\"',\n )\n }\n if (!/^SG\\./.test(sgKey)) {\n throw new Error(\n 'SENDGRID_API_KEY does not look like a SendGrid API key (expected to start with \"SG.\"). ' +\n 'Double-check you copied the full key from https://app.sendgrid.com/settings/api_keys.',\n )\n }\n\n if (opts.cloudflare && !env.CLOUDFLARE_API_TOKEN) {\n throw new Error(\n 'CLOUDFLARE_API_TOKEN env var is required when --cloudflare is set.\\n' +\n ' Create one at: https://dash.cloudflare.com/profile/api-tokens\\n' +\n ' Use the \"Edit zone DNS\" template, restricted to the specific zone you\\'re publishing into.\\n' +\n ' Permissions: Zone:Read + DNS:Edit.\\n' +\n ' Then add to your shell rc: export CLOUDFLARE_API_TOKEN=\"cf-xxx\"',\n )\n }\n\n if (!opts.domains || opts.domains.length === 0) {\n throw new Error('At least one --domain is required')\n }\n\n const sg = sendgridClient(sgKey, f)\n const cf = opts.cloudflare ? cloudflareClient(env.CLOUDFLARE_API_TOKEN!, f) : null\n\n // ---- Per-domain: authentication + DNS publish ---------------------------\n const domainResults: DomainSetupResult[] = []\n for (const domain of opts.domains) {\n info('')\n info(`=== ${domain} ===`)\n info(`SendGrid → checking domain authentication for ${domain}…`)\n const existing = await sg.findDomainAuth(domain)\n let auth: SgDomainAuth\n if (existing) {\n info(` → reusing existing domain auth #${existing.id}`)\n auth = existing\n } else {\n info(` → creating new domain auth`)\n auth = await sg.createDomainAuth({\n domain,\n subdomain: opts.subdomain ?? 'em',\n })\n }\n\n const dnsRecords = Object.values(auth.dns).map((r) => ({ type: r.type, host: r.host, data: r.data }))\n let cloudflarePushed = 0\n\n if (cf) {\n info(`Cloudflare → publishing ${dnsRecords.length} record(s)…`)\n const zoneName = opts.cloudflareZone ?? inferZone(domain)\n const zoneId = await cf.findZoneId(zoneName)\n if (!zoneId) {\n throw new Error(`Cloudflare zone \"${zoneName}\" not found on this account`)\n }\n for (const rec of dnsRecords) {\n const result = await cf.upsertRecord(zoneId, rec)\n if (result === 'created') {\n cloudflarePushed++\n info(` + ${rec.type} ${rec.host} → ${rec.data}`)\n } else if (result === 'updated') {\n cloudflarePushed++\n info(` ~ ${rec.type} ${rec.host} (updated to ${rec.data})`)\n } else {\n info(` = ${rec.type} ${rec.host} (already correct)`)\n }\n }\n\n info('SendGrid → triggering validation…')\n const validated = await sg.validateDomainAuth(auth.id)\n if (validated) {\n info(' ✓ domain authenticated')\n } else {\n warn(' ! validation not yet successful — DNS may still be propagating. Re-run in a few minutes.')\n }\n auth.valid = validated\n } else {\n info('— skipping Cloudflare publish (no --cloudflare). Add these records to your DNS provider:')\n for (const rec of dnsRecords) {\n info(` ${rec.type.padEnd(7)} ${rec.host.padEnd(50)} ${rec.data}`)\n }\n }\n\n domainResults.push({\n domain,\n domainAuthId: auth.id,\n alreadyValidated: auth.valid,\n dnsRecords,\n cloudflarePushed,\n })\n }\n\n info('')\n info('=== Event webhook (account-level) ===')\n\n // ---- Account-level: Enable Signed Event Webhook + fetch public key -----\n info('SendGrid → checking Signed Event Webhook…')\n const signed = await sg.getSignedWebhookSettings()\n let webhookKey: string\n if (signed.enabled && signed.public_key) {\n info(' = signing already enabled; reusing existing public key')\n webhookKey = signed.public_key\n } else {\n info(' → enabling signing')\n webhookKey = await sg.enableSignedWebhook()\n info(' ✓ verification key fetched')\n }\n\n // ---- Step 4: Configure Event Webhook URL + event toggles ----------------\n info('SendGrid → checking Event Webhook…')\n const currentEvents = await sg.getEventWebhookSettings()\n const desiredEvents = {\n url: opts.webhookUrl,\n enabled: true,\n delivered: true,\n open: true,\n click: true,\n bounce: true,\n dropped: true,\n spam_report: true,\n unsubscribe: true,\n deferred: false,\n processed: false,\n group_resubscribe: false,\n group_unsubscribe: false,\n }\n if (currentEvents.url && currentEvents.url !== opts.webhookUrl && !opts.force) {\n throw new Error(\n `Event webhook is already configured for ${currentEvents.url}. ` +\n `Pass --force to overwrite (this will redirect all future events to ${opts.webhookUrl}).`,\n )\n }\n if (eventSettingsMatch(currentEvents, desiredEvents)) {\n info(` = webhook already configured for ${opts.webhookUrl}; nothing to change`)\n } else {\n await sg.setEventWebhookSettings(desiredEvents)\n info(` ✓ webhook URL set to ${opts.webhookUrl}`)\n }\n\n const envSnippet = [\n `# Add to your environment (or .env file):`,\n `SENDGRID_WEBHOOK_VERIFICATION_KEY=\"${webhookKey.replace(/\\n/g, '\\\\n')}\"`,\n ].join('\\n')\n\n info('')\n info('--- Done. Add to your environment ---')\n info(envSnippet)\n\n return {\n domains: domainResults,\n webhookKey,\n webhookUrl: opts.webhookUrl,\n envSnippet,\n }\n}\n\n// ---------------------------------------------------------------------------\n// SendGrid API client\n// ---------------------------------------------------------------------------\n\ninterface SendGridClient {\n findDomainAuth(domain: string): Promise<SgDomainAuth | null>\n createDomainAuth(input: { domain: string; subdomain: string }): Promise<SgDomainAuth>\n validateDomainAuth(id: number): Promise<boolean>\n getSignedWebhookSettings(): Promise<{ enabled?: boolean; public_key?: string }>\n enableSignedWebhook(): Promise<string>\n getEventWebhookSettings(): Promise<{ url?: string; enabled?: boolean; [k: string]: unknown }>\n setEventWebhookSettings(payload: Record<string, unknown>): Promise<void>\n}\n\nfunction sendgridClient(apiKey: string, fetchFn: typeof fetch): SendGridClient {\n const base = 'https://api.sendgrid.com/v3'\n const headers = { authorization: `Bearer ${apiKey}`, 'content-type': 'application/json' }\n\n async function call(path: string, init: RequestInit = {}): Promise<any> {\n const res = await fetchFn(`${base}${path}`, { ...init, headers: { ...headers, ...(init.headers as any) } })\n const text = await res.text()\n const body = text ? safeJson(text) : null\n if (res.status === 401 || res.status === 403) {\n throw new Error(\n `SendGrid rejected the request (HTTP ${res.status}). Your SENDGRID_API_KEY is missing the required permissions ` +\n 'or is invalid. The key needs \"Mail Settings\" (Full Access) and \"Sender Authentication\" (Full Access). ' +\n 'Manage keys at https://app.sendgrid.com/settings/api_keys.',\n )\n }\n if (!res.ok) {\n const message = body?.errors?.[0]?.message ?? text ?? res.statusText\n throw new Error(`SendGrid ${init.method ?? 'GET'} ${path} → ${res.status}: ${message}`)\n }\n return body\n }\n\n return {\n async findDomainAuth(domain: string) {\n const list: SgDomainAuth[] = (await call(`/whitelabel/domains?limit=200`)) ?? []\n return list.find((d) => d.domain === domain) ?? null\n },\n async createDomainAuth({ domain, subdomain }) {\n return call(`/whitelabel/domains`, {\n method: 'POST',\n body: JSON.stringify({ domain, subdomain, automatic_security: true }),\n }) as Promise<SgDomainAuth>\n },\n async validateDomainAuth(id: number) {\n const r = await call(`/whitelabel/domains/${id}/validate`, { method: 'POST' })\n return Boolean(r?.valid)\n },\n async getSignedWebhookSettings() {\n return call(`/user/webhooks/event/settings/signed`)\n },\n async enableSignedWebhook() {\n await call(`/user/webhooks/event/settings/signed`, {\n method: 'PATCH',\n body: JSON.stringify({ enabled: true }),\n })\n const r = await call(`/user/webhooks/event/settings/signed`)\n if (!r?.public_key) throw new Error('SendGrid did not return a public_key for the signed event webhook')\n return r.public_key as string\n },\n async getEventWebhookSettings() {\n return call(`/user/webhooks/event/settings`)\n },\n async setEventWebhookSettings(payload) {\n await call(`/user/webhooks/event/settings`, {\n method: 'PATCH',\n body: JSON.stringify(payload),\n })\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Cloudflare API client\n// ---------------------------------------------------------------------------\n\ninterface CloudflareClient {\n findZoneId(name: string): Promise<string | null>\n upsertRecord(zoneId: string, rec: { type: string; host: string; data: string }): Promise<'created' | 'updated' | 'noop'>\n}\n\nfunction cloudflareClient(apiToken: string, fetchFn: typeof fetch): CloudflareClient {\n const base = 'https://api.cloudflare.com/client/v4'\n const headers = { authorization: `Bearer ${apiToken}`, 'content-type': 'application/json' }\n\n async function call(path: string, init: RequestInit = {}): Promise<any> {\n const res = await fetchFn(`${base}${path}`, { ...init, headers: { ...headers, ...(init.headers as any) } })\n const body: any = await res.json().catch(() => null)\n if (res.status === 401 || res.status === 403) {\n throw new Error(\n `Cloudflare rejected the request (HTTP ${res.status}). Your CLOUDFLARE_API_TOKEN is invalid, expired, ` +\n 'or missing the required permissions. The token needs Zone:Read + DNS:Edit on the specific zone you\\'re ' +\n 'publishing into. Manage tokens at https://dash.cloudflare.com/profile/api-tokens.',\n )\n }\n if (!res.ok || (body && body.success === false)) {\n const message = body?.errors?.[0]?.message ?? res.statusText\n throw new Error(`Cloudflare ${init.method ?? 'GET'} ${path} → ${res.status}: ${message}`)\n }\n return body\n }\n\n return {\n async findZoneId(name: string) {\n const r = await call(`/zones?name=${encodeURIComponent(name)}`)\n return r?.result?.[0]?.id ?? null\n },\n async upsertRecord(zoneId, rec) {\n // CF requires the host to be a FQDN, which SendGrid already gives us.\n const existing = await call(\n `/zones/${zoneId}/dns_records?type=${encodeURIComponent(rec.type)}&name=${encodeURIComponent(rec.host)}`,\n )\n const match = existing?.result?.[0]\n const desiredBody = {\n type: rec.type,\n name: rec.host,\n content: rec.data,\n ttl: 1, // auto\n proxied: false, // critical: SendGrid DKIM CNAMEs must NOT be proxied\n }\n if (!match) {\n await call(`/zones/${zoneId}/dns_records`, { method: 'POST', body: JSON.stringify(desiredBody) })\n return 'created'\n }\n if (match.content === rec.data && match.proxied === false) {\n return 'noop'\n }\n await call(`/zones/${zoneId}/dns_records/${match.id}`, {\n method: 'PUT',\n body: JSON.stringify(desiredBody),\n })\n return 'updated'\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Best-effort eTLD+1 inference. Falls back to last two labels — fine for most\n * `.com` / `.io` style domains. For multi-label public suffixes (`.co.uk`)\n * pass `--cloudflare-zone` explicitly. */\nfunction inferZone(domain: string): string {\n const labels = domain.split('.').filter(Boolean)\n if (labels.length <= 2) return domain\n return labels.slice(-2).join('.')\n}\n\nfunction safeJson(s: string): any {\n try { return JSON.parse(s) } catch { return null }\n}\n\n/** Compare current SendGrid event webhook settings to the desired payload.\n * Treats missing keys on `current` as \"not yet set\" rather than equal-to-false,\n * so a partially-configured webhook still triggers a PATCH. */\nfunction eventSettingsMatch(current: Record<string, unknown>, desired: Record<string, unknown>): boolean {\n for (const [k, v] of Object.entries(desired)) {\n if (current[k] !== v) return false\n }\n return true\n}\n","/**\n * Interactive wizard for `mailery setup-sendgrid`. Walks the user through:\n * - Required env vars (offers to paste them in, then prints export lines)\n * - Domains (accept multiple via repeated entry or comma-separation)\n * - Webhook URL (with a default derived from the first domain's apex)\n * - Cloudflare toggle + zone override\n * - Force flag\n *\n * Renders a summary before any API call and asks for confirmation. All\n * questions are funneled through a `Prompter` so tests can inject canned\n * answers without touching stdin.\n */\n\nimport * as readline from 'node:readline/promises'\nimport { stdin as input, stdout as output } from 'node:process'\n\nimport type { SetupSendgridOpts } from './setup-sendgrid.js'\n\nexport type Prompter = (question: string, opts?: { defaultValue?: string }) => Promise<string>\n\nexport interface InteractiveResult {\n opts: Omit<SetupSendgridOpts, 'fetchFn' | 'logger' | 'env'>\n envOverrides: Record<string, string>\n exportLines: string[]\n proceed: boolean\n}\n\nexport function makeReadlinePrompter(): { prompter: Prompter; close: () => void } {\n const rl = readline.createInterface({ input, output })\n const prompter: Prompter = async (q, opts) => {\n const suffix = opts?.defaultValue ? ` [${opts.defaultValue}]` : ''\n const raw = await rl.question(`${q}${suffix}: `)\n const trimmed = raw.trim()\n return trimmed.length === 0 && opts?.defaultValue ? opts.defaultValue : trimmed\n }\n return { prompter, close: () => rl.close() }\n}\n\nexport async function runInteractive(\n prompter: Prompter,\n env: Record<string, string | undefined>,\n logger: { log: (...a: unknown[]) => void } = console,\n): Promise<InteractiveResult> {\n const log = (m: string) => logger.log(m)\n const exportLines: string[] = []\n const envOverrides: Record<string, string> = {}\n\n log('')\n log('mailery setup-sendgrid (interactive)')\n log('────────────────────────────────────')\n log('')\n\n // ---- SendGrid API key ---------------------------------------------------\n if (!env.SENDGRID_API_KEY) {\n log('SENDGRID_API_KEY is not set in your environment.')\n log(' Create one at https://app.sendgrid.com/settings/api_keys')\n log(' Permissions: Mail Settings (Full Access) + Sender Authentication (Full Access).')\n log('')\n const key = await prompter('Paste your SendGrid API key (starts with SG.)')\n if (!key) throw new Error('SENDGRID_API_KEY is required')\n envOverrides.SENDGRID_API_KEY = key\n exportLines.push(`export SENDGRID_API_KEY=\"${key}\"`)\n }\n\n // ---- Domains ------------------------------------------------------------\n log('')\n log('Which sender domain(s) do you want to authenticate?')\n log(' Examples:')\n log(' news.example.com (one)')\n log(' news.example.com, mail.example.com (two — comma-separated, in one line)')\n log(' Splitting marketing from transactional onto separate domains is recommended.')\n log('')\n const domainsRaw = await prompter('Domains')\n const domains = domainsRaw\n .split(',')\n .map((d) => d.trim())\n .filter(Boolean)\n if (domains.length === 0) throw new Error('at least one --domain is required')\n\n // ---- Webhook URL --------------------------------------------------------\n const defaultWebhook = `https://${apex(domains[0]!)}/m/webhooks/sendgrid`\n log('')\n log('SendGrid will POST event webhooks to this URL.')\n log(' This must be reachable from the public internet (not behind admin auth).')\n log('')\n const webhookUrl = await prompter('Webhook URL', { defaultValue: defaultWebhook })\n\n // ---- Cloudflare ---------------------------------------------------------\n log('')\n const cloudflareRaw = await prompter('Publish DNS records via Cloudflare API? (y/N)', { defaultValue: 'n' })\n const cloudflare = /^y/i.test(cloudflareRaw)\n let cloudflareZone: string | undefined\n\n if (cloudflare) {\n if (!env.CLOUDFLARE_API_TOKEN) {\n log('')\n log('CLOUDFLARE_API_TOKEN is not set in your environment.')\n log(' Create one at https://dash.cloudflare.com/profile/api-tokens')\n log(' Use the \"Edit zone DNS\" template, restricted to your zone(s).')\n log(' Permissions: Zone:Read + DNS:Edit.')\n log('')\n const token = await prompter('Paste your Cloudflare API token')\n if (!token) throw new Error('CLOUDFLARE_API_TOKEN is required when publishing via Cloudflare')\n envOverrides.CLOUDFLARE_API_TOKEN = token\n exportLines.push(`export CLOUDFLARE_API_TOKEN=\"${token}\"`)\n }\n log('')\n const zoneOverride = await prompter('Override Cloudflare zone (blank to infer per-domain)', { defaultValue: '' })\n if (zoneOverride.length > 0) cloudflareZone = zoneOverride\n }\n\n // ---- Force --------------------------------------------------------------\n log('')\n const forceRaw = await prompter(\n 'If a different webhook URL is already configured on this SendGrid account, overwrite it? (y/N)',\n { defaultValue: 'n' },\n )\n const force = /^y/i.test(forceRaw)\n\n // ---- Summary + confirm --------------------------------------------------\n log('')\n log('About to run setup with:')\n log(` domains: ${domains.join(', ')}`)\n log(` webhook URL: ${webhookUrl}`)\n log(` cloudflare: ${cloudflare ? 'yes' : 'no'}`)\n if (cloudflareZone) log(` cloudflare zone: ${cloudflareZone}`)\n log(` force: ${force ? 'yes' : 'no'}`)\n log('')\n const confirmRaw = await prompter('Proceed? (Y/n)', { defaultValue: 'y' })\n const proceed = !/^n/i.test(confirmRaw)\n\n return {\n opts: { domains, webhookUrl, cloudflare, cloudflareZone, force },\n envOverrides,\n exportLines,\n proceed,\n }\n}\n\n/** Strip a leading sub-label so we get the apex (best-effort for webhook URL default). */\nfunction apex(domain: string): string {\n const labels = domain.split('.').filter(Boolean)\n if (labels.length <= 2) return domain\n return labels.slice(-2).join('.')\n}\n","#!/usr/bin/env node\n/**\n * mailery CLI entry. Currently exposes a single command:\n *\n * npx mailery setup-sendgrid --domain X --webhook-url Y [--cloudflare]\n *\n * See src/cli/setup-sendgrid.ts for the full option list.\n */\n\nimport { setupSendgrid } from './setup-sendgrid.js'\nimport { makeReadlinePrompter, runInteractive } from './interactive.js'\n\nasync function main() {\n const argv = process.argv.slice(2)\n const cmd = argv[0]\n\n if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') {\n printUsage()\n process.exit(cmd ? 0 : 1)\n }\n\n switch (cmd) {\n case 'setup-sendgrid': {\n const opts = parseArgs(argv.slice(1))\n const argDomains = collectStringList(opts.domain)\n const argWebhook = typeof opts['webhook-url'] === 'string' ? (opts['webhook-url'] as string) : null\n const explicitInteractive = Boolean(opts.interactive)\n const explicitNonInteractive = Boolean(opts['no-interactive'])\n\n // Decide interactive vs args. Interactive when: --interactive, OR no\n // required args provided AND we have a TTY. Force non-interactive with\n // --no-interactive (useful for CI).\n const wantsInteractive =\n explicitInteractive ||\n (!explicitNonInteractive && argDomains.length === 0 && !argWebhook && Boolean(process.stdin.isTTY))\n\n try {\n if (wantsInteractive) {\n const { prompter, close } = makeReadlinePrompter()\n let interactive\n try {\n interactive = await runInteractive(prompter, process.env)\n } finally {\n close()\n }\n if (!interactive.proceed) {\n console.log('Aborted.')\n process.exit(0)\n }\n await setupSendgrid({\n ...interactive.opts,\n env: { ...process.env, ...interactive.envOverrides },\n })\n if (interactive.exportLines.length > 0) {\n console.log('')\n console.log('Add these to your shell rc (e.g. ~/.zshrc) so future runs find them:')\n for (const line of interactive.exportLines) console.log(` ${line}`)\n }\n } else {\n if (argDomains.length === 0) {\n console.error('error: --domain is required (repeat or comma-separate for multiple, or run without args for interactive mode)')\n printUsage()\n process.exit(2)\n }\n if (!argWebhook) {\n console.error('error: --webhook-url is required')\n printUsage()\n process.exit(2)\n }\n await setupSendgrid({\n domains: argDomains,\n subdomain: opts.subdomain ? String(opts.subdomain) : undefined,\n webhookUrl: argWebhook,\n cloudflare: Boolean(opts.cloudflare),\n cloudflareZone: opts['cloudflare-zone'] ? String(opts['cloudflare-zone']) : undefined,\n force: Boolean(opts.force),\n })\n }\n } catch (err: any) {\n console.error(`\\n✗ ${err?.message ?? err}`)\n process.exit(1)\n }\n break\n }\n default:\n console.error(`unknown command: ${cmd}`)\n printUsage()\n process.exit(2)\n }\n}\n\nfunction parseArgs(argv: string[]): Record<string, string | string[] | boolean> {\n const out: Record<string, string | string[] | boolean> = {}\n function setKey(key: string, value: string | boolean) {\n const existing = out[key]\n if (existing === undefined) {\n out[key] = value\n } else if (Array.isArray(existing)) {\n if (typeof value === 'string') existing.push(value)\n } else if (typeof existing === 'string' && typeof value === 'string') {\n out[key] = [existing, value]\n } else {\n out[key] = value\n }\n }\n for (let i = 0; i < argv.length; i++) {\n const tok = argv[i]!\n if (!tok.startsWith('--')) continue\n const eq = tok.indexOf('=')\n if (eq > 0) {\n setKey(tok.slice(2, eq), tok.slice(eq + 1))\n continue\n }\n const key = tok.slice(2)\n const next = argv[i + 1]\n if (next && !next.startsWith('--')) {\n setKey(key, next)\n i++\n } else {\n setKey(key, true)\n }\n }\n return out\n}\n\n/** Collapse a repeated/comma-separated flag value into a clean string[]. */\nfunction collectStringList(value: string | string[] | boolean | undefined): string[] {\n if (value === undefined || value === true || value === false) return []\n const raw = Array.isArray(value) ? value : [value]\n return raw\n .flatMap((s) => String(s).split(','))\n .map((s) => s.trim())\n .filter(Boolean)\n}\n\nfunction printUsage() {\n console.log(`\nmailery CLI\n\nUsage:\n mailery setup-sendgrid (interactive — prompts you for everything)\n mailery setup-sendgrid --domain <d>[,<d>...] --webhook-url <u> [options] (non-interactive)\n\nOptions:\n --domain <d> Domain to authenticate. Repeat (or comma-separate)\n to authenticate multiple in one run, e.g. when\n isolating marketing from transactional. The event\n webhook itself is account-level and only gets set up\n once.\n --subdomain <s> Sub-label for the link branding CNAME (default: em)\n --webhook-url <u> Public URL for the SendGrid event webhook\n --cloudflare Publish DNS records via the Cloudflare API\n --cloudflare-zone <z> Override the inferred zone (e.g. for multi-label\n public suffixes like .co.uk)\n --force Replace an existing event webhook URL if one is set\n --interactive Force interactive mode even when args are supplied\n --no-interactive Disable interactive mode even when args are missing\n (the script exits with usage instead of prompting —\n intended for CI / scripts)\n\nEnv vars:\n SENDGRID_API_KEY Required. See https://app.sendgrid.com/settings/api_keys\n Permissions: Mail Settings + Sender Authentication.\n CLOUDFLARE_API_TOKEN Required when --cloudflare is set. See\n https://dash.cloudflare.com/profile/api-tokens\n Permissions: Zone:Read + DNS:Edit on the zone(s)\n being published into.\n\nExamples:\n # one domain\n mailery setup-sendgrid \\\\\n --domain news.example.com \\\\\n --webhook-url https://example.com/m/webhooks/sendgrid \\\\\n --cloudflare\n\n # marketing + transactional in one shot\n mailery setup-sendgrid \\\\\n --domain news.example.com \\\\\n --domain mail.example.com \\\\\n --webhook-url https://example.com/m/webhooks/sendgrid \\\\\n --cloudflare\n`)\n}\n\nmain()\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli/setup-sendgrid.ts","../src/cli/interactive.ts","../src/cli/index.ts"],"names":["readline","input","output"],"mappings":"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","file":"cli.cjs","sourcesContent":["/**\n * `mailery setup-sendgrid` — one-shot wiring of SendGrid for a domain.\n *\n * Idempotent. Re-running converges: existing domain auths are reused, existing\n * webhook config is preserved unless `--force` is passed, existing Cloudflare\n * DNS records with the correct values are left alone.\n *\n * npx mailery setup-sendgrid \\\n * --domain news.example.com \\\n * --webhook-url https://example.com/m/webhooks/sendgrid \\\n * --cloudflare\n *\n * Required env:\n * SENDGRID_API_KEY — full-access (or at minimum Mail Settings + Sender Auth)\n * CLOUDFLARE_API_TOKEN — only if --cloudflare; needs Zone:Read + DNS:Edit on the zone\n */\n\nexport interface SetupSendgridOpts {\n /** One or more domains to authenticate, e.g. [\"news.example.com\", \"mail.example.com\"]. */\n domains: string[]\n /** Sub-label SendGrid uses for the link branding CNAME (default 'em'). */\n subdomain?: string\n /** Where SendGrid should POST event webhooks. Account-level — only configured once regardless of domain count. */\n webhookUrl: string\n /** Publish DNS records via Cloudflare API. Requires CLOUDFLARE_API_TOKEN. */\n cloudflare?: boolean\n /** Override the parent zone if any domain isn't a subdomain of its eTLD+1. */\n cloudflareZone?: string\n /** Replace an existing webhook URL if one is already set. */\n force?: boolean\n /** Logger; defaults to console. Pass {} to silence. */\n logger?: { log?: (...a: unknown[]) => void; warn?: (...a: unknown[]) => void }\n /** Override fetch (for tests). */\n fetchFn?: typeof fetch\n /** Override env (for tests). */\n env?: Record<string, string | undefined>\n}\n\nexport interface DomainSetupResult {\n domain: string\n domainAuthId: number\n alreadyValidated: boolean\n dnsRecords: Array<{ type: string; host: string; data: string }>\n cloudflarePushed: number\n}\n\nexport interface SetupSendgridResult {\n domains: DomainSetupResult[]\n webhookKey: string\n webhookUrl: string\n envSnippet: string\n}\n\ninterface SgDomainAuth {\n id: number\n domain: string\n subdomain: string\n username: string\n valid: boolean\n dns: Record<string, { host: string; type: string; data: string; valid: boolean }>\n}\n\nexport async function setupSendgrid(opts: SetupSendgridOpts): Promise<SetupSendgridResult> {\n const env = opts.env ?? process.env\n const log = opts.logger ?? console\n const info = (m: string) => log.log?.(m)\n const warn = (m: string) => log.warn?.(m)\n const f = opts.fetchFn ?? globalThis.fetch\n\n const sgKey = env.SENDGRID_API_KEY\n if (!sgKey) {\n throw new Error(\n 'SENDGRID_API_KEY env var is required.\\n' +\n ' Create one at: https://app.sendgrid.com/settings/api_keys\\n' +\n ' Needs at least: \"Mail Settings\" (Full Access) and \"Sender Authentication\" (Full Access).\\n' +\n ' Then add to your shell rc (e.g. ~/.zshrc): export SENDGRID_API_KEY=\"SG.xxx\"',\n )\n }\n if (!/^SG\\./.test(sgKey)) {\n throw new Error(\n 'SENDGRID_API_KEY does not look like a SendGrid API key (expected to start with \"SG.\"). ' +\n 'Double-check you copied the full key from https://app.sendgrid.com/settings/api_keys.',\n )\n }\n\n if (opts.cloudflare && !env.CLOUDFLARE_API_TOKEN) {\n throw new Error(\n 'CLOUDFLARE_API_TOKEN env var is required when --cloudflare is set.\\n' +\n ' Create one at: https://dash.cloudflare.com/profile/api-tokens\\n' +\n ' Use the \"Edit zone DNS\" template, restricted to the specific zone you\\'re publishing into.\\n' +\n ' Permissions: Zone:Read + DNS:Edit.\\n' +\n ' Then add to your shell rc: export CLOUDFLARE_API_TOKEN=\"cf-xxx\"',\n )\n }\n\n if (!opts.domains || opts.domains.length === 0) {\n throw new Error('At least one --domain is required')\n }\n\n const sg = sendgridClient(sgKey, f)\n const cf = opts.cloudflare ? cloudflareClient(env.CLOUDFLARE_API_TOKEN!, f) : null\n\n // ---- Per-domain: authentication + DNS publish ---------------------------\n const domainResults: DomainSetupResult[] = []\n for (const domain of opts.domains) {\n info('')\n info(`=== ${domain} ===`)\n info(`SendGrid → checking domain authentication for ${domain}…`)\n const existing = await sg.findDomainAuth(domain)\n let auth: SgDomainAuth\n if (existing) {\n info(` → reusing existing domain auth #${existing.id}`)\n auth = existing\n } else {\n info(` → creating new domain auth`)\n auth = await sg.createDomainAuth({\n domain,\n subdomain: opts.subdomain ?? 'em',\n })\n }\n\n const dnsRecords = Object.values(auth.dns).map((r) => ({ type: r.type, host: r.host, data: r.data }))\n let cloudflarePushed = 0\n\n if (cf) {\n info(`Cloudflare → publishing ${dnsRecords.length} record(s)…`)\n const zoneName = opts.cloudflareZone ?? inferZone(domain)\n const zoneId = await cf.findZoneId(zoneName)\n if (!zoneId) {\n throw new Error(`Cloudflare zone \"${zoneName}\" not found on this account`)\n }\n for (const rec of dnsRecords) {\n const result = await cf.upsertRecord(zoneId, rec)\n if (result === 'created') {\n cloudflarePushed++\n info(` + ${rec.type} ${rec.host} → ${rec.data}`)\n } else if (result === 'updated') {\n cloudflarePushed++\n info(` ~ ${rec.type} ${rec.host} (updated to ${rec.data})`)\n } else {\n info(` = ${rec.type} ${rec.host} (already correct)`)\n }\n }\n\n info('SendGrid → triggering validation…')\n const validated = await sg.validateDomainAuth(auth.id)\n if (validated) {\n info(' ✓ domain authenticated')\n } else {\n warn(' ! validation not yet successful — DNS may still be propagating. Re-run in a few minutes.')\n }\n auth.valid = validated\n } else {\n info('— skipping Cloudflare publish (no --cloudflare). Add these records to your DNS provider:')\n for (const rec of dnsRecords) {\n info(` ${rec.type.padEnd(7)} ${rec.host.padEnd(50)} ${rec.data}`)\n }\n }\n\n domainResults.push({\n domain,\n domainAuthId: auth.id,\n alreadyValidated: auth.valid,\n dnsRecords,\n cloudflarePushed,\n })\n }\n\n info('')\n info('=== Event webhook (account-level) ===')\n\n // ---- Account-level: Enable Signed Event Webhook + fetch public key -----\n info('SendGrid → checking Signed Event Webhook…')\n // SendGrid's GET /user/webhooks/event/settings/signed only returns\n // `{ public_key }`. A non-empty key implies signing is already enabled —\n // re-PATCHing `enabled: true` rotates the keypair, silently breaking\n // signature verification everywhere the old key was deployed.\n const signed = await sg.getSignedWebhookSettings()\n let webhookKey: string\n if (signed.public_key) {\n info(' = signing already enabled; reusing existing public key')\n webhookKey = signed.public_key\n } else {\n info(' → enabling signing')\n webhookKey = await sg.enableSignedWebhook()\n info(' ✓ verification key fetched')\n }\n\n // ---- Step 4: Configure Event Webhook URL + event toggles ----------------\n info('SendGrid → checking Event Webhook…')\n const currentEvents = await sg.getEventWebhookSettings()\n const desiredEvents = {\n url: opts.webhookUrl,\n enabled: true,\n delivered: true,\n open: true,\n click: true,\n bounce: true,\n dropped: true,\n spam_report: true,\n unsubscribe: true,\n deferred: false,\n processed: false,\n group_resubscribe: false,\n group_unsubscribe: false,\n }\n if (currentEvents.url && currentEvents.url !== opts.webhookUrl && !opts.force) {\n throw new Error(\n `Event webhook is already configured for ${currentEvents.url}. ` +\n `Pass --force to overwrite (this will redirect all future events to ${opts.webhookUrl}).`,\n )\n }\n if (eventSettingsMatch(currentEvents, desiredEvents)) {\n info(` = webhook already configured for ${opts.webhookUrl}; nothing to change`)\n } else {\n await sg.setEventWebhookSettings(desiredEvents)\n info(` ✓ webhook URL set to ${opts.webhookUrl}`)\n }\n\n const envSnippet = [\n `# Add to your environment (or .env file):`,\n `SENDGRID_WEBHOOK_VERIFICATION_KEY=\"${webhookKey.replace(/\\n/g, '\\\\n')}\"`,\n ].join('\\n')\n\n info('')\n info('--- Done. Add to your environment ---')\n info(envSnippet)\n\n return {\n domains: domainResults,\n webhookKey,\n webhookUrl: opts.webhookUrl,\n envSnippet,\n }\n}\n\n// ---------------------------------------------------------------------------\n// SendGrid API client\n// ---------------------------------------------------------------------------\n\ninterface SendGridClient {\n findDomainAuth(domain: string): Promise<SgDomainAuth | null>\n createDomainAuth(input: { domain: string; subdomain: string }): Promise<SgDomainAuth>\n validateDomainAuth(id: number): Promise<boolean>\n getSignedWebhookSettings(): Promise<{ enabled?: boolean; public_key?: string }>\n enableSignedWebhook(): Promise<string>\n getEventWebhookSettings(): Promise<{ url?: string; enabled?: boolean; [k: string]: unknown }>\n setEventWebhookSettings(payload: Record<string, unknown>): Promise<void>\n}\n\nfunction sendgridClient(apiKey: string, fetchFn: typeof fetch): SendGridClient {\n const base = 'https://api.sendgrid.com/v3'\n const headers = { authorization: `Bearer ${apiKey}`, 'content-type': 'application/json' }\n\n async function call(path: string, init: RequestInit = {}): Promise<any> {\n const res = await fetchFn(`${base}${path}`, { ...init, headers: { ...headers, ...(init.headers as any) } })\n const text = await res.text()\n const body = text ? safeJson(text) : null\n if (res.status === 401 || res.status === 403) {\n throw new Error(\n `SendGrid rejected the request (HTTP ${res.status}). Your SENDGRID_API_KEY is missing the required permissions ` +\n 'or is invalid. The key needs \"Mail Settings\" (Full Access) and \"Sender Authentication\" (Full Access). ' +\n 'Manage keys at https://app.sendgrid.com/settings/api_keys.',\n )\n }\n if (!res.ok) {\n const message = body?.errors?.[0]?.message ?? text ?? res.statusText\n throw new Error(`SendGrid ${init.method ?? 'GET'} ${path} → ${res.status}: ${message}`)\n }\n return body\n }\n\n return {\n async findDomainAuth(domain: string) {\n const list: SgDomainAuth[] = (await call(`/whitelabel/domains?limit=200`)) ?? []\n return list.find((d) => d.domain === domain) ?? null\n },\n async createDomainAuth({ domain, subdomain }) {\n return call(`/whitelabel/domains`, {\n method: 'POST',\n body: JSON.stringify({ domain, subdomain, automatic_security: true }),\n }) as Promise<SgDomainAuth>\n },\n async validateDomainAuth(id: number) {\n const r = await call(`/whitelabel/domains/${id}/validate`, { method: 'POST' })\n return Boolean(r?.valid)\n },\n async getSignedWebhookSettings() {\n return call(`/user/webhooks/event/settings/signed`)\n },\n async enableSignedWebhook() {\n await call(`/user/webhooks/event/settings/signed`, {\n method: 'PATCH',\n body: JSON.stringify({ enabled: true }),\n })\n const r = await call(`/user/webhooks/event/settings/signed`)\n if (!r?.public_key) throw new Error('SendGrid did not return a public_key for the signed event webhook')\n return r.public_key as string\n },\n async getEventWebhookSettings() {\n return call(`/user/webhooks/event/settings`)\n },\n async setEventWebhookSettings(payload) {\n await call(`/user/webhooks/event/settings`, {\n method: 'PATCH',\n body: JSON.stringify(payload),\n })\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Cloudflare API client\n// ---------------------------------------------------------------------------\n\ninterface CloudflareClient {\n findZoneId(name: string): Promise<string | null>\n upsertRecord(zoneId: string, rec: { type: string; host: string; data: string }): Promise<'created' | 'updated' | 'noop'>\n}\n\nfunction cloudflareClient(apiToken: string, fetchFn: typeof fetch): CloudflareClient {\n const base = 'https://api.cloudflare.com/client/v4'\n const headers = { authorization: `Bearer ${apiToken}`, 'content-type': 'application/json' }\n\n async function call(path: string, init: RequestInit = {}): Promise<any> {\n const res = await fetchFn(`${base}${path}`, { ...init, headers: { ...headers, ...(init.headers as any) } })\n const body: any = await res.json().catch(() => null)\n if (res.status === 401 || res.status === 403) {\n throw new Error(\n `Cloudflare rejected the request (HTTP ${res.status}). Your CLOUDFLARE_API_TOKEN is invalid, expired, ` +\n 'or missing the required permissions. The token needs Zone:Read + DNS:Edit on the specific zone you\\'re ' +\n 'publishing into. Manage tokens at https://dash.cloudflare.com/profile/api-tokens.',\n )\n }\n if (!res.ok || (body && body.success === false)) {\n const message = body?.errors?.[0]?.message ?? res.statusText\n throw new Error(`Cloudflare ${init.method ?? 'GET'} ${path} → ${res.status}: ${message}`)\n }\n return body\n }\n\n return {\n async findZoneId(name: string) {\n const r = await call(`/zones?name=${encodeURIComponent(name)}`)\n return r?.result?.[0]?.id ?? null\n },\n async upsertRecord(zoneId, rec) {\n // CF requires the host to be a FQDN, which SendGrid already gives us.\n const existing = await call(\n `/zones/${zoneId}/dns_records?type=${encodeURIComponent(rec.type)}&name=${encodeURIComponent(rec.host)}`,\n )\n const match = existing?.result?.[0]\n const desiredBody = {\n type: rec.type,\n name: rec.host,\n content: rec.data,\n ttl: 1, // auto\n proxied: false, // critical: SendGrid DKIM CNAMEs must NOT be proxied\n }\n if (!match) {\n await call(`/zones/${zoneId}/dns_records`, { method: 'POST', body: JSON.stringify(desiredBody) })\n return 'created'\n }\n if (match.content === rec.data && match.proxied === false) {\n return 'noop'\n }\n await call(`/zones/${zoneId}/dns_records/${match.id}`, {\n method: 'PUT',\n body: JSON.stringify(desiredBody),\n })\n return 'updated'\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Best-effort eTLD+1 inference. Falls back to last two labels — fine for most\n * `.com` / `.io` style domains. For multi-label public suffixes (`.co.uk`)\n * pass `--cloudflare-zone` explicitly. */\nfunction inferZone(domain: string): string {\n const labels = domain.split('.').filter(Boolean)\n if (labels.length <= 2) return domain\n return labels.slice(-2).join('.')\n}\n\nfunction safeJson(s: string): any {\n try { return JSON.parse(s) } catch { return null }\n}\n\n/** Compare current SendGrid event webhook settings to the desired payload.\n * Treats missing keys on `current` as \"not yet set\" rather than equal-to-false,\n * so a partially-configured webhook still triggers a PATCH. */\nfunction eventSettingsMatch(current: Record<string, unknown>, desired: Record<string, unknown>): boolean {\n for (const [k, v] of Object.entries(desired)) {\n if (current[k] !== v) return false\n }\n return true\n}\n","/**\n * Interactive wizard for `mailery setup-sendgrid`. Walks the user through:\n * - Required env vars (offers to paste them in, then prints export lines)\n * - Domains (accept multiple via repeated entry or comma-separation)\n * - Webhook URL (with a default derived from the first domain's apex)\n * - Cloudflare toggle + zone override\n * - Force flag\n *\n * Renders a summary before any API call and asks for confirmation. All\n * questions are funneled through a `Prompter` so tests can inject canned\n * answers without touching stdin.\n */\n\nimport * as readline from 'node:readline/promises'\nimport { stdin as input, stdout as output } from 'node:process'\n\nimport type { SetupSendgridOpts } from './setup-sendgrid.js'\n\nexport type Prompter = (question: string, opts?: { defaultValue?: string }) => Promise<string>\n\nexport interface InteractiveResult {\n opts: Omit<SetupSendgridOpts, 'fetchFn' | 'logger' | 'env'>\n envOverrides: Record<string, string>\n exportLines: string[]\n proceed: boolean\n}\n\nexport function makeReadlinePrompter(): { prompter: Prompter; close: () => void } {\n const rl = readline.createInterface({ input, output })\n const prompter: Prompter = async (q, opts) => {\n const suffix = opts?.defaultValue ? ` [${opts.defaultValue}]` : ''\n const raw = await rl.question(`${q}${suffix}: `)\n const trimmed = raw.trim()\n return trimmed.length === 0 && opts?.defaultValue ? opts.defaultValue : trimmed\n }\n return { prompter, close: () => rl.close() }\n}\n\nexport async function runInteractive(\n prompter: Prompter,\n env: Record<string, string | undefined>,\n logger: { log: (...a: unknown[]) => void } = console,\n): Promise<InteractiveResult> {\n const log = (m: string) => logger.log(m)\n const exportLines: string[] = []\n const envOverrides: Record<string, string> = {}\n\n log('')\n log('mailery setup-sendgrid (interactive)')\n log('────────────────────────────────────')\n log('')\n\n // ---- SendGrid API key ---------------------------------------------------\n if (!env.SENDGRID_API_KEY) {\n log('SENDGRID_API_KEY is not set in your environment.')\n log(' Create one at https://app.sendgrid.com/settings/api_keys')\n log(' Permissions: Mail Settings (Full Access) + Sender Authentication (Full Access).')\n log('')\n const key = await prompter('Paste your SendGrid API key (starts with SG.)')\n if (!key) throw new Error('SENDGRID_API_KEY is required')\n envOverrides.SENDGRID_API_KEY = key\n exportLines.push(`export SENDGRID_API_KEY=\"${key}\"`)\n }\n\n // ---- Domains ------------------------------------------------------------\n log('')\n log('Which sender domain(s) do you want to authenticate?')\n log(' Examples:')\n log(' news.example.com (one)')\n log(' news.example.com, mail.example.com (two — comma-separated, in one line)')\n log(' Splitting marketing from transactional onto separate domains is recommended.')\n log('')\n const domainsRaw = await prompter('Domains')\n const domains = domainsRaw\n .split(',')\n .map((d) => d.trim())\n .filter(Boolean)\n if (domains.length === 0) throw new Error('at least one --domain is required')\n\n // ---- Webhook URL --------------------------------------------------------\n const defaultWebhook = `https://${apex(domains[0]!)}/m/webhooks/sendgrid`\n log('')\n log('SendGrid will POST event webhooks to this URL.')\n log(' This must be reachable from the public internet (not behind admin auth).')\n log('')\n const webhookUrl = await prompter('Webhook URL', { defaultValue: defaultWebhook })\n\n // ---- Cloudflare ---------------------------------------------------------\n log('')\n const cloudflareRaw = await prompter('Publish DNS records via Cloudflare API? (y/N)', { defaultValue: 'n' })\n const cloudflare = /^y/i.test(cloudflareRaw)\n let cloudflareZone: string | undefined\n\n if (cloudflare) {\n if (!env.CLOUDFLARE_API_TOKEN) {\n log('')\n log('CLOUDFLARE_API_TOKEN is not set in your environment.')\n log(' Create one at https://dash.cloudflare.com/profile/api-tokens')\n log(' Use the \"Edit zone DNS\" template, restricted to your zone(s).')\n log(' Permissions: Zone:Read + DNS:Edit.')\n log('')\n const token = await prompter('Paste your Cloudflare API token')\n if (!token) throw new Error('CLOUDFLARE_API_TOKEN is required when publishing via Cloudflare')\n envOverrides.CLOUDFLARE_API_TOKEN = token\n exportLines.push(`export CLOUDFLARE_API_TOKEN=\"${token}\"`)\n }\n log('')\n const zoneOverride = await prompter('Override Cloudflare zone (blank to infer per-domain)', { defaultValue: '' })\n if (zoneOverride.length > 0) cloudflareZone = zoneOverride\n }\n\n // ---- Force --------------------------------------------------------------\n log('')\n const forceRaw = await prompter(\n 'If a different webhook URL is already configured on this SendGrid account, overwrite it? (y/N)',\n { defaultValue: 'n' },\n )\n const force = /^y/i.test(forceRaw)\n\n // ---- Summary + confirm --------------------------------------------------\n log('')\n log('About to run setup with:')\n log(` domains: ${domains.join(', ')}`)\n log(` webhook URL: ${webhookUrl}`)\n log(` cloudflare: ${cloudflare ? 'yes' : 'no'}`)\n if (cloudflareZone) log(` cloudflare zone: ${cloudflareZone}`)\n log(` force: ${force ? 'yes' : 'no'}`)\n log('')\n const confirmRaw = await prompter('Proceed? (Y/n)', { defaultValue: 'y' })\n const proceed = !/^n/i.test(confirmRaw)\n\n return {\n opts: { domains, webhookUrl, cloudflare, cloudflareZone, force },\n envOverrides,\n exportLines,\n proceed,\n }\n}\n\n/** Strip a leading sub-label so we get the apex (best-effort for webhook URL default). */\nfunction apex(domain: string): string {\n const labels = domain.split('.').filter(Boolean)\n if (labels.length <= 2) return domain\n return labels.slice(-2).join('.')\n}\n","#!/usr/bin/env node\n/**\n * mailery CLI entry. Currently exposes a single command:\n *\n * npx mailery setup-sendgrid --domain X --webhook-url Y [--cloudflare]\n *\n * See src/cli/setup-sendgrid.ts for the full option list.\n */\n\nimport { setupSendgrid } from './setup-sendgrid.js'\nimport { makeReadlinePrompter, runInteractive } from './interactive.js'\n\nasync function main() {\n const argv = process.argv.slice(2)\n const cmd = argv[0]\n\n if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') {\n printUsage()\n process.exit(cmd ? 0 : 1)\n }\n\n switch (cmd) {\n case 'setup-sendgrid': {\n const opts = parseArgs(argv.slice(1))\n const argDomains = collectStringList(opts.domain)\n const argWebhook = typeof opts['webhook-url'] === 'string' ? (opts['webhook-url'] as string) : null\n const explicitInteractive = Boolean(opts.interactive)\n const explicitNonInteractive = Boolean(opts['no-interactive'])\n\n // Decide interactive vs args. Interactive when: --interactive, OR no\n // required args provided AND we have a TTY. Force non-interactive with\n // --no-interactive (useful for CI).\n const wantsInteractive =\n explicitInteractive ||\n (!explicitNonInteractive && argDomains.length === 0 && !argWebhook && Boolean(process.stdin.isTTY))\n\n try {\n if (wantsInteractive) {\n const { prompter, close } = makeReadlinePrompter()\n let interactive\n try {\n interactive = await runInteractive(prompter, process.env)\n } finally {\n close()\n }\n if (!interactive.proceed) {\n console.log('Aborted.')\n process.exit(0)\n }\n await setupSendgrid({\n ...interactive.opts,\n env: { ...process.env, ...interactive.envOverrides },\n })\n if (interactive.exportLines.length > 0) {\n console.log('')\n console.log('Add these to your shell rc (e.g. ~/.zshrc) so future runs find them:')\n for (const line of interactive.exportLines) console.log(` ${line}`)\n }\n } else {\n if (argDomains.length === 0) {\n console.error('error: --domain is required (repeat or comma-separate for multiple, or run without args for interactive mode)')\n printUsage()\n process.exit(2)\n }\n if (!argWebhook) {\n console.error('error: --webhook-url is required')\n printUsage()\n process.exit(2)\n }\n await setupSendgrid({\n domains: argDomains,\n subdomain: opts.subdomain ? String(opts.subdomain) : undefined,\n webhookUrl: argWebhook,\n cloudflare: Boolean(opts.cloudflare),\n cloudflareZone: opts['cloudflare-zone'] ? String(opts['cloudflare-zone']) : undefined,\n force: Boolean(opts.force),\n })\n }\n } catch (err: any) {\n console.error(`\\n✗ ${err?.message ?? err}`)\n process.exit(1)\n }\n break\n }\n default:\n console.error(`unknown command: ${cmd}`)\n printUsage()\n process.exit(2)\n }\n}\n\nfunction parseArgs(argv: string[]): Record<string, string | string[] | boolean> {\n const out: Record<string, string | string[] | boolean> = {}\n function setKey(key: string, value: string | boolean) {\n const existing = out[key]\n if (existing === undefined) {\n out[key] = value\n } else if (Array.isArray(existing)) {\n if (typeof value === 'string') existing.push(value)\n } else if (typeof existing === 'string' && typeof value === 'string') {\n out[key] = [existing, value]\n } else {\n out[key] = value\n }\n }\n for (let i = 0; i < argv.length; i++) {\n const tok = argv[i]!\n if (!tok.startsWith('--')) continue\n const eq = tok.indexOf('=')\n if (eq > 0) {\n setKey(tok.slice(2, eq), tok.slice(eq + 1))\n continue\n }\n const key = tok.slice(2)\n const next = argv[i + 1]\n if (next && !next.startsWith('--')) {\n setKey(key, next)\n i++\n } else {\n setKey(key, true)\n }\n }\n return out\n}\n\n/** Collapse a repeated/comma-separated flag value into a clean string[]. */\nfunction collectStringList(value: string | string[] | boolean | undefined): string[] {\n if (value === undefined || value === true || value === false) return []\n const raw = Array.isArray(value) ? value : [value]\n return raw\n .flatMap((s) => String(s).split(','))\n .map((s) => s.trim())\n .filter(Boolean)\n}\n\nfunction printUsage() {\n console.log(`\nmailery CLI\n\nUsage:\n mailery setup-sendgrid (interactive — prompts you for everything)\n mailery setup-sendgrid --domain <d>[,<d>...] --webhook-url <u> [options] (non-interactive)\n\nOptions:\n --domain <d> Domain to authenticate. Repeat (or comma-separate)\n to authenticate multiple in one run, e.g. when\n isolating marketing from transactional. The event\n webhook itself is account-level and only gets set up\n once.\n --subdomain <s> Sub-label for the link branding CNAME (default: em)\n --webhook-url <u> Public URL for the SendGrid event webhook\n --cloudflare Publish DNS records via the Cloudflare API\n --cloudflare-zone <z> Override the inferred zone (e.g. for multi-label\n public suffixes like .co.uk)\n --force Replace an existing event webhook URL if one is set\n --interactive Force interactive mode even when args are supplied\n --no-interactive Disable interactive mode even when args are missing\n (the script exits with usage instead of prompting —\n intended for CI / scripts)\n\nEnv vars:\n SENDGRID_API_KEY Required. See https://app.sendgrid.com/settings/api_keys\n Permissions: Mail Settings + Sender Authentication.\n CLOUDFLARE_API_TOKEN Required when --cloudflare is set. See\n https://dash.cloudflare.com/profile/api-tokens\n Permissions: Zone:Read + DNS:Edit on the zone(s)\n being published into.\n\nExamples:\n # one domain\n mailery setup-sendgrid \\\\\n --domain news.example.com \\\\\n --webhook-url https://example.com/m/webhooks/sendgrid \\\\\n --cloudflare\n\n # marketing + transactional in one shot\n mailery setup-sendgrid \\\\\n --domain news.example.com \\\\\n --domain mail.example.com \\\\\n --webhook-url https://example.com/m/webhooks/sendgrid \\\\\n --cloudflare\n`)\n}\n\nmain()\n"]}
|
package/dist/cli.js
CHANGED
|
@@ -99,7 +99,7 @@ async function setupSendgrid(opts) {
|
|
|
99
99
|
info("SendGrid \u2192 checking Signed Event Webhook\u2026");
|
|
100
100
|
const signed = await sg.getSignedWebhookSettings();
|
|
101
101
|
let webhookKey;
|
|
102
|
-
if (signed.
|
|
102
|
+
if (signed.public_key) {
|
|
103
103
|
info(" = signing already enabled; reusing existing public key");
|
|
104
104
|
webhookKey = signed.public_key;
|
|
105
105
|
} else {
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/setup-sendgrid.ts","../src/cli/interactive.ts","../src/cli/index.ts"],"names":["input","output"],"mappings":"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","file":"cli.js","sourcesContent":["/**\n * `mailery setup-sendgrid` — one-shot wiring of SendGrid for a domain.\n *\n * Idempotent. Re-running converges: existing domain auths are reused, existing\n * webhook config is preserved unless `--force` is passed, existing Cloudflare\n * DNS records with the correct values are left alone.\n *\n * npx mailery setup-sendgrid \\\n * --domain news.example.com \\\n * --webhook-url https://example.com/m/webhooks/sendgrid \\\n * --cloudflare\n *\n * Required env:\n * SENDGRID_API_KEY — full-access (or at minimum Mail Settings + Sender Auth)\n * CLOUDFLARE_API_TOKEN — only if --cloudflare; needs Zone:Read + DNS:Edit on the zone\n */\n\nexport interface SetupSendgridOpts {\n /** One or more domains to authenticate, e.g. [\"news.example.com\", \"mail.example.com\"]. */\n domains: string[]\n /** Sub-label SendGrid uses for the link branding CNAME (default 'em'). */\n subdomain?: string\n /** Where SendGrid should POST event webhooks. Account-level — only configured once regardless of domain count. */\n webhookUrl: string\n /** Publish DNS records via Cloudflare API. Requires CLOUDFLARE_API_TOKEN. */\n cloudflare?: boolean\n /** Override the parent zone if any domain isn't a subdomain of its eTLD+1. */\n cloudflareZone?: string\n /** Replace an existing webhook URL if one is already set. */\n force?: boolean\n /** Logger; defaults to console. Pass {} to silence. */\n logger?: { log?: (...a: unknown[]) => void; warn?: (...a: unknown[]) => void }\n /** Override fetch (for tests). */\n fetchFn?: typeof fetch\n /** Override env (for tests). */\n env?: Record<string, string | undefined>\n}\n\nexport interface DomainSetupResult {\n domain: string\n domainAuthId: number\n alreadyValidated: boolean\n dnsRecords: Array<{ type: string; host: string; data: string }>\n cloudflarePushed: number\n}\n\nexport interface SetupSendgridResult {\n domains: DomainSetupResult[]\n webhookKey: string\n webhookUrl: string\n envSnippet: string\n}\n\ninterface SgDomainAuth {\n id: number\n domain: string\n subdomain: string\n username: string\n valid: boolean\n dns: Record<string, { host: string; type: string; data: string; valid: boolean }>\n}\n\nexport async function setupSendgrid(opts: SetupSendgridOpts): Promise<SetupSendgridResult> {\n const env = opts.env ?? process.env\n const log = opts.logger ?? console\n const info = (m: string) => log.log?.(m)\n const warn = (m: string) => log.warn?.(m)\n const f = opts.fetchFn ?? globalThis.fetch\n\n const sgKey = env.SENDGRID_API_KEY\n if (!sgKey) {\n throw new Error(\n 'SENDGRID_API_KEY env var is required.\\n' +\n ' Create one at: https://app.sendgrid.com/settings/api_keys\\n' +\n ' Needs at least: \"Mail Settings\" (Full Access) and \"Sender Authentication\" (Full Access).\\n' +\n ' Then add to your shell rc (e.g. ~/.zshrc): export SENDGRID_API_KEY=\"SG.xxx\"',\n )\n }\n if (!/^SG\\./.test(sgKey)) {\n throw new Error(\n 'SENDGRID_API_KEY does not look like a SendGrid API key (expected to start with \"SG.\"). ' +\n 'Double-check you copied the full key from https://app.sendgrid.com/settings/api_keys.',\n )\n }\n\n if (opts.cloudflare && !env.CLOUDFLARE_API_TOKEN) {\n throw new Error(\n 'CLOUDFLARE_API_TOKEN env var is required when --cloudflare is set.\\n' +\n ' Create one at: https://dash.cloudflare.com/profile/api-tokens\\n' +\n ' Use the \"Edit zone DNS\" template, restricted to the specific zone you\\'re publishing into.\\n' +\n ' Permissions: Zone:Read + DNS:Edit.\\n' +\n ' Then add to your shell rc: export CLOUDFLARE_API_TOKEN=\"cf-xxx\"',\n )\n }\n\n if (!opts.domains || opts.domains.length === 0) {\n throw new Error('At least one --domain is required')\n }\n\n const sg = sendgridClient(sgKey, f)\n const cf = opts.cloudflare ? cloudflareClient(env.CLOUDFLARE_API_TOKEN!, f) : null\n\n // ---- Per-domain: authentication + DNS publish ---------------------------\n const domainResults: DomainSetupResult[] = []\n for (const domain of opts.domains) {\n info('')\n info(`=== ${domain} ===`)\n info(`SendGrid → checking domain authentication for ${domain}…`)\n const existing = await sg.findDomainAuth(domain)\n let auth: SgDomainAuth\n if (existing) {\n info(` → reusing existing domain auth #${existing.id}`)\n auth = existing\n } else {\n info(` → creating new domain auth`)\n auth = await sg.createDomainAuth({\n domain,\n subdomain: opts.subdomain ?? 'em',\n })\n }\n\n const dnsRecords = Object.values(auth.dns).map((r) => ({ type: r.type, host: r.host, data: r.data }))\n let cloudflarePushed = 0\n\n if (cf) {\n info(`Cloudflare → publishing ${dnsRecords.length} record(s)…`)\n const zoneName = opts.cloudflareZone ?? inferZone(domain)\n const zoneId = await cf.findZoneId(zoneName)\n if (!zoneId) {\n throw new Error(`Cloudflare zone \"${zoneName}\" not found on this account`)\n }\n for (const rec of dnsRecords) {\n const result = await cf.upsertRecord(zoneId, rec)\n if (result === 'created') {\n cloudflarePushed++\n info(` + ${rec.type} ${rec.host} → ${rec.data}`)\n } else if (result === 'updated') {\n cloudflarePushed++\n info(` ~ ${rec.type} ${rec.host} (updated to ${rec.data})`)\n } else {\n info(` = ${rec.type} ${rec.host} (already correct)`)\n }\n }\n\n info('SendGrid → triggering validation…')\n const validated = await sg.validateDomainAuth(auth.id)\n if (validated) {\n info(' ✓ domain authenticated')\n } else {\n warn(' ! validation not yet successful — DNS may still be propagating. Re-run in a few minutes.')\n }\n auth.valid = validated\n } else {\n info('— skipping Cloudflare publish (no --cloudflare). Add these records to your DNS provider:')\n for (const rec of dnsRecords) {\n info(` ${rec.type.padEnd(7)} ${rec.host.padEnd(50)} ${rec.data}`)\n }\n }\n\n domainResults.push({\n domain,\n domainAuthId: auth.id,\n alreadyValidated: auth.valid,\n dnsRecords,\n cloudflarePushed,\n })\n }\n\n info('')\n info('=== Event webhook (account-level) ===')\n\n // ---- Account-level: Enable Signed Event Webhook + fetch public key -----\n info('SendGrid → checking Signed Event Webhook…')\n const signed = await sg.getSignedWebhookSettings()\n let webhookKey: string\n if (signed.enabled && signed.public_key) {\n info(' = signing already enabled; reusing existing public key')\n webhookKey = signed.public_key\n } else {\n info(' → enabling signing')\n webhookKey = await sg.enableSignedWebhook()\n info(' ✓ verification key fetched')\n }\n\n // ---- Step 4: Configure Event Webhook URL + event toggles ----------------\n info('SendGrid → checking Event Webhook…')\n const currentEvents = await sg.getEventWebhookSettings()\n const desiredEvents = {\n url: opts.webhookUrl,\n enabled: true,\n delivered: true,\n open: true,\n click: true,\n bounce: true,\n dropped: true,\n spam_report: true,\n unsubscribe: true,\n deferred: false,\n processed: false,\n group_resubscribe: false,\n group_unsubscribe: false,\n }\n if (currentEvents.url && currentEvents.url !== opts.webhookUrl && !opts.force) {\n throw new Error(\n `Event webhook is already configured for ${currentEvents.url}. ` +\n `Pass --force to overwrite (this will redirect all future events to ${opts.webhookUrl}).`,\n )\n }\n if (eventSettingsMatch(currentEvents, desiredEvents)) {\n info(` = webhook already configured for ${opts.webhookUrl}; nothing to change`)\n } else {\n await sg.setEventWebhookSettings(desiredEvents)\n info(` ✓ webhook URL set to ${opts.webhookUrl}`)\n }\n\n const envSnippet = [\n `# Add to your environment (or .env file):`,\n `SENDGRID_WEBHOOK_VERIFICATION_KEY=\"${webhookKey.replace(/\\n/g, '\\\\n')}\"`,\n ].join('\\n')\n\n info('')\n info('--- Done. Add to your environment ---')\n info(envSnippet)\n\n return {\n domains: domainResults,\n webhookKey,\n webhookUrl: opts.webhookUrl,\n envSnippet,\n }\n}\n\n// ---------------------------------------------------------------------------\n// SendGrid API client\n// ---------------------------------------------------------------------------\n\ninterface SendGridClient {\n findDomainAuth(domain: string): Promise<SgDomainAuth | null>\n createDomainAuth(input: { domain: string; subdomain: string }): Promise<SgDomainAuth>\n validateDomainAuth(id: number): Promise<boolean>\n getSignedWebhookSettings(): Promise<{ enabled?: boolean; public_key?: string }>\n enableSignedWebhook(): Promise<string>\n getEventWebhookSettings(): Promise<{ url?: string; enabled?: boolean; [k: string]: unknown }>\n setEventWebhookSettings(payload: Record<string, unknown>): Promise<void>\n}\n\nfunction sendgridClient(apiKey: string, fetchFn: typeof fetch): SendGridClient {\n const base = 'https://api.sendgrid.com/v3'\n const headers = { authorization: `Bearer ${apiKey}`, 'content-type': 'application/json' }\n\n async function call(path: string, init: RequestInit = {}): Promise<any> {\n const res = await fetchFn(`${base}${path}`, { ...init, headers: { ...headers, ...(init.headers as any) } })\n const text = await res.text()\n const body = text ? safeJson(text) : null\n if (res.status === 401 || res.status === 403) {\n throw new Error(\n `SendGrid rejected the request (HTTP ${res.status}). Your SENDGRID_API_KEY is missing the required permissions ` +\n 'or is invalid. The key needs \"Mail Settings\" (Full Access) and \"Sender Authentication\" (Full Access). ' +\n 'Manage keys at https://app.sendgrid.com/settings/api_keys.',\n )\n }\n if (!res.ok) {\n const message = body?.errors?.[0]?.message ?? text ?? res.statusText\n throw new Error(`SendGrid ${init.method ?? 'GET'} ${path} → ${res.status}: ${message}`)\n }\n return body\n }\n\n return {\n async findDomainAuth(domain: string) {\n const list: SgDomainAuth[] = (await call(`/whitelabel/domains?limit=200`)) ?? []\n return list.find((d) => d.domain === domain) ?? null\n },\n async createDomainAuth({ domain, subdomain }) {\n return call(`/whitelabel/domains`, {\n method: 'POST',\n body: JSON.stringify({ domain, subdomain, automatic_security: true }),\n }) as Promise<SgDomainAuth>\n },\n async validateDomainAuth(id: number) {\n const r = await call(`/whitelabel/domains/${id}/validate`, { method: 'POST' })\n return Boolean(r?.valid)\n },\n async getSignedWebhookSettings() {\n return call(`/user/webhooks/event/settings/signed`)\n },\n async enableSignedWebhook() {\n await call(`/user/webhooks/event/settings/signed`, {\n method: 'PATCH',\n body: JSON.stringify({ enabled: true }),\n })\n const r = await call(`/user/webhooks/event/settings/signed`)\n if (!r?.public_key) throw new Error('SendGrid did not return a public_key for the signed event webhook')\n return r.public_key as string\n },\n async getEventWebhookSettings() {\n return call(`/user/webhooks/event/settings`)\n },\n async setEventWebhookSettings(payload) {\n await call(`/user/webhooks/event/settings`, {\n method: 'PATCH',\n body: JSON.stringify(payload),\n })\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Cloudflare API client\n// ---------------------------------------------------------------------------\n\ninterface CloudflareClient {\n findZoneId(name: string): Promise<string | null>\n upsertRecord(zoneId: string, rec: { type: string; host: string; data: string }): Promise<'created' | 'updated' | 'noop'>\n}\n\nfunction cloudflareClient(apiToken: string, fetchFn: typeof fetch): CloudflareClient {\n const base = 'https://api.cloudflare.com/client/v4'\n const headers = { authorization: `Bearer ${apiToken}`, 'content-type': 'application/json' }\n\n async function call(path: string, init: RequestInit = {}): Promise<any> {\n const res = await fetchFn(`${base}${path}`, { ...init, headers: { ...headers, ...(init.headers as any) } })\n const body: any = await res.json().catch(() => null)\n if (res.status === 401 || res.status === 403) {\n throw new Error(\n `Cloudflare rejected the request (HTTP ${res.status}). Your CLOUDFLARE_API_TOKEN is invalid, expired, ` +\n 'or missing the required permissions. The token needs Zone:Read + DNS:Edit on the specific zone you\\'re ' +\n 'publishing into. Manage tokens at https://dash.cloudflare.com/profile/api-tokens.',\n )\n }\n if (!res.ok || (body && body.success === false)) {\n const message = body?.errors?.[0]?.message ?? res.statusText\n throw new Error(`Cloudflare ${init.method ?? 'GET'} ${path} → ${res.status}: ${message}`)\n }\n return body\n }\n\n return {\n async findZoneId(name: string) {\n const r = await call(`/zones?name=${encodeURIComponent(name)}`)\n return r?.result?.[0]?.id ?? null\n },\n async upsertRecord(zoneId, rec) {\n // CF requires the host to be a FQDN, which SendGrid already gives us.\n const existing = await call(\n `/zones/${zoneId}/dns_records?type=${encodeURIComponent(rec.type)}&name=${encodeURIComponent(rec.host)}`,\n )\n const match = existing?.result?.[0]\n const desiredBody = {\n type: rec.type,\n name: rec.host,\n content: rec.data,\n ttl: 1, // auto\n proxied: false, // critical: SendGrid DKIM CNAMEs must NOT be proxied\n }\n if (!match) {\n await call(`/zones/${zoneId}/dns_records`, { method: 'POST', body: JSON.stringify(desiredBody) })\n return 'created'\n }\n if (match.content === rec.data && match.proxied === false) {\n return 'noop'\n }\n await call(`/zones/${zoneId}/dns_records/${match.id}`, {\n method: 'PUT',\n body: JSON.stringify(desiredBody),\n })\n return 'updated'\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Best-effort eTLD+1 inference. Falls back to last two labels — fine for most\n * `.com` / `.io` style domains. For multi-label public suffixes (`.co.uk`)\n * pass `--cloudflare-zone` explicitly. */\nfunction inferZone(domain: string): string {\n const labels = domain.split('.').filter(Boolean)\n if (labels.length <= 2) return domain\n return labels.slice(-2).join('.')\n}\n\nfunction safeJson(s: string): any {\n try { return JSON.parse(s) } catch { return null }\n}\n\n/** Compare current SendGrid event webhook settings to the desired payload.\n * Treats missing keys on `current` as \"not yet set\" rather than equal-to-false,\n * so a partially-configured webhook still triggers a PATCH. */\nfunction eventSettingsMatch(current: Record<string, unknown>, desired: Record<string, unknown>): boolean {\n for (const [k, v] of Object.entries(desired)) {\n if (current[k] !== v) return false\n }\n return true\n}\n","/**\n * Interactive wizard for `mailery setup-sendgrid`. Walks the user through:\n * - Required env vars (offers to paste them in, then prints export lines)\n * - Domains (accept multiple via repeated entry or comma-separation)\n * - Webhook URL (with a default derived from the first domain's apex)\n * - Cloudflare toggle + zone override\n * - Force flag\n *\n * Renders a summary before any API call and asks for confirmation. All\n * questions are funneled through a `Prompter` so tests can inject canned\n * answers without touching stdin.\n */\n\nimport * as readline from 'node:readline/promises'\nimport { stdin as input, stdout as output } from 'node:process'\n\nimport type { SetupSendgridOpts } from './setup-sendgrid.js'\n\nexport type Prompter = (question: string, opts?: { defaultValue?: string }) => Promise<string>\n\nexport interface InteractiveResult {\n opts: Omit<SetupSendgridOpts, 'fetchFn' | 'logger' | 'env'>\n envOverrides: Record<string, string>\n exportLines: string[]\n proceed: boolean\n}\n\nexport function makeReadlinePrompter(): { prompter: Prompter; close: () => void } {\n const rl = readline.createInterface({ input, output })\n const prompter: Prompter = async (q, opts) => {\n const suffix = opts?.defaultValue ? ` [${opts.defaultValue}]` : ''\n const raw = await rl.question(`${q}${suffix}: `)\n const trimmed = raw.trim()\n return trimmed.length === 0 && opts?.defaultValue ? opts.defaultValue : trimmed\n }\n return { prompter, close: () => rl.close() }\n}\n\nexport async function runInteractive(\n prompter: Prompter,\n env: Record<string, string | undefined>,\n logger: { log: (...a: unknown[]) => void } = console,\n): Promise<InteractiveResult> {\n const log = (m: string) => logger.log(m)\n const exportLines: string[] = []\n const envOverrides: Record<string, string> = {}\n\n log('')\n log('mailery setup-sendgrid (interactive)')\n log('────────────────────────────────────')\n log('')\n\n // ---- SendGrid API key ---------------------------------------------------\n if (!env.SENDGRID_API_KEY) {\n log('SENDGRID_API_KEY is not set in your environment.')\n log(' Create one at https://app.sendgrid.com/settings/api_keys')\n log(' Permissions: Mail Settings (Full Access) + Sender Authentication (Full Access).')\n log('')\n const key = await prompter('Paste your SendGrid API key (starts with SG.)')\n if (!key) throw new Error('SENDGRID_API_KEY is required')\n envOverrides.SENDGRID_API_KEY = key\n exportLines.push(`export SENDGRID_API_KEY=\"${key}\"`)\n }\n\n // ---- Domains ------------------------------------------------------------\n log('')\n log('Which sender domain(s) do you want to authenticate?')\n log(' Examples:')\n log(' news.example.com (one)')\n log(' news.example.com, mail.example.com (two — comma-separated, in one line)')\n log(' Splitting marketing from transactional onto separate domains is recommended.')\n log('')\n const domainsRaw = await prompter('Domains')\n const domains = domainsRaw\n .split(',')\n .map((d) => d.trim())\n .filter(Boolean)\n if (domains.length === 0) throw new Error('at least one --domain is required')\n\n // ---- Webhook URL --------------------------------------------------------\n const defaultWebhook = `https://${apex(domains[0]!)}/m/webhooks/sendgrid`\n log('')\n log('SendGrid will POST event webhooks to this URL.')\n log(' This must be reachable from the public internet (not behind admin auth).')\n log('')\n const webhookUrl = await prompter('Webhook URL', { defaultValue: defaultWebhook })\n\n // ---- Cloudflare ---------------------------------------------------------\n log('')\n const cloudflareRaw = await prompter('Publish DNS records via Cloudflare API? (y/N)', { defaultValue: 'n' })\n const cloudflare = /^y/i.test(cloudflareRaw)\n let cloudflareZone: string | undefined\n\n if (cloudflare) {\n if (!env.CLOUDFLARE_API_TOKEN) {\n log('')\n log('CLOUDFLARE_API_TOKEN is not set in your environment.')\n log(' Create one at https://dash.cloudflare.com/profile/api-tokens')\n log(' Use the \"Edit zone DNS\" template, restricted to your zone(s).')\n log(' Permissions: Zone:Read + DNS:Edit.')\n log('')\n const token = await prompter('Paste your Cloudflare API token')\n if (!token) throw new Error('CLOUDFLARE_API_TOKEN is required when publishing via Cloudflare')\n envOverrides.CLOUDFLARE_API_TOKEN = token\n exportLines.push(`export CLOUDFLARE_API_TOKEN=\"${token}\"`)\n }\n log('')\n const zoneOverride = await prompter('Override Cloudflare zone (blank to infer per-domain)', { defaultValue: '' })\n if (zoneOverride.length > 0) cloudflareZone = zoneOverride\n }\n\n // ---- Force --------------------------------------------------------------\n log('')\n const forceRaw = await prompter(\n 'If a different webhook URL is already configured on this SendGrid account, overwrite it? (y/N)',\n { defaultValue: 'n' },\n )\n const force = /^y/i.test(forceRaw)\n\n // ---- Summary + confirm --------------------------------------------------\n log('')\n log('About to run setup with:')\n log(` domains: ${domains.join(', ')}`)\n log(` webhook URL: ${webhookUrl}`)\n log(` cloudflare: ${cloudflare ? 'yes' : 'no'}`)\n if (cloudflareZone) log(` cloudflare zone: ${cloudflareZone}`)\n log(` force: ${force ? 'yes' : 'no'}`)\n log('')\n const confirmRaw = await prompter('Proceed? (Y/n)', { defaultValue: 'y' })\n const proceed = !/^n/i.test(confirmRaw)\n\n return {\n opts: { domains, webhookUrl, cloudflare, cloudflareZone, force },\n envOverrides,\n exportLines,\n proceed,\n }\n}\n\n/** Strip a leading sub-label so we get the apex (best-effort for webhook URL default). */\nfunction apex(domain: string): string {\n const labels = domain.split('.').filter(Boolean)\n if (labels.length <= 2) return domain\n return labels.slice(-2).join('.')\n}\n","#!/usr/bin/env node\n/**\n * mailery CLI entry. Currently exposes a single command:\n *\n * npx mailery setup-sendgrid --domain X --webhook-url Y [--cloudflare]\n *\n * See src/cli/setup-sendgrid.ts for the full option list.\n */\n\nimport { setupSendgrid } from './setup-sendgrid.js'\nimport { makeReadlinePrompter, runInteractive } from './interactive.js'\n\nasync function main() {\n const argv = process.argv.slice(2)\n const cmd = argv[0]\n\n if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') {\n printUsage()\n process.exit(cmd ? 0 : 1)\n }\n\n switch (cmd) {\n case 'setup-sendgrid': {\n const opts = parseArgs(argv.slice(1))\n const argDomains = collectStringList(opts.domain)\n const argWebhook = typeof opts['webhook-url'] === 'string' ? (opts['webhook-url'] as string) : null\n const explicitInteractive = Boolean(opts.interactive)\n const explicitNonInteractive = Boolean(opts['no-interactive'])\n\n // Decide interactive vs args. Interactive when: --interactive, OR no\n // required args provided AND we have a TTY. Force non-interactive with\n // --no-interactive (useful for CI).\n const wantsInteractive =\n explicitInteractive ||\n (!explicitNonInteractive && argDomains.length === 0 && !argWebhook && Boolean(process.stdin.isTTY))\n\n try {\n if (wantsInteractive) {\n const { prompter, close } = makeReadlinePrompter()\n let interactive\n try {\n interactive = await runInteractive(prompter, process.env)\n } finally {\n close()\n }\n if (!interactive.proceed) {\n console.log('Aborted.')\n process.exit(0)\n }\n await setupSendgrid({\n ...interactive.opts,\n env: { ...process.env, ...interactive.envOverrides },\n })\n if (interactive.exportLines.length > 0) {\n console.log('')\n console.log('Add these to your shell rc (e.g. ~/.zshrc) so future runs find them:')\n for (const line of interactive.exportLines) console.log(` ${line}`)\n }\n } else {\n if (argDomains.length === 0) {\n console.error('error: --domain is required (repeat or comma-separate for multiple, or run without args for interactive mode)')\n printUsage()\n process.exit(2)\n }\n if (!argWebhook) {\n console.error('error: --webhook-url is required')\n printUsage()\n process.exit(2)\n }\n await setupSendgrid({\n domains: argDomains,\n subdomain: opts.subdomain ? String(opts.subdomain) : undefined,\n webhookUrl: argWebhook,\n cloudflare: Boolean(opts.cloudflare),\n cloudflareZone: opts['cloudflare-zone'] ? String(opts['cloudflare-zone']) : undefined,\n force: Boolean(opts.force),\n })\n }\n } catch (err: any) {\n console.error(`\\n✗ ${err?.message ?? err}`)\n process.exit(1)\n }\n break\n }\n default:\n console.error(`unknown command: ${cmd}`)\n printUsage()\n process.exit(2)\n }\n}\n\nfunction parseArgs(argv: string[]): Record<string, string | string[] | boolean> {\n const out: Record<string, string | string[] | boolean> = {}\n function setKey(key: string, value: string | boolean) {\n const existing = out[key]\n if (existing === undefined) {\n out[key] = value\n } else if (Array.isArray(existing)) {\n if (typeof value === 'string') existing.push(value)\n } else if (typeof existing === 'string' && typeof value === 'string') {\n out[key] = [existing, value]\n } else {\n out[key] = value\n }\n }\n for (let i = 0; i < argv.length; i++) {\n const tok = argv[i]!\n if (!tok.startsWith('--')) continue\n const eq = tok.indexOf('=')\n if (eq > 0) {\n setKey(tok.slice(2, eq), tok.slice(eq + 1))\n continue\n }\n const key = tok.slice(2)\n const next = argv[i + 1]\n if (next && !next.startsWith('--')) {\n setKey(key, next)\n i++\n } else {\n setKey(key, true)\n }\n }\n return out\n}\n\n/** Collapse a repeated/comma-separated flag value into a clean string[]. */\nfunction collectStringList(value: string | string[] | boolean | undefined): string[] {\n if (value === undefined || value === true || value === false) return []\n const raw = Array.isArray(value) ? value : [value]\n return raw\n .flatMap((s) => String(s).split(','))\n .map((s) => s.trim())\n .filter(Boolean)\n}\n\nfunction printUsage() {\n console.log(`\nmailery CLI\n\nUsage:\n mailery setup-sendgrid (interactive — prompts you for everything)\n mailery setup-sendgrid --domain <d>[,<d>...] --webhook-url <u> [options] (non-interactive)\n\nOptions:\n --domain <d> Domain to authenticate. Repeat (or comma-separate)\n to authenticate multiple in one run, e.g. when\n isolating marketing from transactional. The event\n webhook itself is account-level and only gets set up\n once.\n --subdomain <s> Sub-label for the link branding CNAME (default: em)\n --webhook-url <u> Public URL for the SendGrid event webhook\n --cloudflare Publish DNS records via the Cloudflare API\n --cloudflare-zone <z> Override the inferred zone (e.g. for multi-label\n public suffixes like .co.uk)\n --force Replace an existing event webhook URL if one is set\n --interactive Force interactive mode even when args are supplied\n --no-interactive Disable interactive mode even when args are missing\n (the script exits with usage instead of prompting —\n intended for CI / scripts)\n\nEnv vars:\n SENDGRID_API_KEY Required. See https://app.sendgrid.com/settings/api_keys\n Permissions: Mail Settings + Sender Authentication.\n CLOUDFLARE_API_TOKEN Required when --cloudflare is set. See\n https://dash.cloudflare.com/profile/api-tokens\n Permissions: Zone:Read + DNS:Edit on the zone(s)\n being published into.\n\nExamples:\n # one domain\n mailery setup-sendgrid \\\\\n --domain news.example.com \\\\\n --webhook-url https://example.com/m/webhooks/sendgrid \\\\\n --cloudflare\n\n # marketing + transactional in one shot\n mailery setup-sendgrid \\\\\n --domain news.example.com \\\\\n --domain mail.example.com \\\\\n --webhook-url https://example.com/m/webhooks/sendgrid \\\\\n --cloudflare\n`)\n}\n\nmain()\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli/setup-sendgrid.ts","../src/cli/interactive.ts","../src/cli/index.ts"],"names":["input","output"],"mappings":"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","file":"cli.js","sourcesContent":["/**\n * `mailery setup-sendgrid` — one-shot wiring of SendGrid for a domain.\n *\n * Idempotent. Re-running converges: existing domain auths are reused, existing\n * webhook config is preserved unless `--force` is passed, existing Cloudflare\n * DNS records with the correct values are left alone.\n *\n * npx mailery setup-sendgrid \\\n * --domain news.example.com \\\n * --webhook-url https://example.com/m/webhooks/sendgrid \\\n * --cloudflare\n *\n * Required env:\n * SENDGRID_API_KEY — full-access (or at minimum Mail Settings + Sender Auth)\n * CLOUDFLARE_API_TOKEN — only if --cloudflare; needs Zone:Read + DNS:Edit on the zone\n */\n\nexport interface SetupSendgridOpts {\n /** One or more domains to authenticate, e.g. [\"news.example.com\", \"mail.example.com\"]. */\n domains: string[]\n /** Sub-label SendGrid uses for the link branding CNAME (default 'em'). */\n subdomain?: string\n /** Where SendGrid should POST event webhooks. Account-level — only configured once regardless of domain count. */\n webhookUrl: string\n /** Publish DNS records via Cloudflare API. Requires CLOUDFLARE_API_TOKEN. */\n cloudflare?: boolean\n /** Override the parent zone if any domain isn't a subdomain of its eTLD+1. */\n cloudflareZone?: string\n /** Replace an existing webhook URL if one is already set. */\n force?: boolean\n /** Logger; defaults to console. Pass {} to silence. */\n logger?: { log?: (...a: unknown[]) => void; warn?: (...a: unknown[]) => void }\n /** Override fetch (for tests). */\n fetchFn?: typeof fetch\n /** Override env (for tests). */\n env?: Record<string, string | undefined>\n}\n\nexport interface DomainSetupResult {\n domain: string\n domainAuthId: number\n alreadyValidated: boolean\n dnsRecords: Array<{ type: string; host: string; data: string }>\n cloudflarePushed: number\n}\n\nexport interface SetupSendgridResult {\n domains: DomainSetupResult[]\n webhookKey: string\n webhookUrl: string\n envSnippet: string\n}\n\ninterface SgDomainAuth {\n id: number\n domain: string\n subdomain: string\n username: string\n valid: boolean\n dns: Record<string, { host: string; type: string; data: string; valid: boolean }>\n}\n\nexport async function setupSendgrid(opts: SetupSendgridOpts): Promise<SetupSendgridResult> {\n const env = opts.env ?? process.env\n const log = opts.logger ?? console\n const info = (m: string) => log.log?.(m)\n const warn = (m: string) => log.warn?.(m)\n const f = opts.fetchFn ?? globalThis.fetch\n\n const sgKey = env.SENDGRID_API_KEY\n if (!sgKey) {\n throw new Error(\n 'SENDGRID_API_KEY env var is required.\\n' +\n ' Create one at: https://app.sendgrid.com/settings/api_keys\\n' +\n ' Needs at least: \"Mail Settings\" (Full Access) and \"Sender Authentication\" (Full Access).\\n' +\n ' Then add to your shell rc (e.g. ~/.zshrc): export SENDGRID_API_KEY=\"SG.xxx\"',\n )\n }\n if (!/^SG\\./.test(sgKey)) {\n throw new Error(\n 'SENDGRID_API_KEY does not look like a SendGrid API key (expected to start with \"SG.\"). ' +\n 'Double-check you copied the full key from https://app.sendgrid.com/settings/api_keys.',\n )\n }\n\n if (opts.cloudflare && !env.CLOUDFLARE_API_TOKEN) {\n throw new Error(\n 'CLOUDFLARE_API_TOKEN env var is required when --cloudflare is set.\\n' +\n ' Create one at: https://dash.cloudflare.com/profile/api-tokens\\n' +\n ' Use the \"Edit zone DNS\" template, restricted to the specific zone you\\'re publishing into.\\n' +\n ' Permissions: Zone:Read + DNS:Edit.\\n' +\n ' Then add to your shell rc: export CLOUDFLARE_API_TOKEN=\"cf-xxx\"',\n )\n }\n\n if (!opts.domains || opts.domains.length === 0) {\n throw new Error('At least one --domain is required')\n }\n\n const sg = sendgridClient(sgKey, f)\n const cf = opts.cloudflare ? cloudflareClient(env.CLOUDFLARE_API_TOKEN!, f) : null\n\n // ---- Per-domain: authentication + DNS publish ---------------------------\n const domainResults: DomainSetupResult[] = []\n for (const domain of opts.domains) {\n info('')\n info(`=== ${domain} ===`)\n info(`SendGrid → checking domain authentication for ${domain}…`)\n const existing = await sg.findDomainAuth(domain)\n let auth: SgDomainAuth\n if (existing) {\n info(` → reusing existing domain auth #${existing.id}`)\n auth = existing\n } else {\n info(` → creating new domain auth`)\n auth = await sg.createDomainAuth({\n domain,\n subdomain: opts.subdomain ?? 'em',\n })\n }\n\n const dnsRecords = Object.values(auth.dns).map((r) => ({ type: r.type, host: r.host, data: r.data }))\n let cloudflarePushed = 0\n\n if (cf) {\n info(`Cloudflare → publishing ${dnsRecords.length} record(s)…`)\n const zoneName = opts.cloudflareZone ?? inferZone(domain)\n const zoneId = await cf.findZoneId(zoneName)\n if (!zoneId) {\n throw new Error(`Cloudflare zone \"${zoneName}\" not found on this account`)\n }\n for (const rec of dnsRecords) {\n const result = await cf.upsertRecord(zoneId, rec)\n if (result === 'created') {\n cloudflarePushed++\n info(` + ${rec.type} ${rec.host} → ${rec.data}`)\n } else if (result === 'updated') {\n cloudflarePushed++\n info(` ~ ${rec.type} ${rec.host} (updated to ${rec.data})`)\n } else {\n info(` = ${rec.type} ${rec.host} (already correct)`)\n }\n }\n\n info('SendGrid → triggering validation…')\n const validated = await sg.validateDomainAuth(auth.id)\n if (validated) {\n info(' ✓ domain authenticated')\n } else {\n warn(' ! validation not yet successful — DNS may still be propagating. Re-run in a few minutes.')\n }\n auth.valid = validated\n } else {\n info('— skipping Cloudflare publish (no --cloudflare). Add these records to your DNS provider:')\n for (const rec of dnsRecords) {\n info(` ${rec.type.padEnd(7)} ${rec.host.padEnd(50)} ${rec.data}`)\n }\n }\n\n domainResults.push({\n domain,\n domainAuthId: auth.id,\n alreadyValidated: auth.valid,\n dnsRecords,\n cloudflarePushed,\n })\n }\n\n info('')\n info('=== Event webhook (account-level) ===')\n\n // ---- Account-level: Enable Signed Event Webhook + fetch public key -----\n info('SendGrid → checking Signed Event Webhook…')\n // SendGrid's GET /user/webhooks/event/settings/signed only returns\n // `{ public_key }`. A non-empty key implies signing is already enabled —\n // re-PATCHing `enabled: true` rotates the keypair, silently breaking\n // signature verification everywhere the old key was deployed.\n const signed = await sg.getSignedWebhookSettings()\n let webhookKey: string\n if (signed.public_key) {\n info(' = signing already enabled; reusing existing public key')\n webhookKey = signed.public_key\n } else {\n info(' → enabling signing')\n webhookKey = await sg.enableSignedWebhook()\n info(' ✓ verification key fetched')\n }\n\n // ---- Step 4: Configure Event Webhook URL + event toggles ----------------\n info('SendGrid → checking Event Webhook…')\n const currentEvents = await sg.getEventWebhookSettings()\n const desiredEvents = {\n url: opts.webhookUrl,\n enabled: true,\n delivered: true,\n open: true,\n click: true,\n bounce: true,\n dropped: true,\n spam_report: true,\n unsubscribe: true,\n deferred: false,\n processed: false,\n group_resubscribe: false,\n group_unsubscribe: false,\n }\n if (currentEvents.url && currentEvents.url !== opts.webhookUrl && !opts.force) {\n throw new Error(\n `Event webhook is already configured for ${currentEvents.url}. ` +\n `Pass --force to overwrite (this will redirect all future events to ${opts.webhookUrl}).`,\n )\n }\n if (eventSettingsMatch(currentEvents, desiredEvents)) {\n info(` = webhook already configured for ${opts.webhookUrl}; nothing to change`)\n } else {\n await sg.setEventWebhookSettings(desiredEvents)\n info(` ✓ webhook URL set to ${opts.webhookUrl}`)\n }\n\n const envSnippet = [\n `# Add to your environment (or .env file):`,\n `SENDGRID_WEBHOOK_VERIFICATION_KEY=\"${webhookKey.replace(/\\n/g, '\\\\n')}\"`,\n ].join('\\n')\n\n info('')\n info('--- Done. Add to your environment ---')\n info(envSnippet)\n\n return {\n domains: domainResults,\n webhookKey,\n webhookUrl: opts.webhookUrl,\n envSnippet,\n }\n}\n\n// ---------------------------------------------------------------------------\n// SendGrid API client\n// ---------------------------------------------------------------------------\n\ninterface SendGridClient {\n findDomainAuth(domain: string): Promise<SgDomainAuth | null>\n createDomainAuth(input: { domain: string; subdomain: string }): Promise<SgDomainAuth>\n validateDomainAuth(id: number): Promise<boolean>\n getSignedWebhookSettings(): Promise<{ enabled?: boolean; public_key?: string }>\n enableSignedWebhook(): Promise<string>\n getEventWebhookSettings(): Promise<{ url?: string; enabled?: boolean; [k: string]: unknown }>\n setEventWebhookSettings(payload: Record<string, unknown>): Promise<void>\n}\n\nfunction sendgridClient(apiKey: string, fetchFn: typeof fetch): SendGridClient {\n const base = 'https://api.sendgrid.com/v3'\n const headers = { authorization: `Bearer ${apiKey}`, 'content-type': 'application/json' }\n\n async function call(path: string, init: RequestInit = {}): Promise<any> {\n const res = await fetchFn(`${base}${path}`, { ...init, headers: { ...headers, ...(init.headers as any) } })\n const text = await res.text()\n const body = text ? safeJson(text) : null\n if (res.status === 401 || res.status === 403) {\n throw new Error(\n `SendGrid rejected the request (HTTP ${res.status}). Your SENDGRID_API_KEY is missing the required permissions ` +\n 'or is invalid. The key needs \"Mail Settings\" (Full Access) and \"Sender Authentication\" (Full Access). ' +\n 'Manage keys at https://app.sendgrid.com/settings/api_keys.',\n )\n }\n if (!res.ok) {\n const message = body?.errors?.[0]?.message ?? text ?? res.statusText\n throw new Error(`SendGrid ${init.method ?? 'GET'} ${path} → ${res.status}: ${message}`)\n }\n return body\n }\n\n return {\n async findDomainAuth(domain: string) {\n const list: SgDomainAuth[] = (await call(`/whitelabel/domains?limit=200`)) ?? []\n return list.find((d) => d.domain === domain) ?? null\n },\n async createDomainAuth({ domain, subdomain }) {\n return call(`/whitelabel/domains`, {\n method: 'POST',\n body: JSON.stringify({ domain, subdomain, automatic_security: true }),\n }) as Promise<SgDomainAuth>\n },\n async validateDomainAuth(id: number) {\n const r = await call(`/whitelabel/domains/${id}/validate`, { method: 'POST' })\n return Boolean(r?.valid)\n },\n async getSignedWebhookSettings() {\n return call(`/user/webhooks/event/settings/signed`)\n },\n async enableSignedWebhook() {\n await call(`/user/webhooks/event/settings/signed`, {\n method: 'PATCH',\n body: JSON.stringify({ enabled: true }),\n })\n const r = await call(`/user/webhooks/event/settings/signed`)\n if (!r?.public_key) throw new Error('SendGrid did not return a public_key for the signed event webhook')\n return r.public_key as string\n },\n async getEventWebhookSettings() {\n return call(`/user/webhooks/event/settings`)\n },\n async setEventWebhookSettings(payload) {\n await call(`/user/webhooks/event/settings`, {\n method: 'PATCH',\n body: JSON.stringify(payload),\n })\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Cloudflare API client\n// ---------------------------------------------------------------------------\n\ninterface CloudflareClient {\n findZoneId(name: string): Promise<string | null>\n upsertRecord(zoneId: string, rec: { type: string; host: string; data: string }): Promise<'created' | 'updated' | 'noop'>\n}\n\nfunction cloudflareClient(apiToken: string, fetchFn: typeof fetch): CloudflareClient {\n const base = 'https://api.cloudflare.com/client/v4'\n const headers = { authorization: `Bearer ${apiToken}`, 'content-type': 'application/json' }\n\n async function call(path: string, init: RequestInit = {}): Promise<any> {\n const res = await fetchFn(`${base}${path}`, { ...init, headers: { ...headers, ...(init.headers as any) } })\n const body: any = await res.json().catch(() => null)\n if (res.status === 401 || res.status === 403) {\n throw new Error(\n `Cloudflare rejected the request (HTTP ${res.status}). Your CLOUDFLARE_API_TOKEN is invalid, expired, ` +\n 'or missing the required permissions. The token needs Zone:Read + DNS:Edit on the specific zone you\\'re ' +\n 'publishing into. Manage tokens at https://dash.cloudflare.com/profile/api-tokens.',\n )\n }\n if (!res.ok || (body && body.success === false)) {\n const message = body?.errors?.[0]?.message ?? res.statusText\n throw new Error(`Cloudflare ${init.method ?? 'GET'} ${path} → ${res.status}: ${message}`)\n }\n return body\n }\n\n return {\n async findZoneId(name: string) {\n const r = await call(`/zones?name=${encodeURIComponent(name)}`)\n return r?.result?.[0]?.id ?? null\n },\n async upsertRecord(zoneId, rec) {\n // CF requires the host to be a FQDN, which SendGrid already gives us.\n const existing = await call(\n `/zones/${zoneId}/dns_records?type=${encodeURIComponent(rec.type)}&name=${encodeURIComponent(rec.host)}`,\n )\n const match = existing?.result?.[0]\n const desiredBody = {\n type: rec.type,\n name: rec.host,\n content: rec.data,\n ttl: 1, // auto\n proxied: false, // critical: SendGrid DKIM CNAMEs must NOT be proxied\n }\n if (!match) {\n await call(`/zones/${zoneId}/dns_records`, { method: 'POST', body: JSON.stringify(desiredBody) })\n return 'created'\n }\n if (match.content === rec.data && match.proxied === false) {\n return 'noop'\n }\n await call(`/zones/${zoneId}/dns_records/${match.id}`, {\n method: 'PUT',\n body: JSON.stringify(desiredBody),\n })\n return 'updated'\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Best-effort eTLD+1 inference. Falls back to last two labels — fine for most\n * `.com` / `.io` style domains. For multi-label public suffixes (`.co.uk`)\n * pass `--cloudflare-zone` explicitly. */\nfunction inferZone(domain: string): string {\n const labels = domain.split('.').filter(Boolean)\n if (labels.length <= 2) return domain\n return labels.slice(-2).join('.')\n}\n\nfunction safeJson(s: string): any {\n try { return JSON.parse(s) } catch { return null }\n}\n\n/** Compare current SendGrid event webhook settings to the desired payload.\n * Treats missing keys on `current` as \"not yet set\" rather than equal-to-false,\n * so a partially-configured webhook still triggers a PATCH. */\nfunction eventSettingsMatch(current: Record<string, unknown>, desired: Record<string, unknown>): boolean {\n for (const [k, v] of Object.entries(desired)) {\n if (current[k] !== v) return false\n }\n return true\n}\n","/**\n * Interactive wizard for `mailery setup-sendgrid`. Walks the user through:\n * - Required env vars (offers to paste them in, then prints export lines)\n * - Domains (accept multiple via repeated entry or comma-separation)\n * - Webhook URL (with a default derived from the first domain's apex)\n * - Cloudflare toggle + zone override\n * - Force flag\n *\n * Renders a summary before any API call and asks for confirmation. All\n * questions are funneled through a `Prompter` so tests can inject canned\n * answers without touching stdin.\n */\n\nimport * as readline from 'node:readline/promises'\nimport { stdin as input, stdout as output } from 'node:process'\n\nimport type { SetupSendgridOpts } from './setup-sendgrid.js'\n\nexport type Prompter = (question: string, opts?: { defaultValue?: string }) => Promise<string>\n\nexport interface InteractiveResult {\n opts: Omit<SetupSendgridOpts, 'fetchFn' | 'logger' | 'env'>\n envOverrides: Record<string, string>\n exportLines: string[]\n proceed: boolean\n}\n\nexport function makeReadlinePrompter(): { prompter: Prompter; close: () => void } {\n const rl = readline.createInterface({ input, output })\n const prompter: Prompter = async (q, opts) => {\n const suffix = opts?.defaultValue ? ` [${opts.defaultValue}]` : ''\n const raw = await rl.question(`${q}${suffix}: `)\n const trimmed = raw.trim()\n return trimmed.length === 0 && opts?.defaultValue ? opts.defaultValue : trimmed\n }\n return { prompter, close: () => rl.close() }\n}\n\nexport async function runInteractive(\n prompter: Prompter,\n env: Record<string, string | undefined>,\n logger: { log: (...a: unknown[]) => void } = console,\n): Promise<InteractiveResult> {\n const log = (m: string) => logger.log(m)\n const exportLines: string[] = []\n const envOverrides: Record<string, string> = {}\n\n log('')\n log('mailery setup-sendgrid (interactive)')\n log('────────────────────────────────────')\n log('')\n\n // ---- SendGrid API key ---------------------------------------------------\n if (!env.SENDGRID_API_KEY) {\n log('SENDGRID_API_KEY is not set in your environment.')\n log(' Create one at https://app.sendgrid.com/settings/api_keys')\n log(' Permissions: Mail Settings (Full Access) + Sender Authentication (Full Access).')\n log('')\n const key = await prompter('Paste your SendGrid API key (starts with SG.)')\n if (!key) throw new Error('SENDGRID_API_KEY is required')\n envOverrides.SENDGRID_API_KEY = key\n exportLines.push(`export SENDGRID_API_KEY=\"${key}\"`)\n }\n\n // ---- Domains ------------------------------------------------------------\n log('')\n log('Which sender domain(s) do you want to authenticate?')\n log(' Examples:')\n log(' news.example.com (one)')\n log(' news.example.com, mail.example.com (two — comma-separated, in one line)')\n log(' Splitting marketing from transactional onto separate domains is recommended.')\n log('')\n const domainsRaw = await prompter('Domains')\n const domains = domainsRaw\n .split(',')\n .map((d) => d.trim())\n .filter(Boolean)\n if (domains.length === 0) throw new Error('at least one --domain is required')\n\n // ---- Webhook URL --------------------------------------------------------\n const defaultWebhook = `https://${apex(domains[0]!)}/m/webhooks/sendgrid`\n log('')\n log('SendGrid will POST event webhooks to this URL.')\n log(' This must be reachable from the public internet (not behind admin auth).')\n log('')\n const webhookUrl = await prompter('Webhook URL', { defaultValue: defaultWebhook })\n\n // ---- Cloudflare ---------------------------------------------------------\n log('')\n const cloudflareRaw = await prompter('Publish DNS records via Cloudflare API? (y/N)', { defaultValue: 'n' })\n const cloudflare = /^y/i.test(cloudflareRaw)\n let cloudflareZone: string | undefined\n\n if (cloudflare) {\n if (!env.CLOUDFLARE_API_TOKEN) {\n log('')\n log('CLOUDFLARE_API_TOKEN is not set in your environment.')\n log(' Create one at https://dash.cloudflare.com/profile/api-tokens')\n log(' Use the \"Edit zone DNS\" template, restricted to your zone(s).')\n log(' Permissions: Zone:Read + DNS:Edit.')\n log('')\n const token = await prompter('Paste your Cloudflare API token')\n if (!token) throw new Error('CLOUDFLARE_API_TOKEN is required when publishing via Cloudflare')\n envOverrides.CLOUDFLARE_API_TOKEN = token\n exportLines.push(`export CLOUDFLARE_API_TOKEN=\"${token}\"`)\n }\n log('')\n const zoneOverride = await prompter('Override Cloudflare zone (blank to infer per-domain)', { defaultValue: '' })\n if (zoneOverride.length > 0) cloudflareZone = zoneOverride\n }\n\n // ---- Force --------------------------------------------------------------\n log('')\n const forceRaw = await prompter(\n 'If a different webhook URL is already configured on this SendGrid account, overwrite it? (y/N)',\n { defaultValue: 'n' },\n )\n const force = /^y/i.test(forceRaw)\n\n // ---- Summary + confirm --------------------------------------------------\n log('')\n log('About to run setup with:')\n log(` domains: ${domains.join(', ')}`)\n log(` webhook URL: ${webhookUrl}`)\n log(` cloudflare: ${cloudflare ? 'yes' : 'no'}`)\n if (cloudflareZone) log(` cloudflare zone: ${cloudflareZone}`)\n log(` force: ${force ? 'yes' : 'no'}`)\n log('')\n const confirmRaw = await prompter('Proceed? (Y/n)', { defaultValue: 'y' })\n const proceed = !/^n/i.test(confirmRaw)\n\n return {\n opts: { domains, webhookUrl, cloudflare, cloudflareZone, force },\n envOverrides,\n exportLines,\n proceed,\n }\n}\n\n/** Strip a leading sub-label so we get the apex (best-effort for webhook URL default). */\nfunction apex(domain: string): string {\n const labels = domain.split('.').filter(Boolean)\n if (labels.length <= 2) return domain\n return labels.slice(-2).join('.')\n}\n","#!/usr/bin/env node\n/**\n * mailery CLI entry. Currently exposes a single command:\n *\n * npx mailery setup-sendgrid --domain X --webhook-url Y [--cloudflare]\n *\n * See src/cli/setup-sendgrid.ts for the full option list.\n */\n\nimport { setupSendgrid } from './setup-sendgrid.js'\nimport { makeReadlinePrompter, runInteractive } from './interactive.js'\n\nasync function main() {\n const argv = process.argv.slice(2)\n const cmd = argv[0]\n\n if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') {\n printUsage()\n process.exit(cmd ? 0 : 1)\n }\n\n switch (cmd) {\n case 'setup-sendgrid': {\n const opts = parseArgs(argv.slice(1))\n const argDomains = collectStringList(opts.domain)\n const argWebhook = typeof opts['webhook-url'] === 'string' ? (opts['webhook-url'] as string) : null\n const explicitInteractive = Boolean(opts.interactive)\n const explicitNonInteractive = Boolean(opts['no-interactive'])\n\n // Decide interactive vs args. Interactive when: --interactive, OR no\n // required args provided AND we have a TTY. Force non-interactive with\n // --no-interactive (useful for CI).\n const wantsInteractive =\n explicitInteractive ||\n (!explicitNonInteractive && argDomains.length === 0 && !argWebhook && Boolean(process.stdin.isTTY))\n\n try {\n if (wantsInteractive) {\n const { prompter, close } = makeReadlinePrompter()\n let interactive\n try {\n interactive = await runInteractive(prompter, process.env)\n } finally {\n close()\n }\n if (!interactive.proceed) {\n console.log('Aborted.')\n process.exit(0)\n }\n await setupSendgrid({\n ...interactive.opts,\n env: { ...process.env, ...interactive.envOverrides },\n })\n if (interactive.exportLines.length > 0) {\n console.log('')\n console.log('Add these to your shell rc (e.g. ~/.zshrc) so future runs find them:')\n for (const line of interactive.exportLines) console.log(` ${line}`)\n }\n } else {\n if (argDomains.length === 0) {\n console.error('error: --domain is required (repeat or comma-separate for multiple, or run without args for interactive mode)')\n printUsage()\n process.exit(2)\n }\n if (!argWebhook) {\n console.error('error: --webhook-url is required')\n printUsage()\n process.exit(2)\n }\n await setupSendgrid({\n domains: argDomains,\n subdomain: opts.subdomain ? String(opts.subdomain) : undefined,\n webhookUrl: argWebhook,\n cloudflare: Boolean(opts.cloudflare),\n cloudflareZone: opts['cloudflare-zone'] ? String(opts['cloudflare-zone']) : undefined,\n force: Boolean(opts.force),\n })\n }\n } catch (err: any) {\n console.error(`\\n✗ ${err?.message ?? err}`)\n process.exit(1)\n }\n break\n }\n default:\n console.error(`unknown command: ${cmd}`)\n printUsage()\n process.exit(2)\n }\n}\n\nfunction parseArgs(argv: string[]): Record<string, string | string[] | boolean> {\n const out: Record<string, string | string[] | boolean> = {}\n function setKey(key: string, value: string | boolean) {\n const existing = out[key]\n if (existing === undefined) {\n out[key] = value\n } else if (Array.isArray(existing)) {\n if (typeof value === 'string') existing.push(value)\n } else if (typeof existing === 'string' && typeof value === 'string') {\n out[key] = [existing, value]\n } else {\n out[key] = value\n }\n }\n for (let i = 0; i < argv.length; i++) {\n const tok = argv[i]!\n if (!tok.startsWith('--')) continue\n const eq = tok.indexOf('=')\n if (eq > 0) {\n setKey(tok.slice(2, eq), tok.slice(eq + 1))\n continue\n }\n const key = tok.slice(2)\n const next = argv[i + 1]\n if (next && !next.startsWith('--')) {\n setKey(key, next)\n i++\n } else {\n setKey(key, true)\n }\n }\n return out\n}\n\n/** Collapse a repeated/comma-separated flag value into a clean string[]. */\nfunction collectStringList(value: string | string[] | boolean | undefined): string[] {\n if (value === undefined || value === true || value === false) return []\n const raw = Array.isArray(value) ? value : [value]\n return raw\n .flatMap((s) => String(s).split(','))\n .map((s) => s.trim())\n .filter(Boolean)\n}\n\nfunction printUsage() {\n console.log(`\nmailery CLI\n\nUsage:\n mailery setup-sendgrid (interactive — prompts you for everything)\n mailery setup-sendgrid --domain <d>[,<d>...] --webhook-url <u> [options] (non-interactive)\n\nOptions:\n --domain <d> Domain to authenticate. Repeat (or comma-separate)\n to authenticate multiple in one run, e.g. when\n isolating marketing from transactional. The event\n webhook itself is account-level and only gets set up\n once.\n --subdomain <s> Sub-label for the link branding CNAME (default: em)\n --webhook-url <u> Public URL for the SendGrid event webhook\n --cloudflare Publish DNS records via the Cloudflare API\n --cloudflare-zone <z> Override the inferred zone (e.g. for multi-label\n public suffixes like .co.uk)\n --force Replace an existing event webhook URL if one is set\n --interactive Force interactive mode even when args are supplied\n --no-interactive Disable interactive mode even when args are missing\n (the script exits with usage instead of prompting —\n intended for CI / scripts)\n\nEnv vars:\n SENDGRID_API_KEY Required. See https://app.sendgrid.com/settings/api_keys\n Permissions: Mail Settings + Sender Authentication.\n CLOUDFLARE_API_TOKEN Required when --cloudflare is set. See\n https://dash.cloudflare.com/profile/api-tokens\n Permissions: Zone:Read + DNS:Edit on the zone(s)\n being published into.\n\nExamples:\n # one domain\n mailery setup-sendgrid \\\\\n --domain news.example.com \\\\\n --webhook-url https://example.com/m/webhooks/sendgrid \\\\\n --cloudflare\n\n # marketing + transactional in one shot\n mailery setup-sendgrid \\\\\n --domain news.example.com \\\\\n --domain mail.example.com \\\\\n --webhook-url https://example.com/m/webhooks/sendgrid \\\\\n --cloudflare\n`)\n}\n\nmain()\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mailery",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Embedded, MongoDB-backed email automation for Node.js. Triggered flows, broadcasts, tracking, suppression, and an admin UI you mount inside your Express app.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jeff Jassky <jeff@jeffjassky.com>",
|