Skip to content

Files & renderer

Two features for file uploads and email templates.

files-provider-s3

Status: ✅ Stable

What: S3-compatible file-storage backend. Targets AWS S3, Cloudflare R2, MinIO, Backblaze B2 — anything that speaks the S3 API. Ships upload URLs (presigned), read URLs (direct or presigned), delete operations.

How it works: createS3Provider({ region, bucket, accessKey, secretKey, endpoint?, forcePathStyle? }) builds a provider that the framework file layer uses. forcePathStyle matters for MinIO + R2 (they don’t support virtual-host style). Per-tenant buckets are possible via bucket: (tenantId) => \tenant-${tenantId}-files“ — gives natural tenant isolation at the S3 level.

Env helper: createS3ProviderFromEnv() reads the standard S3 env vars (AWS_ACCESS_KEY_ID etc.) — fastest setup for single-tenant apps, no code path change when you migrate later.

Example:

import { createS3Provider } from "@kumiko/bundled-features/files-provider-s3";
const fileProvider = createS3Provider({
region: "eu-central-1",
bucket: "kumiko-app-files",
accessKey: process.env["AWS_ACCESS_KEY_ID"]!,
secretKey: process.env["AWS_SECRET_ACCESS_KEY"]!,
});
// In a handler: presigned upload URL for the browser
r.writeHandler({
qn: "files:upload-url",
handler: async (ctx, { filename }) => {
return fileProvider.uploadUrl({
key: `tenant-${ctx.tenantId}/${filename}`,
contentType: "application/octet-stream",
expiresIn: 600, // seconds
});
},
});

renderer-simple

Status: ✅ Stable

What: Minimal HTML renderer for email templates. No React, no SSR pipeline — just a template string with {{ placeholders }} and a list of helpers (i18n, date format, URL builder).

How it works: an email template is a function (data, helpers) => { html, text }. renderer-simple is registered as a renderer implementation in delivery and handles layout inheritance (header + footer reusable) and plain-text fallback (text variant by stripping HTML).

When not: marketing mails with complex layouts → render with an external tool like MJML or unlayer and pipe the output through unchanged. renderer-simple is for transactional mails (reset link, incident update, welcome) — what the app sends itself, not what marketing sends.

Example:

import { createRendererSimpleFeature } from "@kumiko/bundled-features/renderer-simple";
features: [createRendererSimpleFeature(), createDeliveryFeature(), /* ... */];
// Define a template (in your feature)
r.template({
id: "incident-created",
renderer: "simple",
render: ({ data }) => ({
subject: `[${data.tenant}] Incident: ${data.title}`,
html: `<h1>${data.title}</h1><p>${data.body}</p>`,
text: `${data.title}\n\n${data.body}`,
}),
});

See also