@premium-cms/plugin-cloudflare-email-byo 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,9 +16,9 @@ verified domain. The platform never holds the credential.
16
16
 
17
17
  1. In your own Cloudflare dashboard, onboard a domain for **Email Sending**.
18
18
  2. Create an **API token** with email sending permission.
19
- 3. Open the plugin's **Email (own Cloudflare)** settings page and fill in the
20
- account ID, token, and sender address.
21
- 4. Click **Send test email**.
19
+ 3. Under **Plugins Email (own Cloudflare) Settings** fill in the account ID,
20
+ token and sender address (the plugin manager's own settings form).
21
+ 4. On the plugin's **Email (own Cloudflare)** page, click **Send test email**.
22
22
  5. Under **Settings → Email**, select `cloudflare-email-byo` as the provider.
23
23
 
24
24
  Step 5 matters: EmDash auto-selects a provider only when exactly one is active.
@@ -34,9 +34,10 @@ With both this and the built-in provider installed, the choice must be explicit.
34
34
  | Sender name | Optional |
35
35
  | Reply-To | Optional |
36
36
 
37
- Leaving the token field blank on save **keeps** the stored token — Block Kit
38
- submits an empty string for an untouched secret field, so writing it blindly
39
- would wipe a working credential on every unrelated edit. Type `clear` to remove it.
37
+ The settings form is the plugin manager's auto-generated one (declared as
38
+ `admin.settingsSchema` in `emdash-plugin.jsonc`); the token is a write-only
39
+ secret there it is never shown again, and saving other fields leaves it alone.
40
+ The plugin's own page only shows the status and sends a test email.
40
41
 
41
42
  ## Trust contract
42
43
 
package/dist/index.mjs CHANGED
@@ -7,7 +7,7 @@
7
7
  /** @type {import("@premium-cms/emdash").PluginDescriptor} */
8
8
  const descriptor = Object.freeze({
9
9
  "id": "cloudflare-email-byo",
10
- "version": "0.1.0",
10
+ "version": "0.2.0",
11
11
  "format": "standard",
12
12
  "entrypoint": "@premium-cms/plugin-cloudflare-email-byo/sandbox",
13
13
  "capabilities": [
@@ -18,9 +18,36 @@ const descriptor = Object.freeze({
18
18
  "api.cloudflare.com"
19
19
  ],
20
20
  "storage": {},
21
+ "settingsSchema": {
22
+ "accountId": {
23
+ "label": "Cloudflare account ID",
24
+ "description": "32 hex characters, from the dashboard sidebar of the account that sends the mail.",
25
+ "type": "string"
26
+ },
27
+ "apiToken": {
28
+ "label": "API token",
29
+ "description": "A token of that account with the Email Sending permission.",
30
+ "type": "secret"
31
+ },
32
+ "fromAddress": {
33
+ "label": "Send from",
34
+ "description": "An address on a domain onboarded to Email Sending in that account.",
35
+ "type": "email",
36
+ "placeholder": "cms@mail.yourdomain.com"
37
+ },
38
+ "fromName": {
39
+ "label": "Sender name",
40
+ "description": "Optional, e.g. the site name.",
41
+ "type": "string"
42
+ },
43
+ "replyTo": {
44
+ "label": "Reply-To (optional)",
45
+ "type": "email"
46
+ }
47
+ },
21
48
  "adminPages": [
22
49
  {
23
- "path": "/settings",
50
+ "path": "/status",
24
51
  "label": "Email (own Cloudflare)",
25
52
  "icon": "mail"
26
53
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "cloudflare-email-byo",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "declaredAccess": {
5
5
  "network": {
6
6
  "request": {
@@ -31,13 +31,39 @@
31
31
  "routes": [
32
32
  "admin",
33
33
  "settings",
34
- "settings/save",
35
34
  "test"
36
35
  ],
37
36
  "admin": {
37
+ "settingsSchema": {
38
+ "accountId": {
39
+ "label": "Cloudflare account ID",
40
+ "description": "32 hex characters, from the dashboard sidebar of the account that sends the mail.",
41
+ "type": "string"
42
+ },
43
+ "apiToken": {
44
+ "label": "API token",
45
+ "description": "A token of that account with the Email Sending permission.",
46
+ "type": "secret"
47
+ },
48
+ "fromAddress": {
49
+ "label": "Send from",
50
+ "description": "An address on a domain onboarded to Email Sending in that account.",
51
+ "type": "email",
52
+ "placeholder": "cms@mail.yourdomain.com"
53
+ },
54
+ "fromName": {
55
+ "label": "Sender name",
56
+ "description": "Optional, e.g. the site name.",
57
+ "type": "string"
58
+ },
59
+ "replyTo": {
60
+ "label": "Reply-To (optional)",
61
+ "type": "email"
62
+ }
63
+ },
38
64
  "pages": [
39
65
  {
40
- "path": "/settings",
66
+ "path": "/status",
41
67
  "label": "Email (own Cloudflare)",
42
68
  "icon": "mail"
43
69
  }
package/dist/plugin.mjs CHANGED
@@ -1,4 +1,4 @@
1
- const e=`settings:`,t=/^[0-9a-f]{32}$/;function n(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function r(e,t=``){return typeof e==`string`?e:t}function i(e){let t=e.indexOf(`@`);return t>0&&t<e.length-1&&!/\s/.test(e)}async function a(t){let n={accountId:``,fromAddress:``,fromName:``,replyTo:``,apiToken:``};try{let n=await t.kv.list(e),i={};for(let t of n)i[t.key.replace(e,``)]=t.value;return{accountId:r(i.accountId).trim(),fromAddress:r(i.fromAddress).trim(),fromName:r(i.fromName).trim(),replyTo:r(i.replyTo).trim(),apiToken:r(i.apiToken)}}catch(e){return t.log.error(`Failed to read settings`,e),n}}function o(e){let n=[];return e.accountId?t.test(e.accountId)||n.push(`a valid account ID (32 hex characters)`):n.push(`account ID`),e.apiToken||n.push(`API token`),e.fromAddress?i(e.fromAddress)||n.push(`a valid sender address`):n.push(`sender address`),n.length===0?{ok:!0}:{ok:!1,missing:n}}function s(e){return e?e.length<=8?`••••••••`:`••••••••${e.slice(-4)}`:``}function c(e,t){if(e===200&&t?.success!==!1){let e=t?.result?.permanent_bounces??[];return e.length>0?`permanently bounced: ${e.join(`, `)}`:null}let n=(t?.errors??[]).map(e=>e.code?`${e.code}: ${e.message??``}`:e.message??``).filter(Boolean).join(`; `)||`HTTP ${e}`;switch(e){case 401:case 403:return`authentication rejected (${n}) — check the API token has email sending permission`;case 400:return`request rejected (${n}) — check the sender domain is onboarded for Email Sending`;case 429:return`rate limited (${n}) — retryable`;default:return e>=500?`Cloudflare error (${n}) — retryable`:n}}async function l(e,t,n){if(!e.http)throw Error("[cloudflare-email-byo] ctx.http is unavailable — the plugin needs the `network:request` capability.");let r={to:n.to,from:t.fromName?{address:t.fromAddress,name:t.fromName}:{address:t.fromAddress},subject:n.subject,text:n.text};n.html&&(r.html=n.html),t.replyTo&&(r.reply_to=t.replyTo);let i=await e.http.fetch(`https://api.cloudflare.com/client/v4/accounts/${t.accountId}/email/sending/send`,{method:`POST`,headers:{Authorization:`Bearer ${t.apiToken}`,"Content-Type":`application/json`},body:JSON.stringify(r)}),a=null;try{a=await i.json()}catch{}let o=c(i.status,a);if(o)throw Error(`[cloudflare-email-byo] send failed for ${n.to} — ${o}`);e.log.info(`email delivered via the site's own Cloudflare account`,{to:n.to,subject:n.subject,accountId:t.accountId})}const u={hooks:{"plugin:install":async(e,t)=>{t.log.info(`Cloudflare Email (own account) installed — add credentials on its settings page, then select it under Settings → Email`)},"email:deliver":{exclusive:!0,handler:async(e,t)=>{let n=await a(t),r=o(n);if(!r.ok)throw Error(`[cloudflare-email-byo] not configured — missing ${r.missing.join(`, `)}. Add them on the plugin's settings page.`);await l(t,n,e.message)}}},routes:{admin:{handler:async(e,t)=>{let n=e.input;return n.type===`page_load`&&n.page===`/settings`?p(t):n.type===`form_submit`&&n.action_id===`save_settings`?p(t,await d(t,n.values??{})):n.type===`block_action`&&n.action_id===`send_test`?p(t,await f(t)):{blocks:[]}}},settings:{handler:async(e,t)=>{let n=await a(t),r=o(n);return{accountId:n.accountId,fromAddress:n.fromAddress,fromName:n.fromName,replyTo:n.replyTo,apiToken:s(n.apiToken??``),configured:r.ok,missing:r.ok?[]:r.missing}}},"settings/save":{handler:async(e,t)=>{try{return{success:!0,note:await d(t,n(e.input)?e.input:{}),configured:o(await a(t)).ok}}catch(e){return t.log.error(`Failed to save settings`,e),{success:!1,error:String(e)}}}},test:{handler:async(e,t)=>({message:await f(t)})}}};async function d(t,n){if(typeof n.accountId==`string`&&await t.kv.set(`${e}accountId`,n.accountId.trim().toLowerCase()),typeof n.fromAddress==`string`&&await t.kv.set(`${e}fromAddress`,n.fromAddress.trim()),typeof n.fromName==`string`&&await t.kv.set(`${e}fromName`,n.fromName.trim()),typeof n.replyTo==`string`&&await t.kv.set(`${e}replyTo`,n.replyTo.trim()),typeof n.apiToken==`string`){let r=n.apiToken.trim();if(r===`clear`)return await t.kv.set(`${e}apiToken`,``),`API token cleared.`;if(r)return await t.kv.set(`${e}apiToken`,r),`API token updated.`}}async function f(e){let t=await a(e),n=o(t);if(!n.ok)return`Cannot send a test — missing ${n.missing.join(`, `)}.`;try{return await l(e,t,{to:t.fromAddress,subject:`Test email from your site`,text:`This is a test from the Cloudflare Email (own account) provider.
1
+ const e=`settings:`,t=/^[0-9a-f]{32}$/;function n(e,t=``){return typeof e==`string`?e:t}function r(e){let t=e.indexOf(`@`);return t>0&&t<e.length-1&&!/\s/.test(e)}async function i(t){let r={accountId:``,fromAddress:``,fromName:``,replyTo:``,apiToken:``};try{let r=await t.kv.list(e),i={};for(let t of r)i[t.key.replace(e,``)]=t.value;return{accountId:n(i.accountId).trim().toLowerCase(),fromAddress:n(i.fromAddress).trim(),fromName:n(i.fromName).trim(),replyTo:n(i.replyTo).trim(),apiToken:n(i.apiToken)}}catch(e){return t.log.error(`Failed to read settings`,e),r}}function a(e){let n=[];return e.accountId?t.test(e.accountId)||n.push(`a valid account ID (32 hex characters)`):n.push(`account ID`),e.apiToken||n.push(`API token`),e.fromAddress?r(e.fromAddress)||n.push(`a valid sender address`):n.push(`sender address`),n.length===0?{ok:!0}:{ok:!1,missing:n}}function o(e){return e?e.length<=8?`••••••••`:`••••••••${e.slice(-4)}`:``}function s(e,t){if(e===200&&t?.success!==!1){let e=t?.result?.permanent_bounces??[];return e.length>0?`permanently bounced: ${e.join(`, `)}`:null}let n=(t?.errors??[]).map(e=>e.code?`${e.code}: ${e.message??``}`:e.message??``).filter(Boolean).join(`; `)||`HTTP ${e}`;switch(e){case 401:case 403:return`authentication rejected (${n}) — check the API token has email sending permission`;case 400:return`request rejected (${n}) — check the sender domain is onboarded for Email Sending`;case 429:return`rate limited (${n}) — retryable`;default:return e>=500?`Cloudflare error (${n}) — retryable`:n}}async function c(e,t,n){if(!e.http)throw Error("[cloudflare-email-byo] ctx.http is unavailable — the plugin needs the `network:request` capability.");let r={to:n.to,from:t.fromName?{address:t.fromAddress,name:t.fromName}:{address:t.fromAddress},subject:n.subject,text:n.text};n.html&&(r.html=n.html),t.replyTo&&(r.reply_to=t.replyTo);let i=await e.http.fetch(`https://api.cloudflare.com/client/v4/accounts/${t.accountId}/email/sending/send`,{method:`POST`,headers:{Authorization:`Bearer ${t.apiToken}`,"Content-Type":`application/json`},body:JSON.stringify(r)}),a=null;try{a=await i.json()}catch{}let o=s(i.status,a);if(o)throw Error(`[cloudflare-email-byo] send failed for ${n.to} — ${o}`);e.log.info(`email delivered via the site's own Cloudflare account`,{to:n.to,subject:n.subject,accountId:t.accountId})}const l={hooks:{"plugin:install":async(e,t)=>{t.log.info(`Cloudflare Email (own account) installed — add credentials on its settings page, then select it under Settings → Email`)},"email:deliver":{exclusive:!0,handler:async(e,t)=>{let n=await i(t),r=a(n);if(!r.ok)throw Error(`[cloudflare-email-byo] not configured — missing ${r.missing.join(`, `)}. Add them on the plugin's settings page.`);await c(t,n,e.message)}}},routes:{admin:{handler:async(e,t)=>{let n=e.input;return n.type===`page_load`&&n.page===`/status`?d(t):n.type===`block_action`&&n.action_id===`send_test`?d(t,await u(t)):{blocks:[]}}},settings:{handler:async(e,t)=>{let n=await i(t),r=a(n);return{accountId:n.accountId,fromAddress:n.fromAddress,fromName:n.fromName,replyTo:n.replyTo,apiToken:o(n.apiToken??``),configured:r.ok,missing:r.ok?[]:r.missing}}},test:{handler:async(e,t)=>({message:await u(t)})}}};async function u(e){let t=await i(e),n=a(t);if(!n.ok)return`Cannot send a test — missing ${n.missing.join(`, `)}.`;try{return await c(e,t,{to:t.fromAddress,subject:`Test email from your site`,text:`This is a test from the Cloudflare Email (own account) provider.
2
2
 
3
- If you received it, transactional email is sending through your own Cloudflare account.`}),`Test sent to ${t.fromAddress}.`}catch(e){return String(e instanceof Error?e.message:e)}}async function p(e,t){try{let n=await a(e),r=o(n),i=!!n.apiToken,c=[{type:`header`,text:`Email from your own Cloudflare account`},{type:`context`,text:`Transactional email (sign-in links, invites, notifications) is sent through your Cloudflare account instead of the platform's.`},{type:`divider`},{type:`section`,text:r.ok?`Ready — sending as ${n.fromAddress} from account ${n.accountId}.`:`Not sending yet — missing ${r.missing.join(`, `)}.`}];return t&&c.push({type:`context`,text:t}),c.push({type:`divider`},{type:`form`,block_id:`cloudflare-email-byo-settings`,fields:[{type:`text_input`,action_id:`accountId`,label:`Cloudflare account ID`,placeholder:`32 hex characters, from the dashboard sidebar`,initial_value:n.accountId},{type:`secret_input`,action_id:`apiToken`,label:i?`API token (stored: ${s(n.apiToken??``)} leave blank to keep, type "clear" to remove)`:`API token (needs email sending permission)`},{type:`text_input`,action_id:`fromAddress`,label:`Send from`,placeholder:`cms@mail.yourdomain.com`,initial_value:n.fromAddress},{type:`text_input`,action_id:`fromName`,label:`Sender name`,placeholder:`Your Site`,initial_value:n.fromName},{type:`text_input`,action_id:`replyTo`,label:`Reply-To (optional)`,initial_value:n.replyTo}],submit:{label:`Save`,action_id:`save_settings`}},{type:`actions`,elements:[{type:`button`,text:`Send test email`,action_id:`send_test`,style:`primary`}]},{type:`divider`},{type:`context`,text:`The sending domain must be onboarded for Email Sending in your own Cloudflare account, and the token needs email sending permission. Once saved, choose this provider under Settings → Email.`}),{blocks:c}}catch(t){return e.log.error(`Failed to build settings page`,t),{blocks:[{type:`context`,text:`Failed to load email settings.`}]}}}export{u as default};
3
+ If you received it, transactional email is sending through your own Cloudflare account.`}),`Test sent to ${t.fromAddress}.`}catch(e){return String(e instanceof Error?e.message:e)}}async function d(e,t){try{let n=await i(e),r=a(n),o=[{type:`header`,text:`Email from your own Cloudflare account`},{type:`context`,text:`Transactional email (sign-in links, invites, notifications) is sent through your Cloudflare account instead of the platform's.`},{type:`divider`},{type:`section`,text:r.ok?`Ready — sending as ${n.fromAddress} from account ${n.accountId}.`:`Not sending yet — missing ${r.missing.join(`, `)}.`}];return t&&o.push({type:`context`,text:t}),o.push({type:`context`,text:`Account, token and sender are set under Plugins Email (own Cloudflare) Settings.`},{type:`divider`},{type:`actions`,elements:[{type:`button`,text:`Send test email`,action_id:`send_test`,style:`primary`}]},{type:`divider`},{type:`context`,text:`The sending domain must be onboarded for Email Sending in your own Cloudflare account, and the token needs email sending permission. Once saved, choose this provider under Settings → Email.`}),{blocks:o}}catch(t){return e.log.error(`Failed to build settings page`,t),{blocks:[{type:`context`,text:`Failed to load email settings.`}]}}}export{l as default};
4
4
  //# sourceMappingURL=plugin.mjs.map
@@ -28,6 +28,26 @@
28
28
  "storage": {},
29
29
 
30
30
  "admin": {
31
- "pages": [{ "path": "/settings", "label": "Email (own Cloudflare)", "icon": "mail" }],
31
+ "settingsSchema": {
32
+ "accountId": {
33
+ "type": "string",
34
+ "label": "Cloudflare account ID",
35
+ "description": "32 hex characters, from the dashboard sidebar of the account that sends the mail."
36
+ },
37
+ "apiToken": {
38
+ "type": "secret",
39
+ "label": "API token",
40
+ "description": "A token of that account with the Email Sending permission."
41
+ },
42
+ "fromAddress": {
43
+ "type": "email",
44
+ "label": "Send from",
45
+ "placeholder": "cms@mail.yourdomain.com",
46
+ "description": "An address on a domain onboarded to Email Sending in that account."
47
+ },
48
+ "fromName": { "type": "string", "label": "Sender name", "description": "Optional, e.g. the site name." },
49
+ "replyTo": { "type": "email", "label": "Reply-To (optional)" }
50
+ },
51
+ "pages": [{ "path": "/status", "label": "Email (own Cloudflare)", "icon": "mail" }],
32
52
  },
33
53
  }
package/package.json CHANGED
@@ -1,39 +1,43 @@
1
1
  {
2
- "name": "@premium-cms/plugin-cloudflare-email-byo",
3
- "version": "0.1.0",
4
- "type": "module",
5
- "description": "Send email through the site owner's own Cloudflare account",
6
- "main": "dist/index.mjs",
7
- "exports": {
8
- ".": {
9
- "import": "./dist/index.mjs",
10
- "types": "./dist/index.d.mts"
11
- },
12
- "./sandbox": "./dist/plugin.mjs"
13
- },
14
- "files": [
15
- "dist",
16
- "emdash-plugin.jsonc"
17
- ],
18
- "scripts": {
19
- "build": "emdash-plugin build",
20
- "dev": "emdash-plugin dev",
21
- "bundle": "emdash-plugin bundle",
22
- "validate": "emdash-plugin validate .",
23
- "typecheck": "tsc --noEmit",
24
- "test": "vitest run"
25
- },
26
- "peerDependencies": {
27
- "@premium-cms/emdash": ">=0.35.0"
28
- },
29
- "devDependencies": {
30
- "@premium-cms/plugin-cli": "^0.8.1",
31
- "@premium-cms/plugin-types": "^0.3.0",
32
- "@premium-cms/emdash": "^0.35.0",
33
- "typescript": "^6.0.3",
34
- "vitest": "^4.1.5"
35
- },
36
- "publishConfig": {
37
- "access": "public"
38
- }
2
+ "name": "@premium-cms/plugin-cloudflare-email-byo",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "description": "Send email through the site owner's own Cloudflare account",
6
+ "main": "dist/index.mjs",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.mjs",
10
+ "types": "./dist/index.d.mts"
11
+ },
12
+ "./sandbox": "./dist/plugin.mjs"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "emdash-plugin.jsonc"
17
+ ],
18
+ "scripts": {
19
+ "build": "emdash-plugin build",
20
+ "dev": "emdash-plugin dev",
21
+ "bundle": "emdash-plugin bundle",
22
+ "validate": "emdash-plugin validate .",
23
+ "typecheck": "tsc --noEmit",
24
+ "test": "vitest run"
25
+ },
26
+ "peerDependencies": {
27
+ "@premium-cms/emdash": ">=0.35.0"
28
+ },
29
+ "devDependencies": {
30
+ "@premium-cms/plugin-cli": "^0.8.5",
31
+ "@premium-cms/plugin-types": "^0.3.0",
32
+ "@premium-cms/emdash": "^0.35.0",
33
+ "typescript": "^6.0.3",
34
+ "vitest": "^4.1.5"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/MahmoodKhalil57/premium-cms-plugin-cloudflare-email-byo"
42
+ }
39
43
  }