TTMSFNCCloudGoogleGmail – Persisting OAuth tokens across restarts (C++Builder 13 Win64 Modern, TMS FNC Cloud 1.3.0.0). Need headless refresh for Windows Service

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):

  • TTMSFNCCloudGoogleGmail named GMail
  • TButton named BtnSend
  • TMemo named Log
  • (Some TEdits for ClientID/Secret/From/To)
  • GMail.PersistTokens.Location = plRegistry
  • GMail.PersistTokens.Key = 'Software\Cuantic\GMail\'
  • GMail.PersistTokens.Section = 'r@cuantic.es'
    (We see values such as ACCESSTOKEN, ACCESSTOKENREFRESH, SECRET, etc. written under HKCU\Software\Cuantic\GMail\r@cuantic.es\...)

Note: AccessType isn’t available on our build of TTMSFNCCloudOAuthAuthentication, so we can’t set atOffline explicitly. Please advise on how your component ensures access_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

  1. 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.
  2. Subsequent sends in the same session:

    • No consent screen, mail sends OK.
  3. After closing and reopening the app (same user, Win64):

    • First send returns 401 and we get the Google consent screen again.
    • After consenting again, it works for the rest of the session.
  4. 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.
  5. 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->AccessType does not exist (so we cannot set atOffline explicitly).
    • Authentication->IsAccessTokenValid() exists.
    • GMail->RefreshAccessToken() does not exist.
    • We call GMail->FetchAccessToken()->Await() to try a silent access-token get/refresh before sending.
  6. 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.

Questions for TMS

  1. Correct pattern to persist & reuse tokens across restarts

    • Is the above pattern (component-level LoadTokens on startup, then FetchAccessToken()->Await() before send, then SaveTokens) 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 own LoadTokens/SaveTokens)?
  2. Offline / refresh token issuance

    • Since AccessType is not exposed, how do we ensure access_type=offline so that a refresh token is stored and FetchAccessToken can refresh silently after restart?
    • Are there specific properties (Scopes, Prompt, etc.) we must set on Authentication to receive a refresh token for Gmail?
  3. 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?
  4. Why do we get a 401 after restart?

    • With tokens present in the Registry, our first SendMailMessage after restart returns ResponseCode=401, and only after Connect() + consent it works again.
    • Could this mean FetchAccessToken isn’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)?
  5. 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:

  1. Obtain consent once (desktop helper).
  2. Persist tokens to Registry.
  3. Load and silently refresh tokens on subsequent runs (desktop & service), using FetchAccessToken or 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!

Hi,

  1. There is no need to call LoadTokens or SaveTokens. Calling Connect or FetchAccessToken will automatically do this in the background if PersistTokens.Key has a value assigned.

  2. The access type is always set to access_type=offline by default.

  3. In the desktop app: call Connect and if OnConnected is triggered the access token and refresh token has been retrieved.
    In the service app: use FetchAccessToken.ThenBy.

  4. The component will first try a TestTokens call in the background to find out if the current access token is still valid. If this is not the case a 401 response is returned. This way the component knows a new access token should be requested by using the refresh token if one is available.
    FetchAccessToken returns the latest valid access token. This means if the refresh token is missing/expired, it will return the cached access token:

  1. Unfortunately there are currently no C++Builder samples available.
    Here is the code for a minimal Delphi sample application.
procedure TForm1.FormCreate(Sender: TObject);
begin
  TMSFNCCloudGoogleGmail1.Authentication.ClientID := 'abc123';
  TMSFNCCloudGoogleGmail1.Authentication.Secret := 'xyz456';
  TMSFNCCloudGoogleGmail1.Authentication.CallBackURL := 'http://localhost:8888';
  TMSFNCCloudGoogleGmail1.PersistTokens.Key := 'TMSFNCCloudPack';
  TMSFNCCloudGoogleGmail1.PersistTokens.Section := 'GMail';
end;

procedure TForm1.btConnectClick(Sender: TObject);
begin
  TMSFNCCloudGoogleGmail1.Connect;
end;

procedure TForm1.btGetMailsClick(Sender: TObject);
begin
  TMSFNCCloudGoogleGmail1.FetchAccessToken.ThenBy(procedure (const AValue: string)
  begin
    TMSFNCCloudGoogleGmail1.GetMails;
  end);
  ListBox1.Clear;
end;

procedure TForm1.TMSFNCCloudGoogleGmail1Connected(Sender: TObject);
begin
  Label1.Text := 'Connected!';
end;

procedure TForm1.TMSFNCCloudGoogleGmail1GetMailsComplete(Sender: TObject;
  const ARequestResult: TTMSFNCCloudBaseRequestResult);
var
  I: integer;
begin
  for I := 0 to TMSFNCCloudGoogleGmail1.Mails.Count - 1 do
  begin
    ListBox1.Items.Add(TMSFNCCloudGoogleGmail1.Mails[I].Subject);
  end;
end;