amber-backend/pb_migrations/1794000000_czech_mail_templates.js
Claude fb59f504fe Czech account emails, and a reset that finishes on our own domain
The reset email works — confirmed by a delivered message, after every attempt on
record had failed at the lookup and the mail path had never once been exercised.
What the delivered message showed was a different problem: it was in English,
and its button pointed at `pb.petruzalekr.cz/_/#/auth/confirm-password-reset/…`,
which is PocketBase's admin console. Every other surface of this app is Czech on
purpose, because the people using it are the owner's parents and his children —
and the one moment they are most likely to be stuck is the one moment it started
speaking English and sent them to what reads like a developer tool.

Found by looking at a delivered email rather than at the settings. The templates
were simply PocketBase's defaults, and a default is invisible until somebody
receives one.

The reset link now lands on amber.petruzalekr.cz, which hosts the form: two
fields, in Czech, on the domain people already know. A reset link wins over a
stored session, because somebody arriving with one is trying to fix their
account and dropping them into a signed-in page hides the thing they came to do.
The token is stripped from the address bar once spent — a URL carrying a
credential is one that ends up in a bookmark or a screenshot.

A failed confirm names the likely cause. These links expire, and "something went
wrong" gives somebody who opened yesterday's email no way to know that.

Verification and email-change keep PocketBase's own confirm pages: nobody has
used those, they are not part of the report, and pointing them at a page that
does not exist would be worse than English.

As a migration rather than a click in the admin UI, so restoring the backend
from migrations does not quietly put English back.
2026-09-05 19:04:50 +02:00

83 lines
3.4 KiB
JavaScript

/// <reference path="../pb_data/types.d.ts" />
// The account emails were in English, and pointed at PocketBase's admin console.
//
// **Why it matters here.** Every other surface of this app is Czech, on purpose:
// the people using it are the owner's parents and his children. The one moment
// they are most likely to be stuck — locked out, asking for a new password — was
// the one moment the product started speaking English and sent them to
// `pb.petruzalekr.cz/_/#/auth/confirm-password-reset/…`, which is the admin UI's
// own page and reads to anybody's parent like a developer tool opened by
// mistake.
//
// Found by looking at a delivered message rather than at the settings: the
// templates were simply PocketBase's defaults, and defaults are invisible until
// somebody receives one.
//
// The links now point at amber.petruzalekr.cz, which hosts the confirm form in
// Czech (see pb_public/index.html, `?reset=`). Verification and email-change
// keep PocketBase's own confirm pages: nobody has hit those yet, they are not
// part of the reported problem, and pointing them somewhere that does not exist
// would be worse than English.
const RESET_SUBJECT = "Nové heslo k Amberu";
const RESET_BODY = `<p>Ahoj,</p>
<p>Někdo (snad ty) požádal o nové heslo k Amberu. Nastavíš si ho tímhle odkazem:</p>
<p>
<a class="btn" href="https://amber.petruzalekr.cz/?reset={TOKEN}" target="_blank" rel="noopener">Nastavit nové heslo</a>
</p>
<p><i>Pokud jsi o nic nežádal, tenhle e-mail klidně smaž. Nic se nestalo a heslo
zůstává, jaké bylo.</i></p>
<p>Amber</p>`;
const VERIFY_SUBJECT = "Ověření e-mailu pro Amber";
const VERIFY_BODY = `<p>Ahoj,</p>
<p>Potvrď prosím tímhle odkazem, že tenhle e-mail patří tobě:</p>
<p>
<a class="btn" href="{APP_URL}/_/#/auth/confirm-verification/{TOKEN}" target="_blank" rel="noopener">Potvrdit e-mail</a>
</p>
<p>Amber</p>`;
const CHANGE_SUBJECT = "Změna e-mailu u Amberu";
const CHANGE_BODY = `<p>Ahoj,</p>
<p>Potvrď prosím změnu e-mailu u svého účtu:</p>
<p>
<a class="btn" href="{APP_URL}/_/#/auth/confirm-email-change/{TOKEN}" target="_blank" rel="noopener">Potvrdit změnu</a>
</p>
<p><i>Pokud jsi o změnu nežádal, ozvi se Richardovi — někdo se ti hrabe v účtu.</i></p>
<p>Amber</p>`;
// The defaults, so the down migration restores exactly what was there.
const EN = {
reset: {
subject: "Reset your {APP_NAME} password",
body: `<p>Hello,</p>
<p>Click on the button below to reset your password.</p>
<p>
<a class="btn" href="{APP_URL}/_/#/auth/confirm-password-reset/{TOKEN}" target="_blank" rel="noopener">Reset password</a>
</p>
<p><i>If you didn't ask to reset your password, please ignore this email.</i></p>
<p>
Thanks,<br/>
{APP_NAME} team
</p>`,
},
};
migrate(
(app) => {
const users = app.findCollectionByNameOrId("users");
users.resetPasswordTemplate.subject = RESET_SUBJECT;
users.resetPasswordTemplate.body = RESET_BODY;
users.verificationTemplate.subject = VERIFY_SUBJECT;
users.verificationTemplate.body = VERIFY_BODY;
users.confirmEmailChangeTemplate.subject = CHANGE_SUBJECT;
users.confirmEmailChangeTemplate.body = CHANGE_BODY;
app.save(users);
},
(app) => {
const users = app.findCollectionByNameOrId("users");
users.resetPasswordTemplate.subject = EN.reset.subject;
users.resetPasswordTemplate.body = EN.reset.body;
app.save(users);
},
);