Skip to content

Files & renderer

Two features for file storage and notification HTML rendering.

Status: ✅ Stable

What: S3-compatible file-storage backend. Targets AWS S3, Cloudflare R2, MinIO, Backblaze B2, anything that speaks the S3 API. Implements FileStorageProvider (write / writeStream / read / readStream / getSignedUrl / delete).

How it works: createS3Provider({ region, bucket, accessKeyId, secretAccessKey, endpoint?, forcePathStyle? }) builds the provider. forcePathStyle (or a custom endpoint) matters for MinIO + R2. Wire it into the files / file-foundation stack as your storage provider.

Env helper: createS3ProviderFromEnv() reads the S3_ prefix by default (S3_BUCKET, S3_REGION, S3_ACCESS_KEY, S3_SECRET_KEY, optional S3_ENDPOINT, S3_FORCE_PATH_STYLE). Pass another prefix (e.g. "MINIO_") when needed.

Example:

import {
createS3Provider,
createS3ProviderFromEnv,
} from "@cosmicdrift/kumiko-bundled-features/files-provider-s3";
const fileProvider = createS3Provider({
region: "eu-central-1",
bucket: "kumiko-app-files",
accessKeyId: process.env["S3_ACCESS_KEY"]!,
secretAccessKey: process.env["S3_SECRET_KEY"]!,
});
// or: const fileProvider = createS3ProviderFromEnv(); // reads S3_* env
// Presigned GET URL (download). Upload path uses write/writeStream or the
// files feature upload routes, there is no uploadUrl helper.
const url = await fileProvider.getSignedUrl!(
`tenant-${tenantId}/${filename}`,
600,
{ contentDisposition: `attachment; filename="${filename}"` },
);

Status: ✅ Stable

What: Default notification HTML renderer for delivery. No React, no SSR pipeline, structured notification templates (header / sections / button / footer) → HTML.

How it works: Register createRendererSimpleFeature() next to delivery. You do not declare templates with r.template, use r.notification(...) (or ctx.notify) and put per-channel shapes in templates.email / templates.inApp / templates.push. The simple renderer turns the email template object into HTML.

When not: Marketing mails with complex layouts → produce HTML elsewhere (MJML, etc.) and send via a custom channel/transport. renderer-simple is for transactional notifications the app sends itself.

Example:

import { createDeliveryFeature } from "@cosmicdrift/kumiko-bundled-features/delivery";
import { createRendererSimpleFeature } from "@cosmicdrift/kumiko-bundled-features/renderer-simple";
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
features: [createRendererSimpleFeature(), createDeliveryFeature(), /* ... */];
defineFeature("incidents", (r) => {
r.requires("delivery");
const created = r.writeHandler(/* ... */);
r.notification("incident-created", {
trigger: { on: created },
recipient: (result) => result.data["assigneeId"] as string,
data: (result) => ({
title: result.data["title"] as string,
body: (result.data["body"] as string) ?? "",
}),
templates: {
email: (data) => ({
subject: `Incident: ${data["title"]}`,
header: String(data["title"]),
sections: [{ text: String(data["body"]) }],
}),
inApp: (data) => ({
title: String(data["title"]),
body: String(data["body"]),
}),
},
});
});