From c53e469a56fea88746b722a08ef59377380fd107 Mon Sep 17 00:00:00 2001 From: debojyoti452 Date: Thu, 28 May 2026 12:48:36 +0530 Subject: [PATCH 1/2] feat: add send-email-with-keplars Node.js function template --- node/send-email-with-keplars/.gitignore | 2 + node/send-email-with-keplars/README.md | 82 +++++++++++++++++++++++ node/send-email-with-keplars/package.json | 13 ++++ node/send-email-with-keplars/src/main.js | 72 ++++++++++++++++++++ node/send-email-with-keplars/src/utils.js | 11 +++ 5 files changed, 180 insertions(+) create mode 100644 node/send-email-with-keplars/.gitignore create mode 100644 node/send-email-with-keplars/README.md create mode 100644 node/send-email-with-keplars/package.json create mode 100644 node/send-email-with-keplars/src/main.js create mode 100644 node/send-email-with-keplars/src/utils.js diff --git a/node/send-email-with-keplars/.gitignore b/node/send-email-with-keplars/.gitignore new file mode 100644 index 00000000..713d5006 --- /dev/null +++ b/node/send-email-with-keplars/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +.env diff --git a/node/send-email-with-keplars/README.md b/node/send-email-with-keplars/README.md new file mode 100644 index 00000000..f7e1aa82 --- /dev/null +++ b/node/send-email-with-keplars/README.md @@ -0,0 +1,82 @@ +# 📧 Send Email with Keplars + +Send transactional emails from your Appwrite Function using the [Keplars](https://keplars.com) priority-queue API — instant, high, async, or bulk delivery. + +## 🧰 Usage + +### GET / + +Returns a 405 Method Not Allowed. + +### POST / + +Send an email. + +**Request body:** + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `to` | string \| string[] | Yes | Recipient email address(es) | +| `from` | string | Yes | Sender address (must be verified in Keplars) | +| `subject` | string | Yes | Email subject line | +| `body` | string | No | Email body (HTML or plain text) | +| `from_name` | string | No | Sender display name | +| `template_id` | string | No | Keplars template ID | +| `params` | object | No | Template variables | + +**Success response:** + +```json +{ + "ok": true, + "data": { + "id": "msg_...", + "status": "queued" + } +} +``` + +**Error response:** + +```json +{ + "ok": false, + "error": "Missing required fields: to, from, subject" +} +``` + +## ⚙️ Configuration + +| Variable | Description | Required | +| --- | --- | --- | +| `KEPLARS_API_KEY` | Your Keplars API key (`kms_...`) | Yes | +| `KEPLARS_PRIORITY` | Delivery priority: `instant`, `high`, `async`, `bulk` | No (default: `high`) | + +## 🚀 Deployment + +1. Create a new Appwrite Function +2. Add the environment variables above +3. Deploy the function + +**Example request:** + +```bash +curl -X POST https://[REGION].appwrite.io/v1/functions/[FUNCTION_ID]/executions \ + -H "X-Appwrite-Project: [PROJECT_ID]" \ + -H "Content-Type: application/json" \ + -d '{ + "to": "user@example.com", + "from": "hello@yourdomain.com", + "subject": "Welcome!", + "body": "

Welcome!

Thanks for signing up.

" + }' +``` + +## 📦 Priority Reference + +| Priority | Delivery | Use case | +| --- | --- | --- | +| `instant` | 0-5 seconds | OTP, auth codes | +| `high` | 0-30 seconds | Password reset, alerts | +| `async` | 0-5 minutes | Welcome emails, notifications | +| `bulk` | Background | Newsletters, campaigns | diff --git a/node/send-email-with-keplars/package.json b/node/send-email-with-keplars/package.json new file mode 100644 index 00000000..944dfb86 --- /dev/null +++ b/node/send-email-with-keplars/package.json @@ -0,0 +1,13 @@ +{ + "name": "send-email-with-keplars", + "version": "1.0.0", + "description": "Send transactional emails using the Keplars priority-queue API.", + "main": "src/main.js", + "type": "module", + "scripts": { + "format": "prettier --write ." + }, + "devDependencies": { + "prettier": "^3.2.5" + } +} diff --git a/node/send-email-with-keplars/src/main.js b/node/send-email-with-keplars/src/main.js new file mode 100644 index 00000000..f68095b8 --- /dev/null +++ b/node/send-email-with-keplars/src/main.js @@ -0,0 +1,72 @@ +import { throwIfMissing } from './utils.js'; + +const PRIORITY_ENDPOINTS = { + instant: 'https://api.keplars.com/api/v1/send-email/instant', + high: 'https://api.keplars.com/api/v1/send-email/high', + async: 'https://api.keplars.com/api/v1/send-email/async', + bulk: 'https://api.keplars.com/api/v1/send-email/bulk', +}; + +export default async ({ req, res, log, error }) => { + throwIfMissing(process.env, ['KEPLARS_API_KEY']); + + if (req.method !== 'POST') { + return res.json({ ok: false, error: 'Method not allowed' }, 405); + } + + const { + to, + from, + from_name, + subject, + body, + template_id, + params, + } = req.body ?? {}; + + if (!to || !from || !subject) { + return res.json( + { ok: false, error: 'Missing required fields: to, from, subject' }, + 400 + ); + } + + const priority = process.env.KEPLARS_PRIORITY ?? 'high'; + const endpoint = PRIORITY_ENDPOINTS[priority] ?? PRIORITY_ENDPOINTS.high; + + const payload = { + to: Array.isArray(to) ? to : [to], + from, + subject, + }; + + if (from_name) payload.from_name = from_name; + if (body) payload.body = body; + if (template_id) payload.template_id = template_id; + if (params && typeof params === 'object') payload.params = params; + + try { + const response = await fetch(endpoint, { + method: 'POST', + headers: { + Authorization: `Bearer ${process.env.KEPLARS_API_KEY}`, + 'Content-Type': 'application/json', + 'User-Agent': 'keplars-appwrite/1.0.0', + }, + body: JSON.stringify(payload), + }); + + const data = await response.json(); + + if (!response.ok) { + error(`Keplars API error ${response.status}: ${JSON.stringify(data)}`); + return res.json({ ok: false, error: data }, response.status); + } + + log(`Email sent to ${Array.isArray(to) ? to.join(', ') : to}`); + return res.json({ ok: true, data }); + } catch (err) { + error(err.message); + return res.json({ ok: false, error: 'Failed to send email' }, 500); + } +}; diff --git a/node/send-email-with-keplars/src/utils.js b/node/send-email-with-keplars/src/utils.js new file mode 100644 index 00000000..c26f9078 --- /dev/null +++ b/node/send-email-with-keplars/src/utils.js @@ -0,0 +1,11 @@ +/** + * @param {Record} obj + * @param {string[]} keys + * @throws {Error} + */ +export function throwIfMissing(obj, keys) { + const missing = keys.filter((key) => !obj[key]); + if (missing.length > 0) { + throw new Error(`Missing required environment variables: ${missing.join(', ')}`); + } +} From 2b58eb9c342d24c98698b8348a0c7ec3496d27f8 Mon Sep 17 00:00:00 2001 From: debojyoti452 Date: Thu, 28 May 2026 13:03:42 +0530 Subject: [PATCH 2/2] fix: use static async endpoint, add env.d.ts, consistent error types, body/template_id guard, clean README --- node/send-email-with-keplars/README.md | 27 +++++----------- node/send-email-with-keplars/env.d.ts | 9 ++++++ node/send-email-with-keplars/src/main.js | 39 ++++++++++++------------ 3 files changed, 35 insertions(+), 40 deletions(-) create mode 100644 node/send-email-with-keplars/env.d.ts diff --git a/node/send-email-with-keplars/README.md b/node/send-email-with-keplars/README.md index f7e1aa82..f8a406d0 100644 --- a/node/send-email-with-keplars/README.md +++ b/node/send-email-with-keplars/README.md @@ -1,12 +1,8 @@ -# 📧 Send Email with Keplars +# Send Email with Keplars -Send transactional emails from your Appwrite Function using the [Keplars](https://keplars.com) priority-queue API — instant, high, async, or bulk delivery. +Send transactional emails from your Appwrite Function using the [Keplars](https://keplars.com) priority-queue API with instant, high, async, or bulk delivery. -## 🧰 Usage - -### GET / - -Returns a 405 Method Not Allowed. +## Usage ### POST / @@ -19,9 +15,9 @@ Send an email. | `to` | string \| string[] | Yes | Recipient email address(es) | | `from` | string | Yes | Sender address (must be verified in Keplars) | | `subject` | string | Yes | Email subject line | -| `body` | string | No | Email body (HTML or plain text) | +| `body` | string | No | Email body (HTML or plain text). Required if `template_id` is not set. | | `from_name` | string | No | Sender display name | -| `template_id` | string | No | Keplars template ID | +| `template_id` | string | No | Keplars template ID. Required if `body` is not set. | | `params` | object | No | Template variables | **Success response:** @@ -45,14 +41,13 @@ Send an email. } ``` -## ⚙️ Configuration +## Configuration | Variable | Description | Required | | --- | --- | --- | | `KEPLARS_API_KEY` | Your Keplars API key (`kms_...`) | Yes | -| `KEPLARS_PRIORITY` | Delivery priority: `instant`, `high`, `async`, `bulk` | No (default: `high`) | -## 🚀 Deployment +## Deployment 1. Create a new Appwrite Function 2. Add the environment variables above @@ -72,11 +67,3 @@ curl -X POST https://[REGION].appwrite.io/v1/functions/[FUNCTION_ID]/executions }' ``` -## 📦 Priority Reference - -| Priority | Delivery | Use case | -| --- | --- | --- | -| `instant` | 0-5 seconds | OTP, auth codes | -| `high` | 0-30 seconds | Password reset, alerts | -| `async` | 0-5 minutes | Welcome emails, notifications | -| `bulk` | Background | Newsletters, campaigns | diff --git a/node/send-email-with-keplars/env.d.ts b/node/send-email-with-keplars/env.d.ts new file mode 100644 index 00000000..6b52a94c --- /dev/null +++ b/node/send-email-with-keplars/env.d.ts @@ -0,0 +1,9 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + KEPLARS_API_KEY: string; + } + } +} + +export {}; diff --git a/node/send-email-with-keplars/src/main.js b/node/send-email-with-keplars/src/main.js index f68095b8..17ff6cb0 100644 --- a/node/send-email-with-keplars/src/main.js +++ b/node/send-email-with-keplars/src/main.js @@ -1,11 +1,6 @@ import { throwIfMissing } from './utils.js'; -const PRIORITY_ENDPOINTS = { - instant: 'https://api.keplars.com/api/v1/send-email/instant', - high: 'https://api.keplars.com/api/v1/send-email/high', - async: 'https://api.keplars.com/api/v1/send-email/async', - bulk: 'https://api.keplars.com/api/v1/send-email/bulk', -}; +const ENDPOINT = 'https://api.keplars.com/api/v1/send-email/async'; export default async ({ req, res, log, error }) => { throwIfMissing(process.env, ['KEPLARS_API_KEY']); @@ -14,15 +9,8 @@ export default async ({ req, res, log, error }) => { return res.json({ ok: false, error: 'Method not allowed' }, 405); } - const { - to, - from, - from_name, - subject, - body, - template_id, - params, - } = req.body ?? {}; + const { to, from, from_name, subject, body, template_id, params } = + req.body ?? {}; if (!to || !from || !subject) { return res.json( @@ -31,8 +19,15 @@ export default async ({ req, res, log, error }) => { ); } - const priority = process.env.KEPLARS_PRIORITY ?? 'high'; - const endpoint = PRIORITY_ENDPOINTS[priority] ?? PRIORITY_ENDPOINTS.high; + if (!body && !template_id) { + return res.json( + { + ok: false, + error: 'Either body or template_id must be provided', + }, + 400 + ); + } const payload = { to: Array.isArray(to) ? to : [to], @@ -46,7 +41,7 @@ export default async ({ req, res, log, error }) => { if (params && typeof params === 'object') payload.params = params; try { - const response = await fetch(endpoint, { + const response = await fetch(ENDPOINT, { method: 'POST', headers: { Authorization: `Bearer ${process.env.KEPLARS_API_KEY}`, @@ -59,8 +54,12 @@ export default async ({ req, res, log, error }) => { const data = await response.json(); if (!response.ok) { - error(`Keplars API error ${response.status}: ${JSON.stringify(data)}`); - return res.json({ ok: false, error: data }, response.status); + const message = + typeof data?.message === 'string' + ? data.message + : `Keplars API error: ${response.status}`; + error(message); + return res.json({ ok: false, error: message }, response.status); } log(`Email sent to ${Array.isArray(to) ? to.join(', ') : to}`);