Skip to content

Configuring external services

FeedLog can use four optional external services: OAuth sign-in, transactional email, file storage, and a model endpoint. The instance runs without them, but related features may be disabled or use fallback behavior without producing a startup error. Each section explains what to configure, what happens when the service is not configured, and how to identify common failures. The full variable list is in the repository: docs/configuration.md.

Login providers

Google and GitHub are configured independently. A provider is enabled only when both variables in its pair are present:

bash
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=

The sign-in dialog hides providers that are not fully configured. If only one variable in a pair is present, its button does not appear.

If you skip it: email and password sign-in remains available by default.

Email and password sign-in is enabled automatically only when no OAuth provider is configured. If you add Google or GitHub to an existing instance, the password form disappears after the next restart. Set AUTH_EMAIL_ENABLED=true to keep both sign-in methods available.

Callback URLs

Register the callback with the provider exactly as FeedLog will send it:

https://feedback.yourdomain.com/api/auth/callback/google
https://feedback.yourdomain.com/api/auth/callback/github

The scheme, host, port, and path must all match. Use the public origin through which users access FeedLog. A GitHub OAuth app accepts one callback URL, so a staging environment requires a separate app.

Behind a reverse proxy

By default, FeedLog infers its public origin from the request's Host header and uses X-Forwarded-Proto for the scheme when that header is present. X-Forwarded-Host is not consulted. The origin inferred from the first request after a restart is cached for the lifetime of the process.

This works when the proxy preserves the original host, as Caddy and Traefik do by default. A plain nginx proxy_pass replaces Host with the upstream address. FeedLog will then use that internal address in absolute URLs, including the OAuth redirect_uri, password reset and verification links, and links in notification emails. If a health check reaches the container directly before any public request, FeedLog may cache the container's internal origin.

Preserve the original host at the proxy (proxy_set_header Host $host;), or set the public URL explicitly:

bash
BETTER_AUTH_URL=https://feedback.yourdomain.com

Do not include a trailing slash. Setting this value explicitly is recommended for deployments behind a reverse proxy.

Common errors

redirect_uri_mismatch from Google, or "The redirect_uri is not associated with this application" from GitHub — the error page's URL contains the redirect_uri FeedLog sent. Register it verbatim. If it names an internal host or starts with http, fix the proxy, not the OAuth app.

Sign-in completes and lands on the wrong domain — FeedLog inferred the wrong public origin. Set BETTER_AUTH_URL.

The Google or GitHub button isn't there — only one variable in the provider's pair is set.

Password sign-in disappeared after adding OAuth — this is the default when an OAuth provider is configured. Set AUTH_EMAIL_ENABLED=true.

The app won't start after changing AUTH_EMAIL_ENABLED — startup validation fails if email sign-in is disabled and no OAuth provider is configured. An empty value counts as off, not as unset.

Transactional email

bash
RESEND_API_KEY=
EMAIL_FROM=[email protected]
EMAIL_PROVIDER=resend   # optional — resend is the only provider today

Resend is currently the only implemented provider. EMAIL_PROVIDER selects a registered provider by name, so RESEND_API_KEY is sufficient to register Resend.

Set EMAIL_FROM to an address on a domain you have verified with Resend. Without it, mail is sent from Resend's shared onboarding address, which may only deliver to the account owner.

FeedLog sends these five types of email:

MailSent when
Email verificationA new account is created
Password resetSomeone asks for one
Password addedA password is set on an account that signed up with OAuth
Workspace invitationAn admin invites a member
NotificationFeedback someone follows changes status, or gets an official reply

Notifications go to people following the feedback: its author and anyone who upvoted it. Workspace owners, managers, and the person who caused the event are excluded. Status notifications are sent only when an admin chooses to announce the change; changing a status without announcing it sends no email. FeedLog does not send email for new feedback or new comments.

If you skip it: verification, password reset, and invitation email is not sent. Notifications are written to the log instead of being sent to recipients ([DEV EMAIL] to=… subject=…, followed by the link). No error is returned. Users who cannot sign in cannot recover access through password reset, and admins must copy invitation links manually from the Members page.

AUTH_EMAIL_VERIFY controls whether an email address must be verified before sign-in. When unset, verification is enabled if RESEND_API_KEY is set. Use true or false to override that default.

Common errors

Mail reaches you and nobody elseEMAIL_FROM isn't set, so the shared Resend onboarding sender is in use.

Nothing arrives and no error is shown — look for Registered email provider: resend in the startup log. If the registered provider is console, RESEND_API_KEY is not available to the process.

Signed up, then couldn't sign inAUTH_EMAIL_VERIFY=true is set without a mail provider. The account exists, but its address cannot be verified because no verification link is sent or written to the log. Password reset is also unavailable.

Links in mail point at the wrong host — FeedLog inferred the wrong public origin from the Host header. Set BETTER_AUTH_URL. Invitation links fall back to http://localhost:3000 when this value is unavailable.

File storage

Feedback and changelog entries support image attachments up to 10 MB each. The storage backend and its configuration depend on the deployment platform; only Docker and Node deployments use the S3_* variables:

PlatformBackendWhat you set
Docker / NodeS3-compatible, or local diskS3_*
VercelVercel BlobConnect a Blob store; the token is injected for you
Cloudflare WorkersR2Bind a bucket as BLOB in wrangler.toml

Three variables are always required, plus one of two others:

bash
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_BUCKET=feedlog
S3_ENDPOINT=https://<account>.r2.cloudflarestorage.com   # non-AWS services
S3_REGION=us-east-1                                      # AWS S3

FeedLog supports S3-compatible services including R2, MinIO, Backblaze, Wasabi, Alibaba OSS, Tencent COS, and Qiniu. Credentials are read at runtime, so restart the application after rotating them; a rebuild is not required.

The bucket does not need to be public. Files are stored under <UPLOAD_PREFIX>/<workspace id>/ and served through FeedLog's /api/files/ route. UPLOAD_PREFIX defaults to uploads and can separate environments that share a bucket. Each file's full path is stored with it, so changing the prefix does not affect existing attachments.

If you skip it on Docker: uploads are written to /app/.data/blob inside the container. The files are removed with the container unless /app/.data is mounted on a volume; the bundled compose.yml includes this mount. Vercel and Workers do not provide a local-disk fallback, so uploads fail without a Blob store or an R2 binding.

Common errors

Uploads work, then vanish after a redeploy — the local-disk fallback is in use and /app/.data isn't on a volume.

S3 is configured but files still go to disk — the provider is registered only when S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, and S3_BUCKET are all present. Missing variables do not produce an error. Confirm that the startup log contains Registered blob provider: s3.

S3 disabled: set S3_REGION (for AWS) or S3_ENDPOINT (for R2/MinIO/OSS) — the three required variables are set but neither of the other two is, and the driver can't build a hostname from that.

Uploads fail against Alibaba OSS — Alibaba OSS may require virtual-hosted style. Keep S3_BUCKET and point S3_ENDPOINT at the bucket host, https://<bucket>.oss-cn-hangzhou.aliyuncs.com.

AI features

bash
OPENAI_API_KEY=
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_TEXT_MODEL=

OPENAI_API_KEY determines whether AI features are enabled. The other two variables have defaults and are not checked independently, so setting only the base URL does not enable these features.

Three features use the model endpoint, with different behavior when the key is not configured:

FeatureWithout a key
Duplicate detection and searchFalls back to trigram text matching
AI changelog draftingButton still there; clicking it returns an error
Widget message triageMessage submission fails

With a key, new feedback is embedded and matched by meaning, so "can't log in with Google" can match "SSO broken". Without a key, search uses PostgreSQL trigram distance over the stored text. It still returns the closest results, but matches wording rather than meaning and can miss duplicates phrased differently. The interface does not indicate which matching method is in use.

The changelog editor continues to show the AI draft button when the key is missing. After a moderator selects a style and starts generation, the dialog shows AI generation is not configured (OPENAI_API_KEY missing).

The feedback widget requires a model endpoint to turn a user's message into structured feedback. Without a key, message submission fails and the end user sees Could not send that. Please try again. Configure the key before enabling the widget.

Feedback created while AI was disabled has no embedding. FeedLog does not create one retroactively, so that feedback is excluded from meaning-based matching until you run the repository's scripts/backfill-search.ts backfill script.

Pointing at a model you run yourself

OPENAI_BASE_URL accepts any OpenAI-compatible endpoint, including a LiteLLM or vLLM gateway, Ollama, or a regional provider. If that endpoint runs inside your network, feedback text sent to the model remains inside the network.

The same base URL serves both chat completions and embeddings, so an Azure URL scoped to one deployment can support only one of those requests. FeedLog requests text-embedding-3-large embeddings with 768 dimensions, and the database column also requires 768 dimensions. If the endpoint cannot return that shape, duplicate detection uses trigram text matching instead. OPENAI_TEXT_MODEL selects only the chat model and does not affect embeddings.

Common errors

"AI generation is not configured" in the changelog editorOPENAI_API_KEY is not available to the process. On Workers it is a secret (wrangler secret put OPENAI_API_KEY), not a [vars] entry.

Older feedback is missing from semantic duplicate suggestions after adding a key — it was created without an embedding. Run the backfill script.

A gateway 404s on one feature and works on the other — the base URL is scoped to a single deployment. Point it at a gateway that routes both /chat/completions and /embeddings.

Widget messages fail with only a generic error on screen — check the server log for the underlying error.

Open-source feedback management. Self-host it or let us run it.