Subject: TTMSFNCCloudGoogleGmail – Persisting OAuth tokens across restarts (C++Builder 13 Win64 Modern, TMS FNC Cloud 1.3.0.0). Need headless refresh for Windows Service
Environment
- RAD Studio / C++Builder 13 Win64 Modern
- TMS FNC Cloud v1.3.0.0
- Component: TTMSFNCCloudGoogleGmail
- Windows 11 x64
- Goal: obtain OAuth consent once in a small desktop app, persist tokens in the Windows Registry, then run a Windows Service (no UI) that can send emails using silent token refresh (no prompts).
What we need
- Exact, supported steps to persist the OAuth tokens so that after closing and reopening the app (and later in a Windows Service), the component can silently refresh and send without showing Google’s consent screen again.
Minimal Repro Project (VCL)
DFM settings (relevant):
- 1×
TTMSFNCCloudGoogleGmailnamedGMail - 1×
TButtonnamedBtnSend - 1×
TMemonamedLog - (Some
TEdits for ClientID/Secret/From/To) GMail.PersistTokens.Location = plRegistryGMail.PersistTokens.Key = 'Software\Cuantic\GMail\'GMail.PersistTokens.Section = 'r@cuantic.es'
(We see values such asACCESSTOKEN,ACCESSTOKENREFRESH,SECRET, etc. written underHKCU\Software\Cuantic\GMail\r@cuantic.es\...)
Note:
AccessTypeisn’t available on our build ofTTMSFNCCloudOAuthAuthentication, so we can’t setatOfflineexplicitly. Please advise on how your component ensuresaccess_type=offline/ refresh token issuance.
Code (stripped to essentials):
// FM_Principal.h (handlers)
void __fastcall FormCreate(TObject *Sender);
void __fastcall Enviar_correo_de_prueba(TObject *Sender);
void __fastcall GMailAuthenticated(TObject *Sender, bool &ATestTokens);
void __fastcall GMailSendMessage(TObject *Sender,
TTMSFNCCloudGoogleGmailMessage * const AMessage,
TTMSFNCCloudBaseRequestResult * const ARequestResult);
// FM_Principal.cpp (relevant pieces)
__fastcall TPRINCIPAL::TPRINCIPAL(TComponent* Owner) : TForm(Owner) {
this->OnCreate = FormCreate; // avoid touching components in ctor
}
void __fastcall TPRINCIPAL::FormCreate(TObject *Sender)
{
try {
if (GMail) {
// PersistTokens already configured in DFM (Registry)
GMail->LoadTokens(); // <-- component-level (works; void)
// Mirror persisted credentials to UI if present
if (GMail->Authentication) {
if (GMail->Authentication->ClientID.IsEmpty())
GMail->Authentication->ClientID = ID_Aplicacion_Google_Cloud->Text;
if (GMail->Authentication->Secret.IsEmpty())
GMail->Authentication->Secret = Secreto_de_aplicacion_de_Google_Cloud->Text;
if (GMail->Authentication->CallBackURL.IsEmpty())
GMail->Authentication->CallBackURL = L"http://localhost:8000";
}
}
Log->Lines->Add(L"[Init] Tokens/credentials loaded.");
} catch (...) { /* avoid AVs at startup */ }
}
void __fastcall TPRINCIPAL::Enviar_correo_de_prueba(TObject *Sender)
{
Log->Lines->Clear();
Log->Lines->Add(L"Starting send...");
// Apply UI creds to Authentication
GMail->Authentication->CallBackURL = L"http://localhost:8000";
GMail->Authentication->ClientID = ID_Aplicacion_Google_Cloud->Text;
GMail->Authentication->Secret = Secreto_de_aplicacion_de_Google_Cloud->Text;
// Load persisted tokens (from prior session)
GMail->LoadTokens();
// Try to ensure a valid access token WITHOUT UI
try {
auto req = GMail->FetchAccessToken(); // returns request object
if (req) req->Await(); // blocks until token is valid
try { GMail->SaveTokens(); } catch (...) {}
Log->Lines->Add(L"Token OK. Sending...");
} catch (const Exception &E) {
Log->Lines->Add(L"FetchAccessToken failed: " + E.Message);
Log->Lines->Add(L"Falling back to Connect()...");
GMail->Connect(); // triggers OAuth (OnAuthenticated will resend)
return;
}
// Build and send message
auto *Msg = new TTMSFNCCloudGoogleGmailMessage();
Msg->From = Cuenta_de_eMail->Text;
Msg->ToRecipients->Add(Usuario_eMail->Text);
Msg->Subject = Asunto_eMail->Text;
Msg->MessageType = mtHTML;
Msg->HTMLBody = Mensaje->Text;
GMail->SendMailMessage(Msg);
}
void __fastcall TPRINCIPAL::GMailAuthenticated(TObject *Sender, bool &ATestTokens)
{
Log->Lines->Add(L"Authentication successful; saving tokens...");
try { GMail->SaveTokens(); } catch (...) {}
// resend after auth:
Enviar_correo_de_prueba(nullptr);
}
void __fastcall TPRINCIPAL::GMailSendMessage(
TObject *Sender,
TTMSFNCCloudGoogleGmailMessage * const AMessage,
TTMSFNCCloudBaseRequestResult * const ARequestResult)
{
if (ARequestResult && ARequestResult->Success) {
Log->Lines->Add(L"Mail sent OK.");
} else {
Log->Lines->Add(L"Send failed. ResponseCode=" + IntToStr((int)ARequestResult->ResponseCode));
// If this happens, we request interactive auth:
GMail->Connect();
}
}
What we observe
-
First send after app start (fresh machine/user):
- We get Google’s consent screen, we approve, tokens are saved to the Registry, mail is sent successfully.
-
Subsequent sends in the same session:
- No consent screen, mail sends OK.
-
After closing and reopening the app (same user, Win64):
- First send returns
401and we get the Google consent screen again. - After consenting again, it works for the rest of the session.
- First send returns
-
Registry:
- Keys/values are present under
HKCU\Software\Cuantic\GMail\r@cuantic.es\...(e.g.,ACCESSTOKEN,ACCESSTOKENREFRESH,SECRET, etc.). - So persistence appears to work, but the component does not silently reuse/refresh after restart.
- Keys/values are present under
-
Methods available in our build (C++Builder 13 + FNC 1.3.0.0):
GMail->LoadTokens()/GMail->SaveTokens()work (component-level).Authentication->LoadTokens()/Authentication->SaveTokens()also exist, but we standardized on component-level per your docs/samples.Authentication->AccessTypedoes not exist (so we cannot setatOfflineexplicitly).Authentication->IsAccessTokenValid()exists.GMail->RefreshAccessToken()does not exist.- We call
GMail->FetchAccessToken()->Await()to try a silent access-token get/refresh before sending.
-
Crashes we hit while trying alternatives:
- Calling token methods too early (in constructor) caused intermittent Access Violations at startup. We moved setup to
FormCreate, which stabilized the app. - We no longer do any heavy work in the constructor.
- Calling token methods too early (in constructor) caused intermittent Access Violations at startup. We moved setup to
Questions for TMS
-
Correct pattern to persist & reuse tokens across restarts
- Is the above pattern (component-level
LoadTokenson startup, thenFetchAccessToken()->Await()before send, thenSaveTokens) the recommended one for TTMSFNCCloudGoogleGmail? - If not, could you please share the minimal supported pattern for C++Builder (including which object—component vs.
Authentication—should ownLoadTokens/SaveTokens)?
- Is the above pattern (component-level
-
Offline / refresh token issuance
- Since
AccessTypeis not exposed, how do we ensureaccess_type=offlineso that a refresh token is stored andFetchAccessTokencan refresh silently after restart? - Are there specific properties (Scopes, Prompt, etc.) we must set on
Authenticationto receive a refresh token for Gmail?
- Since
-
Windows Service scenario (headless)
- Our final target is a Windows Service (C++Builder 13 Win64 Modern). We can obtain the initial consent once from a small desktop app, but the Service must run unattended and never show a browser.
- What’s the supported approach to:
a) generate & persist tokens from a helper app,
b) load them in the Service, and
c) keep renewing silently forever (respecting Google’s policies)? - Any notes about running as LocalSystem or a service account and the Registry hive to use?
-
Why do we get a 401 after restart?
- With tokens present in the Registry, our first
SendMailMessageafter restart returnsResponseCode=401, and only afterConnect()+ consent it works again. - Could this mean
FetchAccessTokenisn’t attempting a refresh (or fails silently) because a required property is missing? - What diagnostics would you recommend enabling to confirm the refresh flow (e.g., detailed logs/hooks)?
- With tokens present in the Registry, our first
-
Sample Project
- If you have a C++Builder minimal sample that demonstrates “consent once → persist to Registry → close app → reopen → send without UI”, that would help a lot—especially one that’s compatible with TMS FNC Cloud 1.3.0.0 and C++Builder 13 Win64 Modern.
Why we’re asking
We must run this from a Windows Service, so we can’t rely on an interactive browser after deployment. We just need the official, supported way—using your component—to:
- Obtain consent once (desktop helper).
- Persist tokens to Registry.
- Load and silently refresh tokens on subsequent runs (desktop & service), using
FetchAccessTokenor whatever you recommend, without re-prompting the user.
If there are breaking changes in 1.3.0.0 or differences between properties/methods (Authentication vs. component-level), please point us at the correct API to use.
Happy to send a tiny repro if you prefer; the snippets above should be enough to reproduce.
Thanks a lot!