Skip to content

feat: manage masked addresses under Settings > Security, as in the webmail #143 - #144

Closed
wmcparla wants to merge 1 commit into
bulwarkmail:mainfrom
wmcparla:feat/masked-addresses
Closed

wmcparla wants to merge 1 commit into
bulwarkmail:mainfrom
wmcparla:feat/masked-addresses

Conversation

@wmcparla

@wmcparla wmcparla commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Status: now run against a real Stalwart with masked addresses (1.1.0 with an Enterprise licence), see "On a device" below. Stalwart 1.1.1, which brings them to the Community edition, is not released yet; the object and the calls are the same.

The app gets the webmail's masked addresses (#143). On a Stalwart server that offers them, Settings > Security ends with a Masked addresses section: the account's addresses ten to a page, each with Copy and Edit (describe it, disable or enable it, delete it for good after a confirm), and a form to create one with an optional website and description.

It ports the webmail's lib/stalwart/masked-email.ts and components/settings/masked-addresses-section.tsx, and follows them unless noted below.

Changes

  • Calls and checks (src/lib/masked-email.ts): the webmail's manager, kept whole.
    • The list is read with x:MaskedEmail/query (filtered to the account) and /get in batches within maxObjectsInGet, and is either complete or an error: a changed total or query state, a repeated or unasked-for id, a record of another account or a malformed one all fail it.
    • An empty query is followed by an empty get, because the registry's query can succeed where the object itself is refused.
    • Update and delete take only ids the manager has listed. An empty description or website goes out as null, which Stalwart requires.
    • A delete whose answer never came is not sent again (deleteUnconfirmed) until a fresh list has shown whether the address is still there.
  • Transport (src/api/masked-email.ts): the webmail goes through its server-side passthrough; the app asks Stalwart itself with urn:stalwart:jmap in using, as api/account-security.ts does. The manager is bound to the login and account active when it is made (servedLoginOf, loginMovedFrom) and sends nothing once another one is active.
  • The section (MaskedAddressesSection.tsx, shown from AccountSecuritySettings when the Stalwart sections are):
    • Hidden until the first read is answered, and for good when the answer is "not offered": an unknown method, or Stalwart Community's "only available in the Enterprise edition" refusal (webmail#1624). Stalwart has announced masked email for the Community edition from v1.1.1; nothing here needs to change for that.
    • A refused read (a permission the sign-in lacks) shows the webmail's line and a Refresh button.
    • A refused change turns off just that kind of change (create, edit or delete) and keeps the server's reason on screen. Quota and rate limit have their own lines.
    • After a create the page holding the new address is shown, so it is in view wherever the server lists it (Stalwart lists the newest first). The webmail stays on its page.
    • Feedback: each outcome is a toast, and the same line stays under the form, since a toast is gone before a long list is scrolled back.
    • Keyboard: the fields use the settings page's SettingsKeyboardContext, and Done on the keyboard saves or creates.
  • Left out on purpose:
    • the webmail's second capability (urn:inbuxa:jmap:registry, with enabled for enable): the app talks to no Inbuxa registry anywhere else;
    • a Settings search entry: the search index only lists what a page always renders (settings-search.test.ts), and this section is there on some servers only.
  • Strings: the webmail's settings.security.masked_addresses block, copied into each vendored locales/<lang>/common.json at the place the webmail has it. A later sync-locales writes the same text.

Related issues

Closes #143

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactor / code quality improvement
  • Chore / dependency update / CI change

Checklist

  • I have read the Contributing Guide
  • My code follows the project's code style and conventions
  • I have run npm run typecheck and there are no errors (native has no lint script)
  • The build passes (no npm run build in native; an x86_64 release APK, assembleGithubRelease -PallowDebugSignedRelease=true, builds and runs)
  • I have tested my changes locally
  • I have added or updated documentation if needed (none needed)
  • I have updated translations (locales/) if my changes affect user-facing text (the webmail's own translations; npm run i18n:check passes)
  • I have included screenshots or a screen recording for UI changes

Tests: npx vitest run: 7183 passed, 163 skipped (the live Stalwart suites). npx tsc --noEmit and npm run i18n:check are clean. New:

  • src/lib/__tests__/masked-email.test.ts: the webmail's masked-email.test.ts and masked-email-destroy.test.ts against a scripted server (paging, batch size, every malformed or foreign answer, the Enterprise-only refusal, null for empty text, the delete states, another login becoming active).
  • src/api/__tests__/masked-email.test.ts: the capability gate, the request shape, method errors by type, an expired sign-in told from a request with no answer.
  • src/components/settings/__tests__/masked-addresses.test.ts: which line follows which failure, and the page clamp.

On a device (Android 16 emulator, phone):

  • Stalwart 1.1.0 with an Enterprise licence, real build, 18 checks, all pass: create with and without website and description, the form emptied afterwards, copy, describe, disable and enable, delete with Cancel and with confirm, and the account left as it started. With the account's five addresses in place (Stalwart's default quota), a sixth create shows the quota line and keeps the typed text. A create, read, update and delete over JMAP gave the answers the code expects (enable, no accountId on a record, updated: {id: null}, an empty description refused, null accepted).
  • Stand-in build (the same branch with src/api/masked-email.ts answered from memory the way Stalwart's source does, local only), 35 checks, all pass: create with and without website and description, the form emptied afterwards, copy, describe (Save off until the text changes), disable and enable, delete with Cancel and with confirm, ten rows a page with Previous and Next, the page of a new address shown, the last page emptied falling back to the one before, over quota, rate limit, a refused create, update and delete, and an unanswered delete (off until Refresh, which shows the address gone).
  • Stalwart Community 1.1.0 (no masked addresses): the section stays hidden and the rest of Security is unchanged. The server's answer is the Enterprise-only forbidden the code looks for.

Screenshots

The list A row opened Delete

The addresses are the stand-in's.

🤖 Generated with Claude Code

…bmail bulwarkmail#143

Port of the webmail's masked addresses (lib/stalwart/masked-email.ts,
components/settings/masked-addresses-section.tsx). On a Stalwart server
that offers them, Settings > Security lists the account's masked
addresses ten to a page. One can be copied, given a description, turned
off so new mail to it is rejected, or deleted for good after a confirm,
and a new one created with an optional website and description.

The section stays hidden where the server has none (another server, or
Stalwart Community before 1.1.1) and for a sign-in the server gives no
account settings. A delete whose answer never came is not sent again
until the list has been read afresh. The strings are the webmail's
masked_addresses block, in every locale.
@rathlinus

Copy link
Copy Markdown
Member

Thanks for this. Masked addresses landed on main in 19bae84 through the catch-up with the webmail, so I'm closing this one as superseded. Main's section works like yours: hidden when the server doesn't offer masked addresses (including Stalwart's Enterprise-only refusal), with a line when the session may not list them. I took your keyboard handling into ca85c7e, so the create form and the description field stay above the keyboard.

@rathlinus rathlinus closed this Nov 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Masked addresses under Settings > Security, as in the webmail

2 participants