@kiwiton-tech/email-sdk
v0.1.1
Published
Tiny client SDK for any website to submit contact forms and trigger transactional emails through the KiwiTon Tech Email Service.
Maintainers
Readme
@kiwiton-tech/email-sdk
Tiny client SDK for any website to submit contact forms (and other transactional emails) through the KiwiTon Tech Email Service.
- Zero runtime dependencies.
- Works in Node 18+, Next.js, Cloudflare Workers, Bun, modern browsers (server-side use recommended).
- First-class Next.js App Router Route Handler.
- First-class React hook for client-side forms.
Install
npm install @kiwiton-tech/email-sdk
# or
pnpm add @kiwiton-tech/email-sdkGet your credentials
Each KiwiTon Tech client site is issued:
- a
siteKey— short slug likeacme-corp, identifies the site to the email service - an
apiKey— secret, scoped to thatsiteKey. Server-only. Never put this in a browser bundle.
Recipients (the actual contact-form destination emails) are configured server-side per siteKey. The client never specifies recipients, which prevents open-relay abuse.
Quick start — Next.js (App Router)
app/api/contact/route.ts:
import { createContactRoute } from '@kiwiton-tech/email-sdk/next';
export const POST = createContactRoute({
siteKey: process.env.KIWITON_SITE_KEY!,
apiKey: process.env.KIWITON_API_KEY!,
});app/contact/page.tsx:
'use client';
import { useKiwiTonContactForm } from '@kiwiton-tech/email-sdk/react';
export default function ContactPage() {
const { submit, loading, error, result } = useKiwiTonContactForm();
if (result?.success) return <p>Thanks — we'll be in touch.</p>;
return (
<form
onSubmit={async (e) => {
e.preventDefault();
const fd = new FormData(e.currentTarget);
await submit({
name: String(fd.get('name')),
email: String(fd.get('email')),
subject: String(fd.get('subject')),
message: String(fd.get('message')),
});
}}
>
<input name="name" required />
<input name="email" type="email" required />
<input name="subject" required />
<textarea name="message" required />
<button disabled={loading}>{loading ? 'Sending…' : 'Send'}</button>
{error && <p role="alert">{error}</p>}
</form>
);
}Quick start — direct server-side
import { KiwiTonEmail } from '@kiwiton-tech/email-sdk';
const ke = new KiwiTonEmail({
siteKey: process.env.KIWITON_SITE_KEY!,
apiKey: process.env.KIWITON_API_KEY!,
});
const result = await ke.contact.send({
name: 'Jane Doe',
email: '[email protected]',
subject: 'Hello',
message: 'I would like to learn more.',
});
// { success: true, message: '...', emailId: '...' }API
new KiwiTonEmail(options)
| Option | Type | Required | Default |
|--------------|----------|----------|----------------------------------|
| siteKey | string | yes | — |
| apiKey | string | yes | — |
| baseUrl | string | no | https://api.kiwiton-tech.com |
| timeoutMs | number | no | 10000 |
| fetch | fetch | no | globalThis.fetch |
| headers | object | no | {} |
client.contact.send(input)
{
name: string; // required, 1–200 chars
email: string; // required, valid email
subject: string; // required, 1–500 chars
message: string; // required, 1–10,000 chars
hp?: string; // honeypot — leave empty
siteKey?: string; // override (rare)
meta?: Record<string, string | number | boolean | null>;
}Returns { success, message, emailId? }. Throws KiwiTonClientError on failure (network, validation, rate limit, etc).
client.health()
Probes the gateway. Returns { status }.
createContactRoute(options) (from /next)
Returns a Next.js App Router POST handler. Same options as KiwiTonEmail, plus:
onSuccess?(input)— async hook called after a successful submissionshouldAccept?(req)— returnfalseto short-circuit with a 429 (e.g. your own rate limiter)
useKiwiTonContactForm(options?) (from /react)
Returns { submit, loading, error, result, reset }. Submits to your own server endpoint (default /api/contact), so the API key never leaves the server.
Errors
import { KiwiTonClientError } from '@kiwiton-tech/email-sdk';
try {
await ke.contact.send(input);
} catch (e) {
if (e instanceof KiwiTonClientError) {
console.error(e.code, e.status, e.message, e.requestId);
}
}Codes: BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, CONFLICT, RATE_LIMITED, UPSTREAM_ERROR, INTERNAL_ERROR, NETWORK_ERROR.
License
MIT © KiwiTon Tech
