QuickMail v0.7.4
QuickMail v0.7.4 Release Notes
Download
Two options are available for v0.7.4:
| Download | When to use |
|---|---|
quickmail-v0.7.4-setup.exe — Windows installer |
Recommended for most users. Installs per-user with no elevation required, checks for the WebView2 Runtime, and registers an uninstaller. |
QuickMail.exe — standalone portable executable |
No installation required. Copy it anywhere and run. |
Both downloads include the .NET 8 runtime — you do not need to install .NET separately.
New Features
Gmail Support via Google Sign-In
You can now add a Gmail account using your Google account instead of an app password.
Adding a Gmail account: Open the account manager (Settings → Accounts) and choose Add Account. Enter an account name, a sender display name, and your Gmail address in the Email / Username field. When QuickMail sees a Gmail address, it automatically selects Google authentication for the account — you do not need to set the authentication type yourself. Move to the Sign in with Google button and activate it; your default browser opens to a Google sign-in page. Complete the Google sign-in, grant QuickMail permission to read and send mail, then close the browser window. Back in QuickMail, activate the Add Account button to add the account, then close the account manager. Gmail's IMAP and SMTP server settings are filled in automatically; you do not need to enter them.
You may see a message that no password was saved for the account. This is expected when you use Google authentication — there is no password to save, because Gmail signs in through your Google account rather than a stored password.
The Google sign-in itself is stored securely in Windows Credential Manager, the same store QuickMail uses for other accounts. The access token refreshes automatically in the background; you will not be prompted to sign in again unless you revoke access from your Google account settings.
Removing a Gmail account: Open Settings → Accounts, select the account, and press Delete. This removes the account from QuickMail and clears the stored credential from Windows Credential Manager.
Important: Google's "unverified app" warning. When you sign in with your Google account, Google displays a warning that QuickMail is an unverified application and asks you to confirm you want to proceed. This is normal and expected — QuickMail has not undergone Google's App Verification process, which can take several weeks and potentially require expensive third-party security assessment. You can safely ignore this warning and click Advanced → Go to QuickMail (unsafe) to continue signing in.
If you prefer to avoid this warning, Gmail accounts also support app-specific passwords: generate one from your Google Account security settings and use it with QuickMail's standard Password authentication method instead of Google OAuth.
Grab Addresses: Add to Group
When grabbing addresses from an open message (Ctrl+Shift+G), you can now add the selected contacts directly to an address book group in the same step.
How it works:
- Press
Ctrl+Shift+Gto open the Add to Address Book window. - The message's addresses appear as checkboxes, all checked by default. Uncheck any you do not want.
- Check Add to group to show the group options.
- Choose an existing group from the Group combo box, or choose Create new group and type a name in the New group name field.
- Press Save (or Enter). Each checked contact is added to your address book and, if you chose a group, added to that group.
If you type a new group name and leave it blank when Save is pressed, QuickMail announces "Enter a name for the new group" and keeps the window open. No contacts are saved until a valid group name is provided or you remove the name and save without the group option checked.
If you type a new group name that already exists, QuickMail does not create a second group with that name. Instead it tells you the group already exists and keeps the window open so you can enter a different name, or pick the existing group from the list. This prevents two groups with the same name — which could otherwise lead to sending to the wrong people.
Unique Group Names
Address book group names are now required to be unique, regardless of letter case ("Team" and "team" are treated as the same name). This applies everywhere groups are created or renamed — the Grab Addresses window, the Group Manager, and the Address Book. If a name is already in use, QuickMail tells you and leaves your text in place so you can choose a different name. Existing groups are never merged.
Grab Addresses: Tab Navigation Fix
Previously, Tab cycled through every address checkbox in the list, requiring many Tab presses to reach the Save or Cancel button. Tab now stops on the address list once — pressing Tab again exits the list and moves to the Add to group checkbox, then to the group controls, then to Save and Cancel. Arrow keys still move between individual address checkboxes within the list.
Bug Fixes
- Resource leak in compose windows. Each compose window left a timer handle open when closed because
ComposeViewModel's auto-save cancellation token source was never cancelled or disposed. The handle is now cancelled and disposed when the compose window closes. - SemaphoreSlim handles leaked on app exit.
ContactServiceandTemplateServiceeach hold aSemaphoreSlimwhose underlyingWaitHandlewas never released. Both services are now disposed on application exit.
Thank You to Contributors
Thank you to everyone who has contributed to QuickMail through code, bug reports, feature suggestions, and other feedback. Your contributions make the project better for everyone.
Internal
Google OAuth
AuthType.OAuth2Googleenum value;FeatureFlag.GoogleAuth(default on).IGoogleOAuthService/GoogleOAuthService: browser sign-in viaLocalServerCodeReceiver(Google.Apis.Auth); access token refreshed automatically; refresh token persisted in Windows Credential Manager viaCredentialService.SaveSecret/GetSecret/DeleteSecret.OAuthRouter: routesIOAuthServicecalls to MSAL orGoogleOAuthServicebyAuthType, soImapMailServiceandSmtpServiceneed only a one-line guard change.ConfigModel/ConfigService:[google]section readsGoogleClientId/GoogleClientSecretfromconfig.ini; credentials are gitignored and supplied via GitHub Actions secrets in CI.AddAccountViewModel: auto-fillsimap.gmail.com:993 (SSL)andsmtp.gmail.com:587whenOAuth2Googleis selected, matching the Microsoft auto-fill pattern.AccountEditorViewModel:SignInGoogleCommand(async, opens browser flow),IsGoogleOAuthcomputed property, updatedAuthTypeIndex(0 = Password, 1 = Microsoft, 2 = Google).AddAccountDialog/AccountManagerDialog: Google OAuth combo item and Sign in with Google button, both gated byShowGoogleAuthOption/FeatureFlag.GoogleAuth.AccountPropertiesBuilder,MainViewModel,AccountManagerViewModel: updated to handleOAuth2Googlein auth display and sign-out paths.docs/privacy.html: privacy policy page hosted on GitHub Pages athttps://kellylford.github.io/QuickMail/privacy.html; required for Google OAuth verification.StubGoogleOAuthServiceadded to test project.
Grab Addresses
GrabAddressesDialogshown modelessly (.Show()) instead of via.ShowDialog(). Modal nesting over a WebView2 host + editable text field + screen reader caused a hard UI-thread deadlock on focus. Modeless windows have noDialogResult, so Escape and Cancel are wired toClose()explicitly; Escape is guarded against the group combo's open dropdown.Save_Clickservice calls wrapped in try/catch: failures are logged and announced (Resultcategory); the dialog stays open for retry rather than crashing on an unhandledasync voidexception.UpsertContactAsync: setcontact.Idfor existing contacts soAddMemberAsyncreceives a valid ID.GrabAddressesDialogTestsrewritten: 9 tests covering focus on open, Tab exit from address list, static group controls, modeless Escape-close, Save with new group, Save without group, empty-name validation, and duplicate-name rejection (case-insensitive).
Group Name Uniqueness
ContactService.CreateGroupAsyncandRenameGroupAsyncreject a name that already exists (case-insensitive,OrdinalIgnoreCase) by throwing the new publicDuplicateGroupNameException. The check runs inside the load lock so there is no check-then-create race. No merging is performed.- All three group entry points catch it and announce a
Result-category message, leaving the input in place:GrabAddressesDialog.Save_Click,GroupManagerViewModel(create + rename), andAddressBookViewModel(create + rename).
IDisposable
ComposeViewModel.Dispose(): calls_autoSaveCts.Cancel()beforeDispose()(preventsObjectDisposedExceptionin in-flight autosave tasks per the IDisposable rules inCLAUDE.md).ComposeWindow.OnClosed: calls_vm.Dispose().App.OnExit:ContactServiceandTemplateServicepromoted to private fields and disposed.