|
81467
|
2827
|
36
|
2026-05-28T08:16:47.307201+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956207307_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
$originalTo = null;
$to = null;
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$originalTo = $header->value;
} elseif ($header->name === 'To') {
$to = $header->value;
}
}
$recipient = $originalTo ?? $to;
if ($recipient !== null) {
$matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedRecipient = explode('+', $recipient)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedRecipient,
]);
}
return $matches;
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [
'message_id' => $messageId,
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09740692,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8374335,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00930851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n $originalTo = null;\n $to = null;\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $originalTo = $header->value;\n } elseif ($header->name === 'To') {\n $to = $header->value;\n }\n }\n\n $recipient = $originalTo ?? $to;\n\n if ($recipient !== null) {\n $matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedRecipient = explode('+', $recipient)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedRecipient,\n ]);\n }\n\n return $matches;\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [\n 'message_id' => $messageId,\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n $originalTo = null;\n $to = null;\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $originalTo = $header->value;\n } elseif ($header->name === 'To') {\n $to = $header->value;\n }\n }\n\n $recipient = $originalTo ?? $to;\n\n if ($recipient !== null) {\n $matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedRecipient = explode('+', $recipient)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedRecipient,\n ]);\n }\n\n return $matches;\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [\n 'message_id' => $messageId,\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.7293883,"top":0.10055866,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7390292,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.7463431,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"bounds":{"left":0.43051863,"top":0.09736632,"width":0.32280585,"height":0.90263367},"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-4729423357167171668
|
-8235799205988284024
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
$originalTo = null;
$to = null;
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$originalTo = $header->value;
} elseif ($header->name === 'To') {
$to = $header->value;
}
}
$recipient = $originalTo ?? $to;
if ($recipient !== null) {
$matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedRecipient = explode('+', $recipient)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedRecipient,
]);
}
return $matches;
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [
'message_id' => $messageId,
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81466
|
2827
|
35
|
2026-05-28T08:16:31.901746+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956191901_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
$originalTo = null;
$to = null;
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$originalTo = $header->value;
} elseif ($header->name === 'To') {
$to = $header->value;
}
}
$recipient = $originalTo ?? $to;
if ($recipient !== null) {
$matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedRecipient = explode('+', $recipient)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedRecipient,
]);
}
return $matches;
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [
'message_id' => $messageId,
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09740692,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8374335,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00930851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n $originalTo = null;\n $to = null;\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $originalTo = $header->value;\n } elseif ($header->name === 'To') {\n $to = $header->value;\n }\n }\n\n $recipient = $originalTo ?? $to;\n\n if ($recipient !== null) {\n $matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedRecipient = explode('+', $recipient)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedRecipient,\n ]);\n }\n\n return $matches;\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [\n 'message_id' => $messageId,\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n $originalTo = null;\n $to = null;\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $originalTo = $header->value;\n } elseif ($header->name === 'To') {\n $to = $header->value;\n }\n }\n\n $recipient = $originalTo ?? $to;\n\n if ($recipient !== null) {\n $matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedRecipient = explode('+', $recipient)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedRecipient,\n ]);\n }\n\n return $matches;\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [\n 'message_id' => $messageId,\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.7293883,"top":0.10055866,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7390292,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.7463431,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"bounds":{"left":0.43051863,"top":0.09736632,"width":0.32280585,"height":0.90263367},"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-8364568366956037565
|
-8235816729454851704
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
$originalTo = null;
$to = null;
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$originalTo = $header->value;
} elseif ($header->name === 'To') {
$to = $header->value;
}
}
$recipient = $originalTo ?? $to;
if ($recipient !== null) {
$matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedRecipient = explode('+', $recipient)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedRecipient,
]);
}
return $matches;
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [
'message_id' => $messageId,
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project...
|
81465
|
NULL
|
NULL
|
NULL
|
|
81465
|
2827
|
34
|
2026-05-28T08:16:30.609585+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956190609_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
$originalTo = null;
$to = null;
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$originalTo = $header->value;
} elseif ($header->name === 'To') {
$to = $header->value;
}
}
$recipient = $originalTo ?? $to;
if ($recipient !== null) {
$matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedRecipient = explode('+', $recipient)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedRecipient,
]);
}
return $matches;
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [
'message_id' => $messageId,
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09740692,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8374335,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00930851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n $originalTo = null;\n $to = null;\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $originalTo = $header->value;\n } elseif ($header->name === 'To') {\n $to = $header->value;\n }\n }\n\n $recipient = $originalTo ?? $to;\n\n if ($recipient !== null) {\n $matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedRecipient = explode('+', $recipient)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedRecipient,\n ]);\n }\n\n return $matches;\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [\n 'message_id' => $messageId,\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n $originalTo = null;\n $to = null;\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $originalTo = $header->value;\n } elseif ($header->name === 'To') {\n $to = $header->value;\n }\n }\n\n $recipient = $originalTo ?? $to;\n\n if ($recipient !== null) {\n $matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedRecipient = explode('+', $recipient)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedRecipient,\n ]);\n }\n\n return $matches;\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [\n 'message_id' => $messageId,\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.7293883,"top":0.10055866,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7390292,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.7463431,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"bounds":{"left":0.43051863,"top":0.09736632,"width":0.32280585,"height":0.90263367},"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-4729423357167171668
|
-8235799205988284024
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
$originalTo = null;
$to = null;
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$originalTo = $header->value;
} elseif ($header->name === 'To') {
$to = $header->value;
}
}
$recipient = $originalTo ?? $to;
if ($recipient !== null) {
$matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedRecipient = explode('+', $recipient)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedRecipient,
]);
}
return $matches;
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [
'message_id' => $messageId,
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81464
|
2826
|
25
|
2026-05-28T08:16:30.508036+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956190508_m1.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
$originalTo = null;
$to = null;
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$originalTo = $header->value;
} elseif ($header->name === 'To') {
$to = $header->value;
}
}
$recipient = $originalTo ?? $to;
if ($recipient !== null) {
$matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedRecipient = explode('+', $recipient)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedRecipient,
]);
}
return $matches;
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [
'message_id' => $messageId,
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.019444445,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n $originalTo = null;\n $to = null;\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $originalTo = $header->value;\n } elseif ($header->name === 'To') {\n $to = $header->value;\n }\n }\n\n $recipient = $originalTo ?? $to;\n\n if ($recipient !== null) {\n $matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedRecipient = explode('+', $recipient)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedRecipient,\n ]);\n }\n\n return $matches;\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [\n 'message_id' => $messageId,\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n $originalTo = null;\n $to = null;\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $originalTo = $header->value;\n } elseif ($header->name === 'To') {\n $to = $header->value;\n }\n }\n\n $recipient = $originalTo ?? $to;\n\n if ($recipient !== null) {\n $matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedRecipient = explode('+', $recipient)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedRecipient,\n ]);\n }\n\n return $matches;\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [\n 'message_id' => $messageId,\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-4729423357167171668
|
-8235799205988284024
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
$originalTo = null;
$to = null;
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$originalTo = $header->value;
} elseif ($header->name === 'To') {
$to = $header->value;
}
}
$recipient = $originalTo ?? $to;
if ($recipient !== null) {
$matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedRecipient = explode('+', $recipient)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedRecipient,
]);
}
return $matches;
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [
'message_id' => $messageId,
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
81463
|
NULL
|
NULL
|
NULL
|
|
81463
|
2826
|
24
|
2026-05-28T08:16:27.496999+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956187496_m1.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-3150964534923304660
|
-4304507549388960332
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
SlackFileEditViewGoHistoryWindowHelpDOCKER• ₴1DEV (docker)₴2-zshscreenpipe"Digest:sha256:d6b7ebd9b71c0480b909d77fceada3df720c6594e4c853228765509b36f52db6Status: Imageis up todate for 438740370364.dkr.ecr.us-east-2.amazonaws.com/jiminny/app/q438740370364.dkr.ecr.us-east-2.amazonaws.com/jiminny/app/qa:arm64v8-latestWhat's next:View a summary of image vulnerabilities and recommendations → docker scoutquickviewlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix--config=.php-cs-fixer.dist.plPHP CS Fixer 3.95.1 Adalbertus by Fabien Potencier, Dariusz Ruminskiand contributors.PHP runtime: 8.5.5Loadedconfigdefault from ".php-cs-fixer.dist.php"Running analysis on 7cores with 10files per process.5698/5698100%1) app/Services/Mail/TextRelayService.php (statement_indentation)begin diff/home/jiminny/app/Services/Mail/TextRelayService.php+++/home/jiminny/app/Services/Mail/TextRelayService.php-67,7 +67,7 @eSjob = new EmailTextRelay(SmessageId, SrelayedText);Sjob-›onQueue(Constants: :QUEUE_EMAILS);dispatch($job);dispatch($job);Log: :info(' [TextRelayService]Successfully dispatched message', ["message_id'= SmessageId,end diffFixed 1 of 5698 files in 84.046 seconds, 701.18 MBmemory usedDetected deprecations in use (they will stop working in next major release)- Rule set"@PHP74Migration" is deprecated. Use"@PHP7x4Migration"instead.- Rule set "@PHP80Migration" is deprecated. Use "@PHP8x®Migration" instead.- Rule set "@PHP81Migration" is deprecated. Use "@PHP8x1Migration" instead.- Rule set "@PHP82Migration" is deprecated. Use "@PHP8x2Migration" instead.- Rule set "@PHP83Migration" is deprecated. Use "@PHP8x3Migration"instead.- Rule set "@PHP84Migration" is deprecated. Use "@PHP8x4Migration" instead.What's next:Try Docker Debug for seamless, persistent debugging tools in any containeror image »Learn more at https://docs.docker.com/go/debug-cli/lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-• •HomeDMsActivityFilesLater..•More+ED→Jiminny... vw Starrea& platform-backend-...platform-inner-teamChannels# ai-chapter# alerts# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday# jbu-team-info# jiminny-bg# platform-tickets# product_launches# random# releases# support# thank-yous# the_people_of_jimi...• Direct messages& Petko Kashinski% Galya Dimitrova&. Stefka Stoyanova: Todor StamatovStalivan Ganraiou100% <78• Thu 28 May 11:16:27Describe what you are looking for* & platform-inner-team& 106 0• MessagesP Channel OverviewMore v+Yesterday ~Resolve rrir v.oiotPreview in SlackStatusBacklogPriority= MediumAssigneeUnassignedAs of yesterday at 6:20 PM RefreshOpen in Jira*+ SummarizeToday ~NewNikolay Nikolov 10:52 AMФикс за пускане на remote commands за staging:https://github.com/jiminny/app/pull/12132#12132 Fix stage profile for remote commandsrunJIRA: JY-XXXXDeployment notes:• NoneComments1V1jiminny/app| May 26th Added by GitHubMessage & platform-inner-team...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81462
|
2827
|
33
|
2026-05-28T08:16:25.794078+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956185794_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
rapstomCoocWindowFV faVsco.s ~$ JY-20915-fix-missi rapstomCoocWindowFV faVsco.s ~$ JY-20915-fix-missing-header-text-© InternetMessagelnterface.ph© MailChannelService.phg0 MeetinaGeneratoromcadonEOHUNDn PlaybooksJotaeoa Teama Telechony#UserPilotWebhookC Abstrac Semvice cho©ActivitvProviderFactory.oho© ActivityService.php© ApiResponseService.ohoCeonarsneasaries oodg InsightSeatService.phpC InstantMeetingService.phpcilntecomswneoeC IpapiClient.php© IpapiService.phpC ParticipantShareService.phg©. PlanhatService.php© PlaybackService.phpPlaybackVideoOnlyService.pho© PlaybookCategoryService.php© PlaylistGeneratorinterface.phpel PocAlvoTosmErmEmЛodiA ny© SimoleThrottleService.ohg© SlackService.ohdC SocialAccountService.oho)C SoftPhoneService.oho©) TeamOwnerService.ohoC) TeamService.ohoC) TranscodeParameterResolver.ohC UserSemce.on©Uuid.php> M Traitc@ UseCasese ValaationMo halnore nhr@ tnitislGrantondGtnto nhoelliminnv nhamockhrhtsTextRelayServiceTest.php3.env.productionclass TextRelayServiceouoere Tuncczon syne diidAAAAUIUTTTASSnazloox & contzol xeyanny,coogle texc userSexpectedAlias = config(key. 'jiminny.deploy region') =a= 'eu' ? "catch-all-eu' : 'catch-all''(TextRelavSeryicel Stanttina svnc'."MexoeCro mous EXSeXoeCeohee>exoccreohosSsenwice e Sthis->ae-Semuicetstaslbox)SmessageHistory = Sthis->getHistory(Sservice):aulunznace suopore racades Loa::channel-cuscon channel ->intolSnessagchase AcceotSmessageIds = 0:foreach (SnessageHistory as Shistories) {Snessages = Shistories->messagesAdded ?? 0:foreach (Smessages as Smessage)Snessageld = Smessage-snessage->idif ( Sthis->isForQurrentEnvironnent(Sservice, Snallbox, Snessageld, SexoectedAlias.Norowoeroesss0eosrsosSf (ScelavedText === null)ScelavedText = TextRelave:createrfemaa orouidert e> Texthelay.eproumer ostare"emaennouider o s> Shesssoeld.satus' E Textee hy.STATS PRodEsSiNGSiob = new Emai TextRelay(Snessageld, Srelayediext):ahesonthouoteuCanctanternEit MATGoeon chil100dispatch(Siob):Loo:sinfo( messace: "(TextRelavService) Successfully dispatched nessage"nessago id/ pa snsse ice na xaX Reject File oxesr uim nnye rocolnost& console [PROD# consoe leu.cozo"os"lo 01.4/.00 Locat.nr. godidushistoryTypes => messageAddec(startHistoryId) => 359921correlazon1d:YaC0//105-c215-4442-2886-7269823580901trace_1d*: *87f39623-3deb-4827-a8cf-b862aec93289**7826-85-28 87:47:58 Local.IWFU: SnessagchzscomvoTrace10: [CREDIT_CARD]-a8cт-0862aec93789-17876-85528 87-S8857omuotfteSoamaneIhistorylypes => messageAddecstartHistorytoll50%2{"correlation_id":"ebf74386-735e-472b-b788-f9a7f56e4655*,*trace_1d*:"b2a98e4e-d669-4c84-b1de-843cecc6a681")2826-85-28 R7-59-5811 1oclTNED: Snessadels stomuEconnelatton Saw."ehf71z0h-775a.170h-h780.40.7456e01550 "ьласA S/.-6290R0/9-1640-6604-h1de-9/3cecchaf91»)2924-85.28 87-55-17 | 1o601 TNEh• SoananshistoryTypes => messageAddecAwAtU&AtAAuTAl = 26002-4d4a-b84b-a8531ca414a7"1300x.06.2992-66-17| Jocol-Twet: Snossagchs stom.#"correlation_id":"25971837-3161-431b-adS9-e898eb49beSf*, *trace_1d*:*c3463c2c-d21b-4d4a-b84b-a8531ca614a7"}(2826-85-28 88:88:531 Zocal, INF0: Sparansmistorvnvoes => nessagenddedistartHistorvidl s 350925connel atson o""naosynoo%ctheyerark -chn chcoaiieuooace aeuei aioowe ser.oSec-hsyiuhichthuOierhoR RRohieSaioetst Srassadelsrom#[CREDIT_CARD].05cc-H529269c24h7"2924-85-28 AR-95-27| Toeol TNSh: Soananeoo inu co moy tirloiceeeendhOTXCONHENYSWWIC+0.Winst about the. Shams the + cion is in emslWitistill work and hownYes, this will work correctly. Let me trace through the logic with this exact ema:oneioerwilue carchswe/woL7o0orsshoobl.rdowkhtwer1ohnnv.co0 pho11 1. Split on €oirdeccheateeobewtonehoturdos kolotorxrwehtnnv.cos"I1 2. Host check: 'txt.fiminny.com"11 3. Strip plus tag fron local partcatch-all[PHONE]..11 - 'catch-all/1 4. Compare alías: 'catch-all' mm 'catch-all' - returns truSo isForcurrentEnvi ronsentl returns true and the messade orocreos to be disoatchedThen in EnaflTextRelay job, parseRecipient() uses the To header directly and the regex \d+\.1d+\. [a-zA-20-- the + prefixoetore tne phone numoers is stipped or parschecipientintoentities, wiich prepenos + iselt anyway.Everythina worke end-tosand. Now vou luet nesd to ranodly the To Thoader thinrck ir so these mosenaosaren't refused.vice.pho #L182-210haytRelusarich.oor1tile +24-15>Ask anything (XOL)" HodhAdhotvAcceot all• OKwindeurlahmeht4 spad...
|
NULL
|
6843649552266690491
|
NULL
|
click
|
ocr
|
NULL
|
rapstomCoocWindowFV faVsco.s ~$ JY-20915-fix-missi rapstomCoocWindowFV faVsco.s ~$ JY-20915-fix-missing-header-text-© InternetMessagelnterface.ph© MailChannelService.phg0 MeetinaGeneratoromcadonEOHUNDn PlaybooksJotaeoa Teama Telechony#UserPilotWebhookC Abstrac Semvice cho©ActivitvProviderFactory.oho© ActivityService.php© ApiResponseService.ohoCeonarsneasaries oodg InsightSeatService.phpC InstantMeetingService.phpcilntecomswneoeC IpapiClient.php© IpapiService.phpC ParticipantShareService.phg©. PlanhatService.php© PlaybackService.phpPlaybackVideoOnlyService.pho© PlaybookCategoryService.php© PlaylistGeneratorinterface.phpel PocAlvoTosmErmEmЛodiA ny© SimoleThrottleService.ohg© SlackService.ohdC SocialAccountService.oho)C SoftPhoneService.oho©) TeamOwnerService.ohoC) TeamService.ohoC) TranscodeParameterResolver.ohC UserSemce.on©Uuid.php> M Traitc@ UseCasese ValaationMo halnore nhr@ tnitislGrantondGtnto nhoelliminnv nhamockhrhtsTextRelayServiceTest.php3.env.productionclass TextRelayServiceouoere Tuncczon syne diidAAAAUIUTTTASSnazloox & contzol xeyanny,coogle texc userSexpectedAlias = config(key. 'jiminny.deploy region') =a= 'eu' ? "catch-all-eu' : 'catch-all''(TextRelavSeryicel Stanttina svnc'."MexoeCro mous EXSeXoeCeohee>exoccreohosSsenwice e Sthis->ae-Semuicetstaslbox)SmessageHistory = Sthis->getHistory(Sservice):aulunznace suopore racades Loa::channel-cuscon channel ->intolSnessagchase AcceotSmessageIds = 0:foreach (SnessageHistory as Shistories) {Snessages = Shistories->messagesAdded ?? 0:foreach (Smessages as Smessage)Snessageld = Smessage-snessage->idif ( Sthis->isForQurrentEnvironnent(Sservice, Snallbox, Snessageld, SexoectedAlias.Norowoeroesss0eosrsosSf (ScelavedText === null)ScelavedText = TextRelave:createrfemaa orouidert e> Texthelay.eproumer ostare"emaennouider o s> Shesssoeld.satus' E Textee hy.STATS PRodEsSiNGSiob = new Emai TextRelay(Snessageld, Srelayediext):ahesonthouoteuCanctanternEit MATGoeon chil100dispatch(Siob):Loo:sinfo( messace: "(TextRelavService) Successfully dispatched nessage"nessago id/ pa snsse ice na xaX Reject File oxesr uim nnye rocolnost& console [PROD# consoe leu.cozo"os"lo 01.4/.00 Locat.nr. godidushistoryTypes => messageAddec(startHistoryId) => 359921correlazon1d:YaC0//105-c215-4442-2886-7269823580901trace_1d*: *87f39623-3deb-4827-a8cf-b862aec93289**7826-85-28 87:47:58 Local.IWFU: SnessagchzscomvoTrace10: [CREDIT_CARD]-a8cт-0862aec93789-17876-85528 87-S8857omuotfteSoamaneIhistorylypes => messageAddecstartHistorytoll50%2{"correlation_id":"ebf74386-735e-472b-b788-f9a7f56e4655*,*trace_1d*:"b2a98e4e-d669-4c84-b1de-843cecc6a681")2826-85-28 R7-59-5811 1oclTNED: Snessadels stomuEconnelatton Saw."ehf71z0h-775a.170h-h780.40.7456e01550 "ьласA S/.-6290R0/9-1640-6604-h1de-9/3cecchaf91»)2924-85.28 87-55-17 | 1o601 TNEh• SoananshistoryTypes => messageAddecAwAtU&AtAAuTAl = 26002-4d4a-b84b-a8531ca414a7"1300x.06.2992-66-17| Jocol-Twet: Snossagchs stom.#"correlation_id":"25971837-3161-431b-adS9-e898eb49beSf*, *trace_1d*:*c3463c2c-d21b-4d4a-b84b-a8531ca614a7"}(2826-85-28 88:88:531 Zocal, INF0: Sparansmistorvnvoes => nessagenddedistartHistorvidl s 350925connel atson o""naosynoo%ctheyerark -chn chcoaiieuooace aeuei aioowe ser.oSec-hsyiuhichthuOierhoR RRohieSaioetst Srassadelsrom#[CREDIT_CARD].05cc-H529269c24h7"2924-85-28 AR-95-27| Toeol TNSh: Soananeoo inu co moy tirloiceeeendhOTXCONHENYSWWIC+0.Winst about the. Shams the + cion is in emslWitistill work and hownYes, this will work correctly. Let me trace through the logic with this exact ema:oneioerwilue carchswe/woL7o0orsshoobl.rdowkhtwer1ohnnv.co0 pho11 1. Split on €oirdeccheateeobewtonehoturdos kolotorxrwehtnnv.cos"I1 2. Host check: 'txt.fiminny.com"11 3. Strip plus tag fron local partcatch-all[PHONE]..11 - 'catch-all/1 4. Compare alías: 'catch-all' mm 'catch-all' - returns truSo isForcurrentEnvi ronsentl returns true and the messade orocreos to be disoatchedThen in EnaflTextRelay job, parseRecipient() uses the To header directly and the regex \d+\.1d+\. [a-zA-20-- the + prefixoetore tne phone numoers is stipped or parschecipientintoentities, wiich prepenos + iselt anyway.Everythina worke end-tosand. Now vou luet nesd to ranodly the To Thoader thinrck ir so these mosenaosaren't refused.vice.pho #L182-210haytRelusarich.oor1tile +24-15>Ask anything (XOL)" HodhAdhotvAcceot all• OKwindeurlahmeht4 spad...
|
81459
|
NULL
|
NULL
|
NULL
|
|
81461
|
2826
|
23
|
2026-05-28T08:16:25.691166+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956185691_m1.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
SlackFileEditViewGoHistoryWindowHelpDOCKER• ₴1DEV SlackFileEditViewGoHistoryWindowHelpDOCKER• ₴1DEV (docker)₴2-zshscreenpipe"Digest:sha256:d6b7ebd9b71c0480b909d77fceada3df720c6594e4c853228765509b36f52db6Status: Imageis up todate for 438740370364.dkr.ecr.us-east-2.amazonaws.com/jiminny/app/q438740370364.dkr.ecr.us-east-2.amazonaws.com/jiminny/app/qa:arm64v8-latestWhat's next:View a summary of image vulnerabilities and recommendations → docker scoutquickviewlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix--config=.php-cs-fixer.dist.plPHP CS Fixer 3.95.1 Adalbertus by Fabien Potencier, Dariusz Ruminskiand contributors.PHP runtime: 8.5.5Loadedconfigdefault from ".php-cs-fixer.dist.php"Running analysis on 7cores with 10files per process.5698/5698100%1) app/Services/Mail/TextRelayService.php (statement_indentation)begin diff/home/jiminny/app/Services/Mail/TextRelayService.php+++/home/jiminny/app/Services/Mail/TextRelayService.php-67,7 +67,7 @eSjob = new EmailTextRelay(SmessageId, SrelayedText);Sjob-›onQueue(Constants: :QUEUE_EMAILS);dispatch($job);dispatch($job);Log: :info(' [TextRelayService]Successfully dispatched message', ["message_id'= SmessageId,end diffFixed 1 of 5698 files in 84.046 seconds, 701.18 MBmemory usedDetected deprecations in use (they will stop working in next major release)- Rule set"@PHP74Migration" is deprecated. Use"@PHP7x4Migration"instead.- Rule set "@PHP80Migration" is deprecated. Use "@PHP8x®Migration" instead.- Rule set "@PHP81Migration" is deprecated. Use "@PHP8x1Migration" instead.- Rule set "@PHP82Migration" is deprecated. Use "@PHP8x2Migration" instead.- Rule set "@PHP83Migration" is deprecated. Use "@PHP8x3Migration"instead.- Rule set "@PHP84Migration" is deprecated. Use "@PHP8x4Migration" instead.What's next:Try Docker Debug for seamless, persistent debugging tools in any containeror image »Learn more at https://docs.docker.com/go/debug-cli/lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-• •HomeDMsActivityFilesLater..•More+ED→Jiminny... vw Starrea& platform-backend-...platform-inner-teamChannels# ai-chapter# alerts# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday# jbu-team-info# jiminny-bg# platform-tickets# product_launches# random# releases# support# thank-yous# the_people_of_jimi...• Direct messages& Petko Kashinski% Galya Dimitrova&. Stefka Stoyanova: Todor StamatovStalivan Ganraiou100% <78• Thu 28 May 11:16:25Describe what you are looking for* & platform-inner-team& 106 0• MessagesP Channel OverviewMore v+Yesterday ~Resolve rrir v.oiotPreview in SlackStatusBacklogPriority= MediumAssigneeUnassignedAs of yesterday at 6:20 PM RefreshOpen in Jira*+ SummarizeToday ~NewNikolay Nikolov 10:52 AMФикс за пускане на remote commands за staging:https://github.com/jiminny/app/pull/12132#12132 Fix stage profile for remote commandsrunJIRA: JY-XXXXDeployment notes:• NoneComments1V1jiminny/app| May 26th Added by GitHubMessage & platform-inner-team...
|
NULL
|
1854427488783385704
|
NULL
|
click
|
ocr
|
NULL
|
SlackFileEditViewGoHistoryWindowHelpDOCKER• ₴1DEV SlackFileEditViewGoHistoryWindowHelpDOCKER• ₴1DEV (docker)₴2-zshscreenpipe"Digest:sha256:d6b7ebd9b71c0480b909d77fceada3df720c6594e4c853228765509b36f52db6Status: Imageis up todate for 438740370364.dkr.ecr.us-east-2.amazonaws.com/jiminny/app/q438740370364.dkr.ecr.us-east-2.amazonaws.com/jiminny/app/qa:arm64v8-latestWhat's next:View a summary of image vulnerabilities and recommendations → docker scoutquickviewlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix--config=.php-cs-fixer.dist.plPHP CS Fixer 3.95.1 Adalbertus by Fabien Potencier, Dariusz Ruminskiand contributors.PHP runtime: 8.5.5Loadedconfigdefault from ".php-cs-fixer.dist.php"Running analysis on 7cores with 10files per process.5698/5698100%1) app/Services/Mail/TextRelayService.php (statement_indentation)begin diff/home/jiminny/app/Services/Mail/TextRelayService.php+++/home/jiminny/app/Services/Mail/TextRelayService.php-67,7 +67,7 @eSjob = new EmailTextRelay(SmessageId, SrelayedText);Sjob-›onQueue(Constants: :QUEUE_EMAILS);dispatch($job);dispatch($job);Log: :info(' [TextRelayService]Successfully dispatched message', ["message_id'= SmessageId,end diffFixed 1 of 5698 files in 84.046 seconds, 701.18 MBmemory usedDetected deprecations in use (they will stop working in next major release)- Rule set"@PHP74Migration" is deprecated. Use"@PHP7x4Migration"instead.- Rule set "@PHP80Migration" is deprecated. Use "@PHP8x®Migration" instead.- Rule set "@PHP81Migration" is deprecated. Use "@PHP8x1Migration" instead.- Rule set "@PHP82Migration" is deprecated. Use "@PHP8x2Migration" instead.- Rule set "@PHP83Migration" is deprecated. Use "@PHP8x3Migration"instead.- Rule set "@PHP84Migration" is deprecated. Use "@PHP8x4Migration" instead.What's next:Try Docker Debug for seamless, persistent debugging tools in any containeror image »Learn more at https://docs.docker.com/go/debug-cli/lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-• •HomeDMsActivityFilesLater..•More+ED→Jiminny... vw Starrea& platform-backend-...platform-inner-teamChannels# ai-chapter# alerts# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday# jbu-team-info# jiminny-bg# platform-tickets# product_launches# random# releases# support# thank-yous# the_people_of_jimi...• Direct messages& Petko Kashinski% Galya Dimitrova&. Stefka Stoyanova: Todor StamatovStalivan Ganraiou100% <78• Thu 28 May 11:16:25Describe what you are looking for* & platform-inner-team& 106 0• MessagesP Channel OverviewMore v+Yesterday ~Resolve rrir v.oiotPreview in SlackStatusBacklogPriority= MediumAssigneeUnassignedAs of yesterday at 6:20 PM RefreshOpen in Jira*+ SummarizeToday ~NewNikolay Nikolov 10:52 AMФикс за пускане на remote commands за staging:https://github.com/jiminny/app/pull/12132#12132 Fix stage profile for remote commandsrunJIRA: JY-XXXXDeployment notes:• NoneComments1V1jiminny/app| May 26th Added by GitHubMessage & platform-inner-team...
|
81460
|
NULL
|
NULL
|
NULL
|
|
81460
|
2826
|
22
|
2026-05-28T08:16:23.558532+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956183558_m1.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
$originalTo = null;
$to = null;
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$originalTo = $header->value;
} elseif ($header->name === 'To') {
$to = $header->value;
}
}
$recipient = $originalTo ?? $to;
if ($recipient !== null) {
$matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedRecipient = explode('+', $recipient)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedRecipient,
]);
}
return $matches;
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [
'message_id' => $messageId,
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.019444445,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n $originalTo = null;\n $to = null;\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $originalTo = $header->value;\n } elseif ($header->name === 'To') {\n $to = $header->value;\n }\n }\n\n $recipient = $originalTo ?? $to;\n\n if ($recipient !== null) {\n $matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedRecipient = explode('+', $recipient)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedRecipient,\n ]);\n }\n\n return $matches;\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [\n 'message_id' => $messageId,\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n $originalTo = null;\n $to = null;\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $originalTo = $header->value;\n } elseif ($header->name === 'To') {\n $to = $header->value;\n }\n }\n\n $recipient = $originalTo ?? $to;\n\n if ($recipient !== null) {\n $matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedRecipient = explode('+', $recipient)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedRecipient,\n ]);\n }\n\n return $matches;\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [\n 'message_id' => $messageId,\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7456676975688765609
|
-6438878488507806805
|
idle
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
$originalTo = null;
$to = null;
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$originalTo = $header->value;
} elseif ($header->name === 'To') {
$to = $header->value;
}
}
$recipient = $originalTo ?? $to;
if ($recipient !== null) {
$matches = $this->matchesExpectedRecipient($recipient, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedRecipient = explode('+', $recipient)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedRecipient,
]);
}
return $matches;
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To and To headers', [
'message_id' => $messageId,
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81459
|
2827
|
32
|
2026-05-28T08:16:04.643710+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956164643_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09740692,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8374335,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00930851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.7293883,"top":0.10055866,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7390292,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.7463431,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"bounds":{"left":0.43051863,"top":0.09736632,"width":0.32280585,"height":0.90263367},"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
327918965576722946
|
-3660159508063819383
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81458
|
2827
|
31
|
2026-05-28T08:15:51.476969+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956151476_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09740692,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8374335,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-4060176888399009113
|
-9212323791225028208
|
typing_pause
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
PhpStormViewNeweNNCCoocKetuciolool.WindowFV faVsco.|s ~$2 JY-20915-fix-missing-header-text-relaproideta Kemnelonip© SyncMailbox.phpWockhrhta© InternetMessagelnterface.ph© MailChannelService.phgTextRelayServiceTest.phpo Textrelkysewice.on0 MeetinaGeneratoomcadonên OAuth2Dn Playbooks178—KeCaLAJotaeoDn Streamingla Teama Telechony#UserPilotWebhookC Abstrac Semvice cho©ActivityProviderFactory.php© ActivityService.php© ApiResponseService.oho187188189190Ceonarsneasaries oodg InsightSeatService.phpC InstantMeetingService.phgcilntecomswneoeC IpapiClient.php© IpapiService.phpC ParticipantShareService.phg©. PlanhatService.php© PlaybackService.phpPlaybackVideoOnlyService.pho© PlaybookCategoryService.php© PlaylistGeneratorinterface.php© ResolveTeamCrmConnection.ph© SimoleThrottleService.ohdC SlackService.ohoC SocialAccountService.ohoC SoftPhoneService.oho19319419S ©) TeamOwnerService.ohoC) TeamService.oho213C) TranscodeParameterResolver.o 215C UserSemce.onC Uuidloho> M Traitc› @ UseCasesiMusAUtils>E Va cation215218Mo halnore nhr@ tnitislGrantondGtnto nhoeillliminnv nhnA Olen nhí3.env.productionclass TextRelayServiceivare tuncczon asroncurehichvarohaehtsercoousens nos morooreoox.hesodsmesshad->aerP.w.ondiol->oc-headeneorheaders as Sheader) 1eader->name === "X-6n-0riginal-To')tatches=Sthis->natchessxnectedPecsinent/SheaderswallueSexoectedAbias.Sexoecterhostr((!Smatches) ?Sanitize PIl by renoving plus-tag content for loggingSsanitizedOriginalto = explode( separator: *+*, Sheader->value)(0)=Log::info( message:' TextRelayService) Refused message','message id' => Snessageid,'original to sanitized' a› SsanitizedoriginaltoI):nol messase:Tex kelayservice Kerused nessace: eissing x-on-Urzoznal-To headen"oc10 = Snessagclorspresent' => array_nap(fn (Sh) => Sh->nane . *:• . explode( separator: *+*. Sh->value)(e A(messaoe: "[TextRelavServicel Failed to insoect ressaac*. flge_id' => Smessageldtion' = Se->aetMessageo)anwure syceor lone"atchesExpectedReciplent(string Sreciplent, string SexpectedAlias, string SexpectedHost: bool...1 edit+v Accept Fle x- X Reject File ox cSF fiminny@localhost)HS.Jocal jiminny@blocalhost)2826-85-28 87:47:501 Local.INF0: Sparansnzscorylypes = nessageaddeestartHistoryId) => 359921correlazon1d:YaC0//105-c215-4442-8886-72698253309017826-85-28 87:47:58 Local.wFu: Snessagchzscory& console [PROD)& console (EU1d":*87f39623-3deb-4827-a8cf-b862acc93289*}trace10: [CREDIT_CARD]-a8cт-0862aec93789-%17876-85528 87-S8857omuotfteSoamaneIhistorylypes => messageAddecstartHistorytoll50%2"correlation_id":"ebf74306-735e-472b-b788-f9a7f56e4655*,"trace_1d":"b2a98e4e-d669-4C84-b1de-843cecc6a681*)2826-85-28 87-58-5811 JocnlTWEn: Snessadels stomEconnelatton Saw."ehf71z0h-775a.170h-h780.40.7456e01550 "tлaCA S/.-6290R0/9-1640-hc04-h1de-9/3cecchaf91»)2924-85.28 87-55-17 | 1o601 TNEh• SoananshistoryTypes => messageAddecAwAtU&AtAAuTAl = 26002-4d4a-b84b-a8531ca414a7"1300x.06.29 92.66-17 1 1oc0-051: Snossaachstom."correlation_id":"25971837-3161-431b-adS9-e898eb48bASf* *trace_id*:*c3463c2c-d21b-4d4a-b84b-a8531ca414a7*}(2826-85-28 88:88:531 Zocal, INF0: Sparansmistorvivoes => messageaddedistartHistorvidl s 350925connel atson o""naosynoo%ctheyerark -chn chcoaiieuooace aeuei aioowe ser.oSec-hsyiuhichthuOierhoR RRohieSaioetst Srassadelsrom#[CREDIT_CARD].05cc-H529269c24h7"2924-85-28 AR-95-27| Toeol TNSh: Soananeanler herseietS0100% 142-• Thu 28 May 11:15:51TextRelayServiceTest+0.Winat about thit. Saeme the a sion ie in cmall. Witistill work and howaAsk anything (XOL)—@ eodeAdhotvAReinctalAccoot allXwodeurlhimewrekhirest4 space...
|
81456
|
NULL
|
NULL
|
NULL
|
|
81457
|
2826
|
21
|
2026-05-28T08:15:51.274643+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956151274_m1.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-7635200922161528077
|
-4598385934329243181
|
typing_pause
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
SlackFileEditViewGoHistoryWindowHelpDOCKER• ₴1DEV (docker)₴2-zshscreenpipe"Digest:sha256:d6b7ebd9b71c0480b909d77fceada3df720c6594e4c853228765509b36f52db6Status: Imageis up todate for 438740370364.dkr.ecr.us-east-2.amazonaws.com/jiminny/app/q438740370364.dkr.ecr.us-east-2.amazonaws.com/jiminny/app/qa:arm64v8-latestWhat's next:View a summary of image vulnerabilities and recommendations → docker scoutquickviewlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix--config=.php-cs-fixer.dist.plPHP CS Fixer 3.95.1 Adalbertus by Fabien Potencier, Dariusz Ruminskiand contributors.PHP runtime: 8.5.5Loadedconfigdefault from ".php-cs-fixer.dist.php"Running analysis on 7cores with 10files per process.5698/5698100%1) app/Services/Mail/TextRelayService.php (statement_indentation)begin diff/home/jiminny/app/Services/Mail/TextRelayService.php+++/home/jiminny/app/Services/Mail/TextRelayService.php-67,7 +67,7 @eSjob = new EmailTextRelay(SmessageId, SrelayedText);Sjob-›onQueue(Constants: :QUEUE_EMAILS);dispatch($job);dispatch($job);Log: :info(' [TextRelayService]Successfully dispatched message', ["message_id'= SmessageId,end diffFixed 1 of 5698 files in 84.046 seconds, 701.18 MBmemory usedDetected deprecations in use (they will stop working in next major release)- Rule set"@PHP74Migration" is deprecated. Use"@PHP7x4Migration"instead.- Rule set "@PHP80Migration" is deprecated. Use "@PHP8x®Migration" instead.- Rule set "@PHP81Migration" is deprecated. Use "@PHP8x1Migration" instead.- Rule set "@PHP82Migration" is deprecated. Use "@PHP8x2Migration" instead.- Rule set "@PHP83Migration" is deprecated. Use "@PHP8x3Migration"instead.- Rule set "@PHP84Migration" is deprecated. Use "@PHP8x4Migration" instead.What's next:Try Docker Debug for seamless, persistent debugging tools in any containeror image »Learn more at https://docs.docker.com/go/debug-cli/lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-• •HomeDMsActivityFilesLater..•More+ED→Jiminny... vw Starrea& platform-backend-...platform-inner-teamChannels# ai-chapter# alerts# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday# jbu-team-info# jiminny-bg# platform-tickets# product_launches# random# releases# support# thank-yous# the_people_of_jimi...• Direct messages& Petko Kashinski% Galya Dimitrova&. Stefka Stoyanova: Todor StamatovStalivan Ganraiou100% <78• Thu 28 May 11:15:51Describe what you are looking for* & platform-inner-team& 106 0• MessagesP Channel OverviewMore v+Yesterday ~Resolve rrir v.oiotPreview in SlackStatusBacklogPriority= MediumAssigneeUnassignedAs of yesterday at 6:20 PM RefreshOpen in Jira*+ SummarizeToday ~NewNikolay Nikolov 10:52 AMФикс за пускане на remote commands за staging:https://github.com/jiminny/app/pull/12132#12132 Fix stage profile for remote commandsrunJIRA: JY-XXXXDeployment notes:• NoneComments1V1jiminny/app| May 26th Added by GitHubMessage & platform-inner-team...
|
81450
|
NULL
|
NULL
|
NULL
|
|
81456
|
2827
|
30
|
2026-05-28T08:15:50.442577+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956150442_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09740692,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8374335,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00930851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.7293883,"top":0.10055866,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7390292,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.7463431,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"bounds":{"left":0.43051863,"top":0.09736632,"width":0.32280585,"height":0.90263367},"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-6234231735877047162
|
-4200521094587332216
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81455
|
2827
|
29
|
2026-05-28T08:15:48.546944+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956148546_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09740692,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8374335,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00930851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.7293883,"top":0.10055866,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7390292,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.7463431,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"bounds":{"left":0.43051863,"top":0.09736632,"width":0.32280585,"height":0.90263367},"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-1908413605128525314
|
-3732217102101747319
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All...
|
81454
|
NULL
|
NULL
|
NULL
|
|
81454
|
2827
|
28
|
2026-05-28T08:15:45.087226+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956145087_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09740692,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8374335,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00930851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.7293883,"top":0.10055866,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7390292,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.7463431,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"bounds":{"left":0.43051863,"top":0.09736632,"width":0.32280585,"height":0.90263367},"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
327918965576722946
|
-3660159508063819383
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81453
|
2827
|
27
|
2026-05-28T08:15:36.886474+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956136886_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09740692,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8374335,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00930851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.7293883,"top":0.10055866,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7390292,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.7463431,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"bounds":{"left":0.43051863,"top":0.09736632,"width":0.32280585,"height":0.90263367},"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-2059073067913265170
|
-4200521094587332216
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}...
|
81452
|
NULL
|
NULL
|
NULL
|
|
81452
|
2827
|
26
|
2026-05-28T08:15:35.236604+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956135236_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09740692,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8374335,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00930851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.7293883,"top":0.10055866,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7390292,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.7463431,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"bounds":{"left":0.43051863,"top":0.09736632,"width":0.32280585,"height":0.90263367},"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
327918965576722946
|
-3660159508063819383
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81451
|
2827
|
25
|
2026-05-28T08:15:29.116657+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956129116_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09740692,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8374335,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00930851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.7293883,"top":0.10055866,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7390292,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.7463431,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"bounds":{"left":0.43051863,"top":0.09736632,"width":0.32280585,"height":0.90263367},"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5203598102691296047
|
-3624130711044855415
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options...
|
81449
|
NULL
|
NULL
|
NULL
|
|
81450
|
2826
|
20
|
2026-05-28T08:15:25.280154+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956125280_m1.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.019444445,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
327918965576722946
|
-3660159508063819383
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81449
|
2827
|
24
|
2026-05-28T08:15:24.014801+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956124014_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09740692,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8374335,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00930851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.7293883,"top":0.10055866,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7390292,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.7463431,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"bounds":{"left":0.43051863,"top":0.09736632,"width":0.32280585,"height":0.90263367},"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
327918965576722946
|
-3660159508063819383
|
visual_change
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81447
|
2826
|
19
|
2026-05-28T08:15:23.144584+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956123144_m1.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.019444445,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-7873217691253624362
|
-3624060342283908727
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…...
|
81446
|
NULL
|
NULL
|
NULL
|
|
81448
|
2827
|
23
|
2026-05-28T08:15:22.028433+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956122028_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09740692,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8374335,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5165548209905977393
|
-7627414387934682686
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
PhpStormViewNeweNiCCoocKetucioTOOI-WindowFV faVsco.s ~$2 JY-20915-fix-missing-header-text-rela)proideta Kemnelonip© SyncMailbox.phpWockhrhta© InternetMessagelnterface.ph© MailChannelService.phgTextRelayServiceTest.phpo Textrelkysewice.on0 MeetinaGeneratoomcadonên OAuth2Dn Playbooks178—KeCaLAJotaeoDn Streamingla Teama Telechony#UserPilotWebhookC Abstrac Semvice cho©ActivityProviderFactory.php© ActivityService.php© ApiResponseService.oho187188189190Ceonarsneasaries oodg InsightSeatService.phpC InstantMeetingService.phgcilntecomswneoeC IpapiClient.php© IpapiService.phpC ParticipantShareService.phg©. PlanhatService.php© PlaybackService.phpPlaybackVideoOnlyService.pho© PlaybookCategoryService.php© PlaylistGeneratorinterface.php© ResolveTeamCrmConnection.ph© SimoleThrottleService.ohgC SlackService.oho© SoclalAccountService.oho)C SoftPhoneService.oho19319419S ©) TeamOwnerService.ohoC) TeamService.oho213C) TranscodeParameterResolver.ot 215C UserSemce.onC Uuidloho> M Traitc› @ UseCasesiMusAUtils>E Va cation215218Mo halnore nhr@ tnitislGrantondGtnto nhoeillliminnv nhnA Olen nhí3.env.productionclass TextRelayServiceivare tuncczon asroncurehichvarohaehtsercosusens nos moreoordemhoy,hesoeosmesshac->actPavlondio->oc-Headeneiorheaders as Sheader) 1eader->name === "X-6n-0riginal-To') tatches=Sthis->natchessxnectedPecsinent/SheaderswallueSexoectedAbias.Sexoecterhostr((!Smatches) ?Sanitize PIl by renoving plus-tag content for loggingSsanitizedOriginalto = explode( separator: *+*, Sheader->value)(0)=Log::info( message:' TextRelayService) Refused message','message id' => Snessageid,'original to sanitized' a› SsanitizedoriginaltoI):nol messase:Tex kelayservice Kerused nessace: eissing x-bn-Urzoznal-To headen'oc10 = Snessagclors present' => array nap(fn (Sh) => Sh->nane . *: • . explode( separator: *+*. Sh->value) (0 A(messaoe: "[TextRelavServicel Failed to insoect ressaac*. flge_id' => Smessageldtion' = Se->aetMessageo)anwure syceor lone"atchesExpectedReciplent(string Sreciplent, string SexpectedAlias, string SexpectedHost: bool...1 edit+v Accept Fle x- X Reject File ox cSF fiminny@localhost)HS.Jocal jiminny@blocalhost)& console [PROD)# consoe leu.2826-85-28 87:47:501 Local.INF0: Sparansnzscorylypes = nessageaddeestartHistoryId) => 359921correlazon10:YaC0//105-02715-4442-8886-7268823530901d":*87f39623-3deb-4827-aBcf-b862acc93289**7826-85-28 87:47:58 Local.IWFU: Snessagchzscomvtrace10: [CREDIT_CARD]-a8cт-0862aec93789-17876-85528 87-S8857omuotfteSoamaneIhistorylypes => messageAddecstartHistorytoll50%2cornellatiionsd"."ph67/206-7350-172h-6789-40a7456066550 "trace 3o".=62a0Rohp-d660-hcAu-h1de-R/3ceccba6at*2826-85-28 R7-59-5811 1oclTNED: Snessadels stomuEconnelatton Saw."ehf71z0h-775a.170h-h780.40.7456e01550 "ьласA S/.-6290R0/9-1640-6604-h1de-9/3cecchaf91»)2924-85.28 87-55-17 | 1o601 TNEh• SoananshistoryTypes => messageAddecAwAtU&AtAAuTAl = 26002-4d4a-b84b-a8531ca414a7"1300x.06.29 92-66-171 Jocol-1w51: Snossagchs stom.#"correlation_id":"25971837-3161-431b-adS9-e898eb48beSf*, *trace_id*:*c3463c2c-d21b-4d4a-b84b-a8531ca614a7"}(2826-85-28 88:88:531 Zocal, INF0: Sparansmistorvnvoes => nessagenddedistartHistorvidl s 350925connel atson o""naosynoo%ctheyerark -chn chcoaiieuooace aeuei aioowe ser.oSec-hsyiuhichthuOierhoR RRohieSaioetst Srassadelsrom#[CREDIT_CARD].05cc-H529269c24h7"2924-85-28 AR-95-27| Toeol TNSh: SoananeTO0У L7• Thu 28 May 11:15:21TextRelayServiceTestler henwserwietCiniishet somewnete now on stagina or oroduction?Yes. The next time the sync runs and hits a message without X-e-original-lo, the warning will now include teaoeis prescht in the log contextheckit in Cloud Watch or wherever woduet onlstac na loos go) Saarch tor• oTextRelaySeruice Refused message: miosing Xec orioins -to hesdeThe log entry will now look like"Delivered-To: catch-all""To: catchesh"The Sync runs on a schedule = check kernel pho to see now trequendyineschtouletkewinseo.tarescnectlineoloworconto.thcommnotsrhorttex.erclav.swmx=yolcan trigger it manually right now on staging/production to get the new log output immediately:docker exec -it docker lamp 1 php artisan mailbox: text-relay: syndThen check the lons straight after. If there's a refused messace in the current history window vou'll see the new nahrAeachntsrrau.n thowarninaloammodistow MarashtawetwthscehhahiNhet ehois th-@ eodeAdhotvAReinctalAcceot allXwodeurlhimewrekhirest4 spad...
|
81445
|
NULL
|
NULL
|
NULL
|
|
81446
|
2826
|
18
|
2026-05-28T08:15:20.510788+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956120510_m1.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.019444445,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
327918965576722946
|
-3660159508063819383
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81445
|
2827
|
22
|
2026-05-28T08:15:20.399260+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956120399_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09740692,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8374335,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00930851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.7293883,"top":0.10055866,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7390292,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.7463431,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"bounds":{"left":0.43051863,"top":0.09736632,"width":0.32280585,"height":0.90263367},"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"bounds":{"left":0.011968086,"top":0.047885075,"width":0.024268618,"height":0.024740623},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5203598102691296047
|
-3624130711044855415
|
typing_pause
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81444
|
2827
|
21
|
2026-05-28T08:15:11.087907+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956111087_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09740692,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8374335,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00930851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"bounds":{"left":0.7293883,"top":0.10055866,"width":0.007978723,"height":0.015163607},"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.7390292,"top":0.09896249,"width":0.00731383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.7463431,"top":0.09896249,"width":0.006981383,"height":0.018355945},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"bounds":{"left":0.43051863,"top":0.09736632,"width":0.32280585,"height":0.90263367},"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-2059073067913265170
|
-4200521094587332216
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}...
|
81442
|
NULL
|
NULL
|
NULL
|
|
81443
|
2826
|
17
|
2026-05-28T08:15:09.777635+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956109777_m1.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-5165548209905977393
|
-7627414387934682686
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
SlackFileEditViewGoHistoryWindowHelpDOCKER₴81DEV (docker)₴2-zshscreenpipe"Digest:sha256:d6b7ebd9b71c0480b909d77fceada3df720c6594e4c853228765509b36f52db6Status: Imageis up to date for 438740370364.dkr.ecr.us-east-2.amazonaws.com/jiminny/app/‹438740370364.dkr.ecr.us-east-2.amazonaws.com/jiminny/app/qa:arm64v8-latestWhat's next:View a summary ofimage vulnerabilities and recommendations docker scoutquickviewlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix--config=.php-cs-fixer.dist.plPHP CS Fixer 3.95.1 Adalbertus by Fabien Potencier, Dariusz Ruminskiand contributors.PHP runtime: 8.5.5Loadedconfigdefault from ".php-cs-fixer.dist.php"Running analysis on 7cores with 10files perprocess.5698/5698100%1) app/Services/Mail/TextRelayService.php (statement_indentation)begin diff/home/jiminny/app/Services/Mail/TextRelayService.php+++/home/jiminny/app/Services/Mail/TextRelayService.php-67,7 +67,7 @eSjob = new EmailTextRelay(SmessageId, SrelayedText);Sjob-›onQueue(Constants: :QUEUE_EMAILS);dispatch($job);dispatch($job);Log: :info(' [TextRelayService]Successfully dispatched message', ["message_id'= SmessageId,end diffFixed 1 of 5698 files in 84.046 seconds, 701.18 MBmemory usedDetected deprecations in use (they will stop working in next major release)- Rule set"@PHP74Migration" is deprecated. Use"@PHP7x4Migration"instead.- Rule set "@PHP80Migration" is deprecated. Use "@PHP8x®Migration" instead.- Rule set "@PHP81Migration" is deprecated. Use "@PHP8x1Migration" instead.- Rule set "@PHP82Migration" is deprecated. Use "@PHP8x2Migration" instead.- Rule set "@PHP83Migration" is deprecated. Use "@PHP8x3Migration"instead.- Rule set "@PHP84Migration" is deprecated. Use "@PHP8x4Migration" instead.What's next:Try Docker Debug for seamless, persistent debugging tools in any containeror image »Learn more at https://docs.docker.com/go/debug-cli/lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-• •HomeDMsActivityFilesLater..•More+ED→Jiminny... vw Starrea& platform-backend-...platform-inner-teamChannels# ai-chapter# alerts# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday# jbu-team-info# jiminny-bg# platform-tickets# product_launches# random# releases# support# thank-yous# the_people_of_jimi...• Direct messages& Petko Kashinski% Galya Dimitrova&. Stefka Stoyanova: Todor StamatovStalivan Ganraiou100% <78• Thu 28 May 11:15:09Describe what you are looking for®* & platform-inner-team& 106 0• MessagesP Channel OverviewMore v+Yesterday ~Resolve rrir v.oiotPreview in SlackStatusBacklogPriority= MediumAssigneeUnassignedAs of yesterday at 6:20 PM RefreshOpen in Jira*+ SummarizeToday ~NewNikolay Nikolov 10:52 AMФикс за пускане на remote commands за staging:https://github.com/jiminny/app/pull/12132#12132 Fix stage profile for remote commandsrunJIRA: JY-XXXXDeployment notes:• NoneComments1V1jiminny/app| May 26th Added by GitHubMessage & platform-inner-team...
|
81441
|
NULL
|
NULL
|
NULL
|
|
81441
|
2826
|
16
|
2026-05-28T08:15:08.663215+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956108663_m1.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.019444445,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-2059073067913265170
|
-4200521094587332216
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81442
|
2827
|
20
|
2026-05-28T08:15:08.527901+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956108527_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormViewNeweNiCCoocKelucioTOOI-WindowFV faVsco PhpStormViewNeweNiCCoocKelucioTOOI-WindowFV faVsco.s ~$ JY-20915-fix-missing-header-text-reproideta Kemnelonip© SyncMailbox.phpmockhrhts© InternetMessagelnterface.ph© MailChannelService.phgTextRelayServiceTest.phpo Textrelkysewice.on0 MeetinaGeneratoomcadonên OAuth2Dn Playbooks178—KeCaLA885JotaeoDn Streamingla Teama Telechony#UserPilotWebhook187188C Abstrac Semvice cho©ActivitvProviderFactory.oho189190© ActivityService.php© ApiResponseService.oho191192Ceonarsneasaries ood193g InsightSeatService.phpC InstantMeetingService.phpcilntecomswneoe19419S196C IpapiClient.php© IpapiService.php197198C ParticipantShareService.phg199©. PlanhatService.php© PlaybackService.php201PlaybackVideoOnlyService.pho© PlaybookCategoryService.php28© PlaylistGeneratorinterface.phpe PocaivaTnametmeonnecoon© SimoleThrottleService.ohg© SlackService.oho© SoclalAccountService.oho)284207C SoftPhoneService.oho209©) TeamOwnerService.ohoC) TeamService.ohoC) TranscodeParameterResolver.oh213213C UserSemce.onC Uuidloho> M Traitc› @ UseCases215218>E Va cationMo halnore nhr@ tnitislGrantondGtnto nhoeillliminnv nhnfyminny.ohe3.env.productionclass TextRelayServiceivare tuncczon asroncurehichvarohaehtsercosusens nos moreoordemhoy,hesoeosmesshac->actPavlondio->oc-Headeneiorheaders as Sheader) 1eader->name === "X-6n-0riginal-To') tatches=Sthis->natchessxnectedPecsinent/SheaderswallueSexoectedAbias.Sexoecterhostr((!Smatches) ?Sanitize PIl by renoving plus-tag content for loggingSsanitizedOriginalto = explode( separator: *+*, Sheader-›value)(0)=Log::info( message:' TextRelayService) Refused message','message id' => Snessageid,'original to sanitized' a› SsanitizedoriginaltoDD:nol messase:Tex kelayservice Kerused nessace: eissing x-bn-Urzoznal-To headen'oc10 = Snessagclors present' => array nap(fn (Sh) => Sh->nane . *: • . explode( separator: *+*. Sh->value) (0 A(messaoe: "[TextRelavServicel Failed to insoect ressaac*. flge_id' => Smessageldtion' => Se->aetMessace@))anwure syceor lone"atchesExpectedReciplent(string Sreciplent, string SexpectedAlias, string SexpectedHost: bool...Qua fier can be replaced with an importWindsurt: Explain & Fix. 10ev Accept Fle xeX Reject File oxcSF (iminny@localhost)HS.Jocal jiminny@blocalhost)& console [PROD)# consoe leu.2826-85-28 87:47:501 Local.INF0: Sparansnzscorylypes = nessageaddeestartHistoryId) => 359921correlazon10:YaC0//105-02715-4442-8886-7268823530901d":*87f39623-3deb-4827-aBcf-b862acc93289**17826-85-28 87:47:58 Local.wFu: Snessagchzscorytrace10: [CREDIT_CARD]-a8cт-0862aec93789-17876-85528 87-S8857omuotfteSoamaneIhistorylypes => messageAddecstartHistorytoll50%2cornellatiionsd"."ph67/206-7350-172h-6789-40a7456066550 "trace 3o".=62a0Rohp-d660-hcAu-h1de-R/3ceccba6at*2826-85-28 R7-59-5811 1oclTNED: Snessadels stomuEconnelatton Saw."ehf71z0h-775a.170h-h780.40.7456e01550 "ьласA S/.-6290R0/9-1640-6604-h1de-9/3cecchaf91»)2924-85.28 87-55-17 | 1o601 TNEh• SoananshistoryTypes => messageAddecAwAtU&AtAAuTAl = 26002-4d4a-b84b-a8531ca414a7"1300x.06.29 92-66-171 Jocol-1w51: Snossagchs stom.#"correlation_id":"25971837-3161-431b-adS9-e898eb48beSf*, *trace_id*:*c3463c2c-d21b-4d4a-b84b-a8531ca614a7"}(2826-85-28 88:88:531 Zocal, INF0: Sparansmistorvnvoes => nessagenddedistartHistorvidl s 350925connel atson o""naosynoo%ctheyerark -chn chcoaiieuooace aeuei aioowe ser.oSec-hsyiuhichthuierhooR neohieSa oetet shacsadel stom#[CREDIT_CARD].05cc-H529269c24h7"2924-85-28 AR-95-27| Toeol TNSh: SoananeTO0У L7• Thu 28 May 11:15:08TextRelayServiceTestrner hensewietCiniishet somewnete now on stagina or oroduction?Yes. The next time the sync runs and hits a message without X-e-original-lo, the warning will now include teaoeis prescht in the log contextheckit in Cloud Watch or wherever woduet onlstac na loos go) Saarch tor• oTextRelaySeruice Refused message: miosing Xec orioins -to hesdeThe log entry will now look like"Delivered-To: catch-all""To: catchesh"The Sync runs on a schedule = check kernel pho to see now trequendyineschtouletkewinseo.tarescnectlineoloworconto.thcommnotsrhorttex.erclav.swmx=yolcan trigger it manually right now on staging/production to get the new log output immediatelydocker exec -it docker lamp 1 php artisan mailbox: text-relay: syndThen check the lons straight after. If there's a refused messace in the current history window vou'll see the new nahrAeachntsrrau.n thowarninaloammodistow MarashtawetwthscehhahiCan I maybe fetch it form the amail- @ CodnAdhotvAReinctalAcceot allXwodeurlhimewrekhirest4 spad...
|
NULL
|
-6539081771006680042
|
NULL
|
visual_change
|
ocr
|
NULL
|
PhpStormViewNeweNiCCoocKelucioTOOI-WindowFV faVsco PhpStormViewNeweNiCCoocKelucioTOOI-WindowFV faVsco.s ~$ JY-20915-fix-missing-header-text-reproideta Kemnelonip© SyncMailbox.phpmockhrhts© InternetMessagelnterface.ph© MailChannelService.phgTextRelayServiceTest.phpo Textrelkysewice.on0 MeetinaGeneratoomcadonên OAuth2Dn Playbooks178—KeCaLA885JotaeoDn Streamingla Teama Telechony#UserPilotWebhook187188C Abstrac Semvice cho©ActivitvProviderFactory.oho189190© ActivityService.php© ApiResponseService.oho191192Ceonarsneasaries ood193g InsightSeatService.phpC InstantMeetingService.phpcilntecomswneoe19419S196C IpapiClient.php© IpapiService.php197198C ParticipantShareService.phg199©. PlanhatService.php© PlaybackService.php201PlaybackVideoOnlyService.pho© PlaybookCategoryService.php28© PlaylistGeneratorinterface.phpe PocaivaTnametmeonnecoon© SimoleThrottleService.ohg© SlackService.oho© SoclalAccountService.oho)284207C SoftPhoneService.oho209©) TeamOwnerService.ohoC) TeamService.ohoC) TranscodeParameterResolver.oh213213C UserSemce.onC Uuidloho> M Traitc› @ UseCases215218>E Va cationMo halnore nhr@ tnitislGrantondGtnto nhoeillliminnv nhnfyminny.ohe3.env.productionclass TextRelayServiceivare tuncczon asroncurehichvarohaehtsercosusens nos moreoordemhoy,hesoeosmesshac->actPavlondio->oc-Headeneiorheaders as Sheader) 1eader->name === "X-6n-0riginal-To') tatches=Sthis->natchessxnectedPecsinent/SheaderswallueSexoectedAbias.Sexoecterhostr((!Smatches) ?Sanitize PIl by renoving plus-tag content for loggingSsanitizedOriginalto = explode( separator: *+*, Sheader-›value)(0)=Log::info( message:' TextRelayService) Refused message','message id' => Snessageid,'original to sanitized' a› SsanitizedoriginaltoDD:nol messase:Tex kelayservice Kerused nessace: eissing x-bn-Urzoznal-To headen'oc10 = Snessagclors present' => array nap(fn (Sh) => Sh->nane . *: • . explode( separator: *+*. Sh->value) (0 A(messaoe: "[TextRelavServicel Failed to insoect ressaac*. flge_id' => Smessageldtion' => Se->aetMessace@))anwure syceor lone"atchesExpectedReciplent(string Sreciplent, string SexpectedAlias, string SexpectedHost: bool...Qua fier can be replaced with an importWindsurt: Explain & Fix. 10ev Accept Fle xeX Reject File oxcSF (iminny@localhost)HS.Jocal jiminny@blocalhost)& console [PROD)# consoe leu.2826-85-28 87:47:501 Local.INF0: Sparansnzscorylypes = nessageaddeestartHistoryId) => 359921correlazon10:YaC0//105-02715-4442-8886-7268823530901d":*87f39623-3deb-4827-aBcf-b862acc93289**17826-85-28 87:47:58 Local.wFu: Snessagchzscorytrace10: [CREDIT_CARD]-a8cт-0862aec93789-17876-85528 87-S8857omuotfteSoamaneIhistorylypes => messageAddecstartHistorytoll50%2cornellatiionsd"."ph67/206-7350-172h-6789-40a7456066550 "trace 3o".=62a0Rohp-d660-hcAu-h1de-R/3ceccba6at*2826-85-28 R7-59-5811 1oclTNED: Snessadels stomuEconnelatton Saw."ehf71z0h-775a.170h-h780.40.7456e01550 "ьласA S/.-6290R0/9-1640-6604-h1de-9/3cecchaf91»)2924-85.28 87-55-17 | 1o601 TNEh• SoananshistoryTypes => messageAddecAwAtU&AtAAuTAl = 26002-4d4a-b84b-a8531ca414a7"1300x.06.29 92-66-171 Jocol-1w51: Snossagchs stom.#"correlation_id":"25971837-3161-431b-adS9-e898eb48beSf*, *trace_id*:*c3463c2c-d21b-4d4a-b84b-a8531ca614a7"}(2826-85-28 88:88:531 Zocal, INF0: Sparansmistorvnvoes => nessagenddedistartHistorvidl s 350925connel atson o""naosynoo%ctheyerark -chn chcoaiieuooace aeuei aioowe ser.oSec-hsyiuhichthuierhooR neohieSa oetet shacsadel stom#[CREDIT_CARD].05cc-H529269c24h7"2924-85-28 AR-95-27| Toeol TNSh: SoananeTO0У L7• Thu 28 May 11:15:08TextRelayServiceTestrner hensewietCiniishet somewnete now on stagina or oroduction?Yes. The next time the sync runs and hits a message without X-e-original-lo, the warning will now include teaoeis prescht in the log contextheckit in Cloud Watch or wherever woduet onlstac na loos go) Saarch tor• oTextRelaySeruice Refused message: miosing Xec orioins -to hesdeThe log entry will now look like"Delivered-To: catch-all""To: catchesh"The Sync runs on a schedule = check kernel pho to see now trequendyineschtouletkewinseo.tarescnectlineoloworconto.thcommnotsrhorttex.erclav.swmx=yolcan trigger it manually right now on staging/production to get the new log output immediatelydocker exec -it docker lamp 1 php artisan mailbox: text-relay: syndThen check the lons straight after. If there's a refused messace in the current history window vou'll see the new nahrAeachntsrrau.n thowarninaloammodistow MarashtawetwthscehhahiCan I maybe fetch it form the amail- @ CodnAdhotvAReinctalAcceot allXwodeurlhimewrekhirest4 spad...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81440
|
2827
|
19
|
2026-05-28T08:15:05.255234+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956105255_m2.jpg...
|
Firefox
|
Text message from 087 787 8118 - lukas.kovalik@jim Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail — Work...
|
1
|
mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLq mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLqvLLsBfWCCDMqrP...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738
app 5648 unread has menu
app
5,648
extension 217 unread has menu
extension
217
vuejs has menu
vuejs
JIRA has menu
JIRA
Collapse label: Notes
Notes expanded has menu
Notes
Shared has menu
Shared
Sentry 103246 unread has menu
Sentry
103,246
Utilities has menu
Utilities
New chat
New chat
Shortcuts
Shortcuts
Home
Mentions
star
Starred
Ask Gemini
Direct messages 31 unread messages
Direct messages
31
Unread Daily - Platform - May 28Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Daily - Platform - May 28
Meeting conversation
Open in a pop-up...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0018284575,"top":0.0518755,"width":0.038065158,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.039893616,"top":0.0518755,"width":0.037898935,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.09497207,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.10614525,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"BE upgrade libraries","depth":4,"bounds":{"left":0.0028257978,"top":0.13288109,"width":0.03939495,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":4,"bounds":{"left":0.0,"top":0.15203512,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.1632083,"width":0.12799202,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Text relay","depth":4,"bounds":{"left":0.0028257978,"top":0.18994413,"width":0.020279255,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0028257978,"top":0.21348763,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"bounds":{"left":0.015957447,"top":0.22466081,"width":0.04288564,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.2462091,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.25738227,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.27893057,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.29010376,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.31165203,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.32282522,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":4,"bounds":{"left":0.0028257978,"top":0.3443735,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":5,"bounds":{"left":0.015957447,"top":0.35554668,"width":0.1278258,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.37709498,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.38826814,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.40981644,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.42098963,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"bounds":{"left":0.0,"top":0.4425379,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"bounds":{"left":0.013297873,"top":0.4537111,"width":0.042220745,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.47525936,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.48643255,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login | Salesforce","depth":4,"bounds":{"left":0.0,"top":0.5079808,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login | Salesforce","depth":5,"bounds":{"left":0.013297873,"top":0.519154,"width":0.030917553,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":4,"bounds":{"left":0.0,"top":0.54070234,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":5,"bounds":{"left":0.013297873,"top":0.5518755,"width":0.24301861,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.5734238,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.013297873,"top":0.584597,"width":0.13248006,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.5806065,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":4,"bounds":{"left":0.0,"top":0.60614526,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.61731845,"width":0.08543883,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.6388667,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.6500399,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.6715882,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.6827614,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":4,"bounds":{"left":0.0,"top":0.70430964,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.71548283,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.7370311,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.7482043,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.7713488,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Trimmed content expanded.","depth":2,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Using Jiminny Mail with screen readers","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Using Jiminny Mail with screen readers","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Main menu","depth":5,"bounds":{"left":0.08294548,"top":0.058260176,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Gmail","depth":6,"bounds":{"left":0.1022274,"top":0.061452515,"width":0.043550532,"height":0.035115723},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Search mail","depth":6,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search mail","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Ask Gmail","depth":12,"bounds":{"left":0.20561835,"top":0.06943336,"width":0.18916224,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Advanced search options","depth":6,"bounds":{"left":0.40807846,"top":0.058260176,"width":0.01861702,"height":0.03671189},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search mail","depth":6,"bounds":{"left":0.18733378,"top":0.058260176,"width":0.01861702,"height":0.03671189},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Status: Active","depth":6,"bounds":{"left":0.83627,"top":0.058260176,"width":0.03357713,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Support","depth":7,"bounds":{"left":0.87450135,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Settings","depth":7,"bounds":{"left":0.889129,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Studio","depth":6,"bounds":{"left":0.9030917,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ask Gemini","depth":7,"bounds":{"left":0.91638964,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Google apps","depth":8,"bounds":{"left":0.9303524,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":6,"bounds":{"left":0.94763964,"top":0.058260176,"width":0.04720745,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":9,"bounds":{"left":0.9808843,"top":0.06464485,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Mail, 1731 unread messages","depth":3,"bounds":{"left":0.08361037,"top":0.11731844,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Mail","depth":3,"bounds":{"left":0.07962101,"top":0.14285715,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mail","depth":4,"bounds":{"left":0.087101065,"top":0.14325619,"width":0.0076462766,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat, 32 unread messages","depth":3,"bounds":{"left":0.08361037,"top":0.16839585,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"32 new messages","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Chat","depth":3,"bounds":{"left":0.07962101,"top":0.19393456,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Chat","depth":4,"bounds":{"left":0.0866024,"top":0.1943336,"width":0.008643617,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Meet","depth":3,"bounds":{"left":0.08361037,"top":0.21947326,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Meet","depth":3,"bounds":{"left":0.07962101,"top":0.24501197,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meet","depth":4,"bounds":{"left":0.086269945,"top":0.24541101,"width":0.00930851,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Compose","depth":3,"bounds":{"left":0.10621676,"top":0.10933759,"width":0.046043884,"height":0.044692736},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Inbox 1731 unread","depth":11,"bounds":{"left":0.12350399,"top":0.17238627,"width":0.012466756,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox","depth":12,"bounds":{"left":0.12350399,"top":0.17238627,"width":0.012466756,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1,731","depth":11,"bounds":{"left":0.17287233,"top":0.1735834,"width":0.009142287,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Starred","depth":12,"bounds":{"left":0.12350399,"top":0.19792499,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Starred","depth":13,"bounds":{"left":0.12350399,"top":0.19792499,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Snoozed","depth":12,"bounds":{"left":0.12350399,"top":0.22346368,"width":0.018284574,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Snoozed","depth":13,"bounds":{"left":0.12350399,"top":0.22346368,"width":0.018284574,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sent","depth":12,"bounds":{"left":0.12350399,"top":0.2490024,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sent","depth":13,"bounds":{"left":0.12350399,"top":0.2490024,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Drafts 3 unread","depth":12,"bounds":{"left":0.12350399,"top":0.2745411,"width":0.013796543,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Drafts","depth":13,"bounds":{"left":0.12350399,"top":0.2745411,"width":0.013796543,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":12,"bounds":{"left":0.17985372,"top":0.27573824,"width":0.0021609042,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Categories","depth":9,"bounds":{"left":0.105884306,"top":0.29928172,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Categories expanded","depth":11,"bounds":{"left":0.12350399,"top":0.30007982,"width":0.022938829,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Categories","depth":12,"bounds":{"left":0.12350399,"top":0.30007982,"width":0.022938829,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Purchases 2 unread has menu","depth":11,"bounds":{"left":0.12616356,"top":0.3256185,"width":0.023603724,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Purchases","depth":12,"bounds":{"left":0.12616356,"top":0.3256185,"width":0.023603724,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":11,"bounds":{"left":0.17985372,"top":0.32681563,"width":0.0021609042,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More labels","depth":7,"bounds":{"left":0.10621676,"top":0.34557062,"width":0.07978723,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"More","depth":9,"bounds":{"left":0.12350399,"top":0.35115722,"width":0.010804521,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Labels","depth":6,"bounds":{"left":0.11087101,"top":0.3934557,"width":0.06582447,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":7,"bounds":{"left":0.11087101,"top":0.3934557,"width":0.016456118,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create new label","depth":6,"bounds":{"left":0.17669548,"top":0.39385474,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"App emails has menu","depth":12,"bounds":{"left":0.12350399,"top":0.42498004,"width":0.023271276,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"App emails","depth":13,"bounds":{"left":0.12350399,"top":0.42498004,"width":0.023271276,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Github","depth":10,"bounds":{"left":0.105884306,"top":0.44972068,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Github 738 unread expanded has menu","depth":12,"bounds":{"left":0.12350399,"top":0.45051876,"width":0.015458777,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Github","depth":13,"bounds":{"left":0.12350399,"top":0.45051876,"width":0.015458777,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"738","depth":12,"bounds":{"left":0.17569813,"top":0.4517159,"width":0.0063164895,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app 5648 unread has menu","depth":12,"bounds":{"left":0.12616356,"top":0.47605747,"width":0.008643617,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":13,"bounds":{"left":0.12616356,"top":0.47605747,"width":0.008643617,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5,648","depth":12,"bounds":{"left":0.17204122,"top":0.4772546,"width":0.009973404,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"extension 217 unread has menu","depth":12,"bounds":{"left":0.12616356,"top":0.50159615,"width":0.021941489,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"extension","depth":13,"bounds":{"left":0.12616356,"top":0.50159615,"width":0.021941489,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"217","depth":12,"bounds":{"left":0.17619681,"top":0.5027933,"width":0.005817819,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"vuejs has menu","depth":12,"bounds":{"left":0.12616356,"top":0.5271349,"width":0.010804521,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"vuejs","depth":13,"bounds":{"left":0.12616356,"top":0.5271349,"width":0.010804521,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JIRA has menu","depth":12,"bounds":{"left":0.12350399,"top":0.5526736,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JIRA","depth":13,"bounds":{"left":0.12350399,"top":0.5526736,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Notes","depth":10,"bounds":{"left":0.105884306,"top":0.5774142,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Notes expanded has menu","depth":12,"bounds":{"left":0.12350399,"top":0.57821226,"width":0.012300532,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notes","depth":13,"bounds":{"left":0.12350399,"top":0.57821226,"width":0.012300532,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Shared has menu","depth":12,"bounds":{"left":0.12616356,"top":0.603751,"width":0.014960106,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Shared","depth":13,"bounds":{"left":0.12616356,"top":0.603751,"width":0.014960106,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sentry 103246 unread has menu","depth":12,"bounds":{"left":0.12350399,"top":0.6292897,"width":0.01512633,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sentry","depth":13,"bounds":{"left":0.12350399,"top":0.6292897,"width":0.01512633,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"103,246","depth":12,"bounds":{"left":0.16788563,"top":0.63048685,"width":0.01412899,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Utilities has menu","depth":12,"bounds":{"left":0.12350399,"top":0.6548284,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Utilities","depth":13,"bounds":{"left":0.12350399,"top":0.6548284,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New chat","depth":2,"bounds":{"left":0.08361037,"top":0.10933759,"width":0.045877658,"height":0.044692736},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New chat","depth":4,"bounds":{"left":0.1008976,"top":0.1245012,"width":0.020611702,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Shortcuts","depth":5,"bounds":{"left":0.08361037,"top":0.16839585,"width":0.005984043,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Shortcuts","depth":6,"bounds":{"left":0.08959442,"top":0.16520351,"width":0.06881649,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Home","depth":6,"bounds":{"left":0.10023271,"top":0.18994413,"width":0.013131649,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mentions","depth":6,"bounds":{"left":0.10023271,"top":0.2122905,"width":0.019281914,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"star","depth":6,"bounds":{"left":0.087932184,"top":0.23224261,"width":0.0066489363,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Starred","depth":6,"bounds":{"left":0.10023271,"top":0.23463687,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Ask Gemini","depth":6,"bounds":{"left":0.10023271,"top":0.25698325,"width":0.0234375,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Direct messages 31 unread messages","depth":5,"bounds":{"left":0.08361037,"top":0.28651237,"width":0.005984043,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Direct messages","depth":6,"bounds":{"left":0.08959442,"top":0.28332004,"width":0.059507977,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"31","depth":7,"bounds":{"left":0.15325798,"top":0.28731045,"width":0.003656915,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Unread Daily - Platform - May 28Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"bounds":{"left":0.087932184,"top":0.28172386,"width":0.0731383,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"bounds":{"left":0.099567816,"top":0.292498,"width":0.01761968,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Daily - Platform - May 28","depth":14,"bounds":{"left":0.09990027,"top":0.2857143,"width":0.059175532,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"bounds":{"left":0.099567816,"top":0.2932961,"width":0.05069814,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in a pop-up","depth":14,"bounds":{"left":0.15109707,"top":0.27334398,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-5782273457291483587
|
-1097370323972984635
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738
app 5648 unread has menu
app
5,648
extension 217 unread has menu
extension
217
vuejs has menu
vuejs
JIRA has menu
JIRA
Collapse label: Notes
Notes expanded has menu
Notes
Shared has menu
Shared
Sentry 103246 unread has menu
Sentry
103,246
Utilities has menu
Utilities
New chat
New chat
Shortcuts
Shortcuts
Home
Mentions
star
Starred
Ask Gemini
Direct messages 31 unread messages
Direct messages
31
Unread Daily - Platform - May 28Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Daily - Platform - May 28
Meeting conversation
Open in a pop-up...
|
81439
|
NULL
|
NULL
|
NULL
|
|
81439
|
2827
|
18
|
2026-05-28T08:15:04.520978+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956104520_m2.jpg...
|
Firefox
|
Text message from 087 787 8118 - lukas.kovalik@jim Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail — Work...
|
1
|
mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLq mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLqvLLsBfWCCDMqrP...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738
app 5648 unread has menu
app
5,648
extension 217 unread has menu
extension
217
vuejs has menu...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0018284575,"top":0.0518755,"width":0.038065158,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.039893616,"top":0.0518755,"width":0.037898935,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.09497207,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.10614525,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"BE upgrade libraries","depth":4,"bounds":{"left":0.0028257978,"top":0.13288109,"width":0.03939495,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":4,"bounds":{"left":0.0,"top":0.15203512,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.1632083,"width":0.12799202,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Text relay","depth":4,"bounds":{"left":0.0028257978,"top":0.18994413,"width":0.020279255,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0028257978,"top":0.21348763,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"bounds":{"left":0.015957447,"top":0.22466081,"width":0.04288564,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.2462091,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.25738227,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.27893057,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.29010376,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.31165203,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.32282522,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":4,"bounds":{"left":0.0028257978,"top":0.3443735,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":5,"bounds":{"left":0.015957447,"top":0.35554668,"width":0.1278258,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.37709498,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.38826814,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.40981644,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.42098963,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"bounds":{"left":0.0,"top":0.4425379,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"bounds":{"left":0.013297873,"top":0.4537111,"width":0.042220745,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.47525936,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.48643255,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login | Salesforce","depth":4,"bounds":{"left":0.0,"top":0.5079808,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login | Salesforce","depth":5,"bounds":{"left":0.013297873,"top":0.519154,"width":0.030917553,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":4,"bounds":{"left":0.0,"top":0.54070234,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":5,"bounds":{"left":0.013297873,"top":0.5518755,"width":0.24301861,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.5734238,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.013297873,"top":0.584597,"width":0.13248006,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.5806065,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":4,"bounds":{"left":0.0,"top":0.60614526,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.61731845,"width":0.08543883,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.6388667,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.6500399,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.6715882,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.6827614,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":4,"bounds":{"left":0.0,"top":0.70430964,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.71548283,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.7370311,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.7482043,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.7713488,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Trimmed content expanded.","depth":2,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Using Jiminny Mail with screen readers","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Using Jiminny Mail with screen readers","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Main menu","depth":5,"bounds":{"left":0.08294548,"top":0.058260176,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Gmail","depth":6,"bounds":{"left":0.1022274,"top":0.061452515,"width":0.043550532,"height":0.035115723},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Search mail","depth":6,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search mail","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Ask Gmail","depth":12,"bounds":{"left":0.20561835,"top":0.06943336,"width":0.18916224,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Advanced search options","depth":6,"bounds":{"left":0.40807846,"top":0.058260176,"width":0.01861702,"height":0.03671189},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search mail","depth":6,"bounds":{"left":0.18733378,"top":0.058260176,"width":0.01861702,"height":0.03671189},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Status: Active","depth":6,"bounds":{"left":0.83627,"top":0.058260176,"width":0.03357713,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Support","depth":7,"bounds":{"left":0.87450135,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Settings","depth":7,"bounds":{"left":0.889129,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Studio","depth":6,"bounds":{"left":0.9030917,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ask Gemini","depth":7,"bounds":{"left":0.91638964,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Google apps","depth":8,"bounds":{"left":0.9303524,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":6,"bounds":{"left":0.94763964,"top":0.058260176,"width":0.04720745,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":9,"bounds":{"left":0.9808843,"top":0.06464485,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Mail, 1731 unread messages","depth":3,"bounds":{"left":0.08361037,"top":0.11731844,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Mail","depth":3,"bounds":{"left":0.07962101,"top":0.14285715,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mail","depth":4,"bounds":{"left":0.087101065,"top":0.14325619,"width":0.0076462766,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat, 32 unread messages","depth":3,"bounds":{"left":0.08361037,"top":0.16839585,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"32 new messages","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Chat","depth":3,"bounds":{"left":0.07962101,"top":0.19393456,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Chat","depth":4,"bounds":{"left":0.0866024,"top":0.1943336,"width":0.008643617,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Meet","depth":3,"bounds":{"left":0.08361037,"top":0.21947326,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Meet","depth":3,"bounds":{"left":0.07962101,"top":0.24501197,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meet","depth":4,"bounds":{"left":0.086269945,"top":0.24541101,"width":0.00930851,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Compose","depth":3,"bounds":{"left":0.10621676,"top":0.10933759,"width":0.046043884,"height":0.044692736},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Inbox 1731 unread","depth":11,"bounds":{"left":0.12350399,"top":0.17238627,"width":0.012466756,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox","depth":12,"bounds":{"left":0.12350399,"top":0.17238627,"width":0.012466756,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1,731","depth":11,"bounds":{"left":0.17287233,"top":0.1735834,"width":0.009142287,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Starred","depth":12,"bounds":{"left":0.12350399,"top":0.19792499,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Starred","depth":13,"bounds":{"left":0.12350399,"top":0.19792499,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Snoozed","depth":12,"bounds":{"left":0.12350399,"top":0.22346368,"width":0.018284574,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Snoozed","depth":13,"bounds":{"left":0.12350399,"top":0.22346368,"width":0.018284574,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sent","depth":12,"bounds":{"left":0.12350399,"top":0.2490024,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sent","depth":13,"bounds":{"left":0.12350399,"top":0.2490024,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Drafts 3 unread","depth":12,"bounds":{"left":0.12350399,"top":0.2745411,"width":0.013796543,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Drafts","depth":13,"bounds":{"left":0.12350399,"top":0.2745411,"width":0.013796543,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":12,"bounds":{"left":0.17985372,"top":0.27573824,"width":0.0021609042,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Categories","depth":9,"bounds":{"left":0.105884306,"top":0.29928172,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Categories expanded","depth":11,"bounds":{"left":0.12350399,"top":0.30007982,"width":0.022938829,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Categories","depth":12,"bounds":{"left":0.12350399,"top":0.30007982,"width":0.022938829,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Purchases 2 unread has menu","depth":11,"bounds":{"left":0.12616356,"top":0.3256185,"width":0.023603724,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Purchases","depth":12,"bounds":{"left":0.12616356,"top":0.3256185,"width":0.023603724,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":11,"bounds":{"left":0.17985372,"top":0.32681563,"width":0.0021609042,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More labels","depth":7,"bounds":{"left":0.10621676,"top":0.34557062,"width":0.07978723,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"More","depth":9,"bounds":{"left":0.12350399,"top":0.35115722,"width":0.010804521,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Labels","depth":6,"bounds":{"left":0.11087101,"top":0.3934557,"width":0.06582447,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":7,"bounds":{"left":0.11087101,"top":0.3934557,"width":0.016456118,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create new label","depth":6,"bounds":{"left":0.17669548,"top":0.39385474,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"App emails has menu","depth":12,"bounds":{"left":0.12350399,"top":0.42498004,"width":0.023271276,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"App emails","depth":13,"bounds":{"left":0.12350399,"top":0.42498004,"width":0.023271276,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Github","depth":10,"bounds":{"left":0.105884306,"top":0.44972068,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Github 738 unread expanded has menu","depth":12,"bounds":{"left":0.12350399,"top":0.45051876,"width":0.015458777,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Github","depth":13,"bounds":{"left":0.12350399,"top":0.45051876,"width":0.015458777,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"738","depth":12,"bounds":{"left":0.17569813,"top":0.4517159,"width":0.0063164895,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app 5648 unread has menu","depth":12,"bounds":{"left":0.12616356,"top":0.47605747,"width":0.008643617,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":13,"bounds":{"left":0.12616356,"top":0.47605747,"width":0.008643617,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5,648","depth":12,"bounds":{"left":0.17204122,"top":0.4772546,"width":0.009973404,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"extension 217 unread has menu","depth":12,"bounds":{"left":0.12616356,"top":0.50159615,"width":0.021941489,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"extension","depth":13,"bounds":{"left":0.12616356,"top":0.50159615,"width":0.021941489,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"217","depth":12,"bounds":{"left":0.17619681,"top":0.5027933,"width":0.005817819,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"vuejs has menu","depth":12,"bounds":{"left":0.12616356,"top":0.5271349,"width":0.010804521,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
342274626927070585
|
-1124387523690696507
|
click
|
accessibility
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738
app 5648 unread has menu
app
5,648
extension 217 unread has menu
extension
217
vuejs has menu...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81438
|
2826
|
15
|
2026-05-28T08:15:04.417367+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956104417_m1.jpg...
|
Firefox
|
Text message from 087 787 8118 - lukas.kovalik@jim Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail — Work...
|
1
|
mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLq mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLqvLLsBfWCCDMqrP...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"BE upgrade libraries","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Text relay","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login | Salesforce","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login | Salesforce","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Trimmed content expanded.","depth":2,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Using Jiminny Mail with screen readers","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Using Jiminny Mail with screen readers","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Main menu","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Gmail","depth":6,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Search mail","depth":6,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search mail","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Ask Gmail","depth":12,"on_screen":true,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Advanced search options","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search mail","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Status: Active","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Support","depth":7,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Settings","depth":7,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Studio","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ask Gemini","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Google apps","depth":8,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":9,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Mail, 1731 unread messages","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Mail","depth":3,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mail","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat, 32 unread messages","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"32 new messages","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Chat","depth":3,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Chat","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Meet","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Meet","depth":3,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meet","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Compose","depth":3,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Inbox 1731 unread","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
1433427049135257573
|
-1133394722874133307
|
click
|
accessibility
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread...
|
81436
|
NULL
|
NULL
|
NULL
|
|
81437
|
2827
|
17
|
2026-05-28T08:15:01.546230+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956101546_m2.jpg...
|
Firefox
|
Text message from 087 787 8118 - lukas.kovalik@jim Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail — Work...
|
1
|
mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLq mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLqvLLsBfWCCDMqrP...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0018284575,"top":0.0518755,"width":0.038065158,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.039893616,"top":0.0518755,"width":0.037898935,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.09497207,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.10614525,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"BE upgrade libraries","depth":4,"bounds":{"left":0.0028257978,"top":0.13288109,"width":0.03939495,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":4,"bounds":{"left":0.0,"top":0.15203512,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.1632083,"width":0.12799202,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Text relay","depth":4,"bounds":{"left":0.0028257978,"top":0.18994413,"width":0.020279255,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0028257978,"top":0.21348763,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"bounds":{"left":0.015957447,"top":0.22466081,"width":0.04288564,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.2462091,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.25738227,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.27893057,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.29010376,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.31165203,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.32282522,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":4,"bounds":{"left":0.0028257978,"top":0.3443735,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":5,"bounds":{"left":0.015957447,"top":0.35554668,"width":0.1278258,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.37709498,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.38826814,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.40981644,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.42098963,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"bounds":{"left":0.0,"top":0.4425379,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"bounds":{"left":0.013297873,"top":0.4537111,"width":0.042220745,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.47525936,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.48643255,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login | Salesforce","depth":4,"bounds":{"left":0.0,"top":0.5079808,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login | Salesforce","depth":5,"bounds":{"left":0.013297873,"top":0.519154,"width":0.030917553,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":4,"bounds":{"left":0.0,"top":0.54070234,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":5,"bounds":{"left":0.013297873,"top":0.5518755,"width":0.24301861,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.5734238,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.013297873,"top":0.584597,"width":0.13248006,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.5806065,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":4,"bounds":{"left":0.0,"top":0.60614526,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.61731845,"width":0.08543883,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.6388667,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.6500399,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.6715882,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.6827614,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":4,"bounds":{"left":0.0,"top":0.70430964,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.71548283,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.7370311,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.7482043,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.7713488,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Trimmed content expanded.","depth":2,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Using Jiminny Mail with screen readers","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Using Jiminny Mail with screen readers","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Main menu","depth":5,"bounds":{"left":0.08294548,"top":0.058260176,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Gmail","depth":6,"bounds":{"left":0.1022274,"top":0.061452515,"width":0.043550532,"height":0.035115723},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Search mail","depth":6,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search mail","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Ask Gmail","depth":12,"bounds":{"left":0.20561835,"top":0.06943336,"width":0.18916224,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Advanced search options","depth":6,"bounds":{"left":0.40807846,"top":0.058260176,"width":0.01861702,"height":0.03671189},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search mail","depth":6,"bounds":{"left":0.18733378,"top":0.058260176,"width":0.01861702,"height":0.03671189},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Status: Active","depth":6,"bounds":{"left":0.83627,"top":0.058260176,"width":0.03357713,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Support","depth":7,"bounds":{"left":0.87450135,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Settings","depth":7,"bounds":{"left":0.889129,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Studio","depth":6,"bounds":{"left":0.9030917,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ask Gemini","depth":7,"bounds":{"left":0.91638964,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Google apps","depth":8,"bounds":{"left":0.9303524,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":6,"bounds":{"left":0.94763964,"top":0.058260176,"width":0.04720745,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":9,"bounds":{"left":0.9808843,"top":0.06464485,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Mail, 1731 unread messages","depth":3,"bounds":{"left":0.08361037,"top":0.11731844,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Mail","depth":3,"bounds":{"left":0.07962101,"top":0.14285715,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mail","depth":4,"bounds":{"left":0.087101065,"top":0.14325619,"width":0.0076462766,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat, 32 unread messages","depth":3,"bounds":{"left":0.08361037,"top":0.16839585,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"32 new messages","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Chat","depth":3,"bounds":{"left":0.07962101,"top":0.19393456,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Chat","depth":4,"bounds":{"left":0.0866024,"top":0.1943336,"width":0.008643617,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Meet","depth":3,"bounds":{"left":0.08361037,"top":0.21947326,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Meet","depth":3,"bounds":{"left":0.07962101,"top":0.24501197,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meet","depth":4,"bounds":{"left":0.086269945,"top":0.24541101,"width":0.00930851,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Compose","depth":3,"bounds":{"left":0.10621676,"top":0.10933759,"width":0.046043884,"height":0.044692736},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Inbox 1731 unread","depth":11,"bounds":{"left":0.12350399,"top":0.17238627,"width":0.012466756,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox","depth":12,"bounds":{"left":0.12350399,"top":0.17238627,"width":0.012466756,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1,731","depth":11,"bounds":{"left":0.17287233,"top":0.1735834,"width":0.009142287,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
4092646375783865580
|
-1061337128836205371
|
click
|
accessibility
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731...
|
81435
|
NULL
|
NULL
|
NULL
|
|
81436
|
2826
|
14
|
2026-05-28T08:15:01.392539+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956101392_m1.jpg...
|
Firefox
|
Text message from 087 787 8118 - lukas.kovalik@jim Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail — Work...
|
1
|
mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLq mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLqvLLsBfWCCDMqrP...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"BE upgrade libraries","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Text relay","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login | Salesforce","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login | Salesforce","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
2303773288568442954
|
-3241149717144345467
|
click
|
accessibility
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81435
|
2827
|
16
|
2026-05-28T08:15:00.136292+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956100136_m2.jpg...
|
Firefox
|
Text message from 087 787 8118 - lukas.kovalik@jim Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail — Work...
|
1
|
mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLq mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLqvLLsBfWCCDMqrP...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738
app 5648 unread has menu
app
5,648
extension 217 unread has menu
extension
217
vuejs has menu
vuejs
JIRA has menu
JIRA
Collapse label: Notes
Notes expanded has menu
Notes
Shared has menu
Shared
Sentry 103246 unread has menu
Sentry
103,246
Utilities has menu
Utilities
New chat
New chat
Shortcuts
Shortcuts
Home
Mentions
star
Starred
Ask Gemini
Direct messages 31 unread messages
Direct messages
31
Unread Daily - Platform - May 28Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Daily - Platform - May 28
Meeting conversation
Open in a pop-up
Options
Unread [Platform] Refinement - May 26Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
[Platform] Refinement
- May 26
Meeting conversation
Open in a pop-up
Options
Unread AI chapter - May 20Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
AI chapter - May 20
Meeting conversation
Open in a pop-up
Options
Unread Daily - Platform - May 14Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Daily - Platform - May 14
Meeting conversation
Open in a pop-up
Options
Unread Sprint Review - May 13Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Sprint Review - May 13
Meeting conversation...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0018284575,"top":0.0518755,"width":0.038065158,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.039893616,"top":0.0518755,"width":0.037898935,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.09497207,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.10614525,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"BE upgrade libraries","depth":4,"bounds":{"left":0.0028257978,"top":0.13288109,"width":0.03939495,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":4,"bounds":{"left":0.0,"top":0.15203512,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.1632083,"width":0.12799202,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Text relay","depth":4,"bounds":{"left":0.0028257978,"top":0.18994413,"width":0.020279255,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0028257978,"top":0.21348763,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"bounds":{"left":0.015957447,"top":0.22466081,"width":0.04288564,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.2462091,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.25738227,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.27893057,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.29010376,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.31165203,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.32282522,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":4,"bounds":{"left":0.0028257978,"top":0.3443735,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":5,"bounds":{"left":0.015957447,"top":0.35554668,"width":0.1278258,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.37709498,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.38826814,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.40981644,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.42098963,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"bounds":{"left":0.0,"top":0.4425379,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"bounds":{"left":0.013297873,"top":0.4537111,"width":0.042220745,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.47525936,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.48643255,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login | Salesforce","depth":4,"bounds":{"left":0.0,"top":0.5079808,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login | Salesforce","depth":5,"bounds":{"left":0.013297873,"top":0.519154,"width":0.030917553,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":4,"bounds":{"left":0.0,"top":0.54070234,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":5,"bounds":{"left":0.013297873,"top":0.5518755,"width":0.24301861,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.5734238,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.013297873,"top":0.584597,"width":0.13248006,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.5806065,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":4,"bounds":{"left":0.0,"top":0.60614526,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.61731845,"width":0.08543883,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.6388667,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.6500399,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.6715882,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.6827614,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":4,"bounds":{"left":0.0,"top":0.70430964,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.71548283,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.7370311,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.7482043,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.7713488,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Trimmed content expanded.","depth":2,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Using Jiminny Mail with screen readers","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Using Jiminny Mail with screen readers","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Main menu","depth":5,"bounds":{"left":0.08294548,"top":0.058260176,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Gmail","depth":6,"bounds":{"left":0.1022274,"top":0.061452515,"width":0.043550532,"height":0.035115723},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Search mail","depth":6,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search mail","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Ask Gmail","depth":12,"bounds":{"left":0.20561835,"top":0.06943336,"width":0.18916224,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Advanced search options","depth":6,"bounds":{"left":0.40807846,"top":0.058260176,"width":0.01861702,"height":0.03671189},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search mail","depth":6,"bounds":{"left":0.18733378,"top":0.058260176,"width":0.01861702,"height":0.03671189},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Status: Active","depth":6,"bounds":{"left":0.83627,"top":0.058260176,"width":0.03357713,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Support","depth":7,"bounds":{"left":0.87450135,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Settings","depth":7,"bounds":{"left":0.889129,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Studio","depth":6,"bounds":{"left":0.9030917,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ask Gemini","depth":7,"bounds":{"left":0.91638964,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Google apps","depth":8,"bounds":{"left":0.9303524,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":6,"bounds":{"left":0.94763964,"top":0.058260176,"width":0.04720745,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":9,"bounds":{"left":0.9808843,"top":0.06464485,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Mail, 1731 unread messages","depth":3,"bounds":{"left":0.08361037,"top":0.11731844,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Mail","depth":3,"bounds":{"left":0.07962101,"top":0.14285715,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mail","depth":4,"bounds":{"left":0.087101065,"top":0.14325619,"width":0.0076462766,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat, 32 unread messages","depth":3,"bounds":{"left":0.08361037,"top":0.16839585,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"32 new messages","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Chat","depth":3,"bounds":{"left":0.07962101,"top":0.19393456,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Chat","depth":4,"bounds":{"left":0.0866024,"top":0.1943336,"width":0.008643617,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Meet","depth":3,"bounds":{"left":0.08361037,"top":0.21947326,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Meet","depth":3,"bounds":{"left":0.07962101,"top":0.24501197,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meet","depth":4,"bounds":{"left":0.086269945,"top":0.24541101,"width":0.00930851,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Compose","depth":3,"bounds":{"left":0.10621676,"top":0.10933759,"width":0.046043884,"height":0.044692736},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Inbox 1731 unread","depth":11,"bounds":{"left":0.12350399,"top":0.17238627,"width":0.012466756,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox","depth":12,"bounds":{"left":0.12350399,"top":0.17238627,"width":0.012466756,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1,731","depth":11,"bounds":{"left":0.17287233,"top":0.1735834,"width":0.009142287,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Starred","depth":12,"bounds":{"left":0.12350399,"top":0.19792499,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Starred","depth":13,"bounds":{"left":0.12350399,"top":0.19792499,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Snoozed","depth":12,"bounds":{"left":0.12350399,"top":0.22346368,"width":0.018284574,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Snoozed","depth":13,"bounds":{"left":0.12350399,"top":0.22346368,"width":0.018284574,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sent","depth":12,"bounds":{"left":0.12350399,"top":0.2490024,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sent","depth":13,"bounds":{"left":0.12350399,"top":0.2490024,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Drafts 3 unread","depth":12,"bounds":{"left":0.12350399,"top":0.2745411,"width":0.013796543,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Drafts","depth":13,"bounds":{"left":0.12350399,"top":0.2745411,"width":0.013796543,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":12,"bounds":{"left":0.17985372,"top":0.27573824,"width":0.0021609042,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Categories","depth":9,"bounds":{"left":0.105884306,"top":0.29928172,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Categories expanded","depth":11,"bounds":{"left":0.12350399,"top":0.30007982,"width":0.022938829,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Categories","depth":12,"bounds":{"left":0.12350399,"top":0.30007982,"width":0.022938829,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Purchases 2 unread has menu","depth":11,"bounds":{"left":0.12616356,"top":0.3256185,"width":0.023603724,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Purchases","depth":12,"bounds":{"left":0.12616356,"top":0.3256185,"width":0.023603724,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":11,"bounds":{"left":0.17985372,"top":0.32681563,"width":0.0021609042,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More labels","depth":7,"bounds":{"left":0.10621676,"top":0.34557062,"width":0.07978723,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"More","depth":9,"bounds":{"left":0.12350399,"top":0.35115722,"width":0.010804521,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Labels","depth":6,"bounds":{"left":0.11087101,"top":0.3934557,"width":0.06582447,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":7,"bounds":{"left":0.11087101,"top":0.3934557,"width":0.016456118,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create new label","depth":6,"bounds":{"left":0.17669548,"top":0.39385474,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"App emails has menu","depth":12,"bounds":{"left":0.12350399,"top":0.42498004,"width":0.023271276,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"App emails","depth":13,"bounds":{"left":0.12350399,"top":0.42498004,"width":0.023271276,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Github","depth":10,"bounds":{"left":0.105884306,"top":0.44972068,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Github 738 unread expanded has menu","depth":12,"bounds":{"left":0.12350399,"top":0.45051876,"width":0.015458777,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Github","depth":13,"bounds":{"left":0.12350399,"top":0.45051876,"width":0.015458777,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"738","depth":12,"bounds":{"left":0.17569813,"top":0.4517159,"width":0.0063164895,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app 5648 unread has menu","depth":12,"bounds":{"left":0.12616356,"top":0.47605747,"width":0.008643617,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":13,"bounds":{"left":0.12616356,"top":0.47605747,"width":0.008643617,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5,648","depth":12,"bounds":{"left":0.17204122,"top":0.4772546,"width":0.009973404,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"extension 217 unread has menu","depth":12,"bounds":{"left":0.12616356,"top":0.50159615,"width":0.021941489,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"extension","depth":13,"bounds":{"left":0.12616356,"top":0.50159615,"width":0.021941489,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"217","depth":12,"bounds":{"left":0.17619681,"top":0.5027933,"width":0.005817819,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"vuejs has menu","depth":12,"bounds":{"left":0.12616356,"top":0.5271349,"width":0.010804521,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"vuejs","depth":13,"bounds":{"left":0.12616356,"top":0.5271349,"width":0.010804521,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JIRA has menu","depth":12,"bounds":{"left":0.12350399,"top":0.5526736,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JIRA","depth":13,"bounds":{"left":0.12350399,"top":0.5526736,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Notes","depth":10,"bounds":{"left":0.105884306,"top":0.5774142,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Notes expanded has menu","depth":12,"bounds":{"left":0.12350399,"top":0.57821226,"width":0.012300532,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notes","depth":13,"bounds":{"left":0.12350399,"top":0.57821226,"width":0.012300532,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Shared has menu","depth":12,"bounds":{"left":0.12616356,"top":0.603751,"width":0.014960106,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Shared","depth":13,"bounds":{"left":0.12616356,"top":0.603751,"width":0.014960106,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sentry 103246 unread has menu","depth":12,"bounds":{"left":0.12350399,"top":0.6292897,"width":0.01512633,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sentry","depth":13,"bounds":{"left":0.12350399,"top":0.6292897,"width":0.01512633,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"103,246","depth":12,"bounds":{"left":0.16788563,"top":0.63048685,"width":0.01412899,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Utilities has menu","depth":12,"bounds":{"left":0.12350399,"top":0.6548284,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Utilities","depth":13,"bounds":{"left":0.12350399,"top":0.6548284,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New chat","depth":2,"bounds":{"left":0.08361037,"top":0.10933759,"width":0.045877658,"height":0.044692736},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New chat","depth":4,"bounds":{"left":0.1008976,"top":0.1245012,"width":0.020611702,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Shortcuts","depth":5,"bounds":{"left":0.08361037,"top":0.16839585,"width":0.005984043,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Shortcuts","depth":6,"bounds":{"left":0.08959442,"top":0.16520351,"width":0.06881649,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Home","depth":6,"bounds":{"left":0.10023271,"top":0.18994413,"width":0.013131649,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mentions","depth":6,"bounds":{"left":0.10023271,"top":0.2122905,"width":0.019281914,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"star","depth":6,"bounds":{"left":0.087932184,"top":0.23224261,"width":0.0066489363,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Starred","depth":6,"bounds":{"left":0.10023271,"top":0.23463687,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Ask Gemini","depth":6,"bounds":{"left":0.10023271,"top":0.25698325,"width":0.0234375,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Direct messages 31 unread messages","depth":5,"bounds":{"left":0.08361037,"top":0.28651237,"width":0.005984043,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Direct messages","depth":6,"bounds":{"left":0.08959442,"top":0.28332004,"width":0.059507977,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"31","depth":7,"bounds":{"left":0.15325798,"top":0.28731045,"width":0.003656915,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Unread Daily - Platform - May 28Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"bounds":{"left":0.087932184,"top":0.28172386,"width":0.0731383,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"bounds":{"left":0.099567816,"top":0.292498,"width":0.01761968,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Daily - Platform - May 28","depth":14,"bounds":{"left":0.09990027,"top":0.2857143,"width":0.059175532,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"bounds":{"left":0.099567816,"top":0.2932961,"width":0.05069814,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in a pop-up","depth":14,"bounds":{"left":0.15109707,"top":0.27334398,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Options","depth":14,"bounds":{"left":0.15641622,"top":0.2849162,"width":0.005319149,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Unread [Platform] Refinement - May 26Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"bounds":{"left":0.087932184,"top":0.30407023,"width":0.0731383,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"bounds":{"left":0.099567816,"top":0.31484437,"width":0.01761968,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[Platform] Refinement","depth":14,"bounds":{"left":0.09990027,"top":0.30806065,"width":0.05418883,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"- May 26","depth":14,"bounds":{"left":0.16023937,"top":0.30806065,"width":0.021775266,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"bounds":{"left":0.099567816,"top":0.31564245,"width":0.05069814,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in a pop-up","depth":14,"bounds":{"left":0.15109707,"top":0.29569036,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Options","depth":14,"bounds":{"left":0.15641622,"top":0.30726257,"width":0.005319149,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Unread AI chapter - May 20Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"bounds":{"left":0.087932184,"top":0.3264166,"width":0.0731383,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"bounds":{"left":0.099567816,"top":0.33719075,"width":0.01761968,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AI chapter - May 20","depth":14,"bounds":{"left":0.09990027,"top":0.33040702,"width":0.046875,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"bounds":{"left":0.099567816,"top":0.33798882,"width":0.05069814,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in a pop-up","depth":14,"bounds":{"left":0.15109707,"top":0.3180367,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Options","depth":14,"bounds":{"left":0.15641622,"top":0.32960895,"width":0.005319149,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Unread Daily - Platform - May 14Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"bounds":{"left":0.087932184,"top":0.34876296,"width":0.0731383,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"bounds":{"left":0.099567816,"top":0.35953712,"width":0.01761968,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Daily - Platform - May 14","depth":14,"bounds":{"left":0.09990027,"top":0.3527534,"width":0.05867686,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"bounds":{"left":0.099567816,"top":0.3603352,"width":0.05069814,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in a pop-up","depth":14,"bounds":{"left":0.15109707,"top":0.34038308,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Options","depth":14,"bounds":{"left":0.15641622,"top":0.3519553,"width":0.005319149,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Unread Sprint Review - May 13Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"bounds":{"left":0.087932184,"top":0.37110934,"width":0.0731383,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"bounds":{"left":0.099567816,"top":0.38188347,"width":0.01761968,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Sprint Review - May 13","depth":14,"bounds":{"left":0.09990027,"top":0.37509975,"width":0.053523935,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"bounds":{"left":0.099567816,"top":0.38268158,"width":0.05069814,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-7561826779172911090
|
-841192773288136219
|
click
|
accessibility
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738
app 5648 unread has menu
app
5,648
extension 217 unread has menu
extension
217
vuejs has menu
vuejs
JIRA has menu
JIRA
Collapse label: Notes
Notes expanded has menu
Notes
Shared has menu
Shared
Sentry 103246 unread has menu
Sentry
103,246
Utilities has menu
Utilities
New chat
New chat
Shortcuts
Shortcuts
Home
Mentions
star
Starred
Ask Gemini
Direct messages 31 unread messages
Direct messages
31
Unread Daily - Platform - May 28Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Daily - Platform - May 28
Meeting conversation
Open in a pop-up
Options
Unread [Platform] Refinement - May 26Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
[Platform] Refinement
- May 26
Meeting conversation
Open in a pop-up
Options
Unread AI chapter - May 20Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
AI chapter - May 20
Meeting conversation
Open in a pop-up
Options
Unread Daily - Platform - May 14Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Daily - Platform - May 14
Meeting conversation
Open in a pop-up
Options
Unread Sprint Review - May 13Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Sprint Review - May 13
Meeting conversation...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81434
|
2826
|
13
|
2026-05-28T08:14:59.933533+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956099933_m1.jpg...
|
Firefox
|
Text message from 087 787 8118 - lukas.kovalik@jim Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail — Work...
|
1
|
mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLq mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLqvLLsBfWCCDMqrP...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738
app 5648 unread has menu
app
5,648
extension 217 unread has menu
extension
217
vuejs has menu
vuejs
JIRA has menu...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"BE upgrade libraries","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Text relay","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login | Salesforce","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login | Salesforce","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Trimmed content expanded.","depth":2,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Using Jiminny Mail with screen readers","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Using Jiminny Mail with screen readers","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Main menu","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Gmail","depth":6,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Search mail","depth":6,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search mail","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Ask Gmail","depth":12,"on_screen":true,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Advanced search options","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search mail","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Status: Active","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Support","depth":7,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Settings","depth":7,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Studio","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ask Gemini","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Google apps","depth":8,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":9,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Mail, 1731 unread messages","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Mail","depth":3,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mail","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat, 32 unread messages","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"32 new messages","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Chat","depth":3,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Chat","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Meet","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Meet","depth":3,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meet","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Compose","depth":3,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Inbox 1731 unread","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1,731","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Starred","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Starred","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Snoozed","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Snoozed","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sent","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sent","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Drafts 3 unread","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Drafts","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Categories","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Categories expanded","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Categories","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Purchases 2 unread has menu","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Purchases","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More labels","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"More","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Labels","depth":6,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create new label","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"App emails has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"App emails","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Github","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Github 738 unread expanded has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Github","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"738","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app 5648 unread has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5,648","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"extension 217 unread has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"extension","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"217","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"vuejs has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"vuejs","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JIRA has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-5361873623015500330
|
-1124387523689647931
|
click
|
accessibility
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738
app 5648 unread has menu
app
5,648
extension 217 unread has menu
extension
217
vuejs has menu
vuejs
JIRA has menu...
|
81433
|
NULL
|
NULL
|
NULL
|
|
81433
|
2826
|
12
|
2026-05-28T08:14:59.123375+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956099123_m1.jpg...
|
Firefox
|
Text message from 087 787 8118 - lukas.kovalik@jim Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail — Work...
|
1
|
mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLq mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLqvLLsBfWCCDMqrP...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738
app 5648 unread has menu
app
5,648
extension 217 unread has menu
extension
217
vuejs has menu
vuejs
JIRA has menu
JIRA
Collapse label: Notes
Notes expanded has menu
Notes
Shared has menu
Shared
Sentry 103246 unread has menu
Sentry
103,246
Utilities has menu
Utilities
New chat
New chat
Shortcuts
Shortcuts
Home
Mentions
star
Starred
Ask Gemini
Direct messages 31 unread messages
Direct messages
31
Unread Daily - Platform - May 28Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Daily - Platform - May 28
Meeting conversation
Open in a pop-up
Options
Unread [Platform] Refinement - May 26Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
[Platform] Refinement
- May 26
Meeting conversation
Open in a pop-up
Options
Unread AI chapter - May 20Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
AI chapter - May 20
Meeting conversation
Open in a pop-up
Options
Unread Daily - Platform - May 14Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Daily - Platform - May 14
Meeting conversation
Open in a pop-up
Options
Unread Sprint Review - May 13Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Sprint Review - May 13
Meeting conversation
Open in a pop-up
Options
Unread [Platform] Planning I Session - May 13Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
[Platform] Planning I Session
- May 13
Meeting conversation
Open in a pop-up
Options
Unread Daily - Platform - May 12Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"BE upgrade libraries","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Text relay","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login | Salesforce","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login | Salesforce","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Trimmed content expanded.","depth":2,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Using Jiminny Mail with screen readers","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Using Jiminny Mail with screen readers","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Main menu","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Gmail","depth":6,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Search mail","depth":6,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search mail","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Ask Gmail","depth":12,"on_screen":true,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Advanced search options","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search mail","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Status: Active","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Support","depth":7,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Settings","depth":7,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Studio","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ask Gemini","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Google apps","depth":8,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":9,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Mail, 1731 unread messages","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Mail","depth":3,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mail","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat, 32 unread messages","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"32 new messages","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Chat","depth":3,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Chat","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Meet","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Meet","depth":3,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meet","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Compose","depth":3,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Inbox 1731 unread","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1,731","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Starred","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Starred","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Snoozed","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Snoozed","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sent","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sent","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Drafts 3 unread","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Drafts","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Categories","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Categories expanded","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Categories","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Purchases 2 unread has menu","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Purchases","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More labels","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"More","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Labels","depth":6,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create new label","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"App emails has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"App emails","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Github","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Github 738 unread expanded has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Github","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"738","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app 5648 unread has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5,648","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"extension 217 unread has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"extension","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"217","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"vuejs has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"vuejs","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JIRA has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JIRA","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Notes","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Notes expanded has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notes","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Shared has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Shared","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sentry 103246 unread has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sentry","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"103,246","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Utilities has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Utilities","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New chat","depth":2,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New chat","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Shortcuts","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Shortcuts","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Home","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mentions","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"star","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Starred","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Ask Gemini","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Direct messages 31 unread messages","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Direct messages","depth":6,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"31","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Unread Daily - Platform - May 28Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Daily - Platform - May 28","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in a pop-up","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Options","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Unread [Platform] Refinement - May 26Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[Platform] Refinement","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"- May 26","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in a pop-up","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Options","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Unread AI chapter - May 20Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AI chapter - May 20","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in a pop-up","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Options","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Unread Daily - Platform - May 14Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Daily - Platform - May 14","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in a pop-up","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Options","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Unread Sprint Review - May 13Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Sprint Review - May 13","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in a pop-up","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Options","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Unread [Platform] Planning I Session - May 13Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[Platform] Planning I Session","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"- May 13","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in a pop-up","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Options","depth":14,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Unread Daily - Platform - May 12Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
6927494324250503504
|
-841214763260644891
|
click
|
accessibility
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738
app 5648 unread has menu
app
5,648
extension 217 unread has menu
extension
217
vuejs has menu
vuejs
JIRA has menu
JIRA
Collapse label: Notes
Notes expanded has menu
Notes
Shared has menu
Shared
Sentry 103246 unread has menu
Sentry
103,246
Utilities has menu
Utilities
New chat
New chat
Shortcuts
Shortcuts
Home
Mentions
star
Starred
Ask Gemini
Direct messages 31 unread messages
Direct messages
31
Unread Daily - Platform - May 28Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Daily - Platform - May 28
Meeting conversation
Open in a pop-up
Options
Unread [Platform] Refinement - May 26Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
[Platform] Refinement
- May 26
Meeting conversation
Open in a pop-up
Options
Unread AI chapter - May 20Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
AI chapter - May 20
Meeting conversation
Open in a pop-up
Options
Unread Daily - Platform - May 14Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Daily - Platform - May 14
Meeting conversation
Open in a pop-up
Options
Unread Sprint Review - May 13Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Sprint Review - May 13
Meeting conversation
Open in a pop-up
Options
Unread [Platform] Planning I Session - May 13Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
[Platform] Planning I Session
- May 13
Meeting conversation
Open in a pop-up
Options
Unread Daily - Platform - May 12Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81432
|
2827
|
15
|
2026-05-28T08:14:58.958750+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956098958_m2.jpg...
|
Firefox
|
Text message from 087 787 8118 - lukas.kovalik@jim Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail — Work...
|
1
|
mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLq mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLqvLLsBfWCCDMqrP...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0018284575,"top":0.0518755,"width":0.038065158,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.039893616,"top":0.0518755,"width":0.037898935,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.09497207,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.10614525,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"BE upgrade libraries","depth":4,"bounds":{"left":0.0028257978,"top":0.13288109,"width":0.03939495,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":4,"bounds":{"left":0.0,"top":0.15203512,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.1632083,"width":0.12799202,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Text relay","depth":4,"bounds":{"left":0.0028257978,"top":0.18994413,"width":0.020279255,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0028257978,"top":0.21348763,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"bounds":{"left":0.015957447,"top":0.22466081,"width":0.04288564,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.2462091,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.25738227,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.27893057,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.29010376,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.31165203,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.32282522,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":4,"bounds":{"left":0.0028257978,"top":0.3443735,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":5,"bounds":{"left":0.015957447,"top":0.35554668,"width":0.1278258,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.37709498,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.38826814,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.40981644,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.42098963,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"bounds":{"left":0.0,"top":0.4425379,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"bounds":{"left":0.013297873,"top":0.4537111,"width":0.042220745,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.47525936,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.48643255,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login | Salesforce","depth":4,"bounds":{"left":0.0,"top":0.5079808,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login | Salesforce","depth":5,"bounds":{"left":0.013297873,"top":0.519154,"width":0.030917553,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":4,"bounds":{"left":0.0,"top":0.54070234,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":5,"bounds":{"left":0.013297873,"top":0.5518755,"width":0.24301861,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.5734238,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.013297873,"top":0.584597,"width":0.13248006,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.5806065,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":4,"bounds":{"left":0.0,"top":0.60614526,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.61731845,"width":0.08543883,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.6388667,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.6500399,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.6715882,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.6827614,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":4,"bounds":{"left":0.0,"top":0.70430964,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.71548283,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.7370311,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.7482043,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.7713488,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Trimmed content expanded.","depth":2,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Using Jiminny Mail with screen readers","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Using Jiminny Mail with screen readers","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Main menu","depth":5,"bounds":{"left":0.08294548,"top":0.058260176,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Gmail","depth":6,"bounds":{"left":0.1022274,"top":0.061452515,"width":0.043550532,"height":0.035115723},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Search mail","depth":6,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search mail","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Ask Gmail","depth":12,"bounds":{"left":0.20561835,"top":0.06943336,"width":0.18916224,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Advanced search options","depth":6,"bounds":{"left":0.40807846,"top":0.058260176,"width":0.01861702,"height":0.03671189},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search mail","depth":6,"bounds":{"left":0.18733378,"top":0.058260176,"width":0.01861702,"height":0.03671189},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Status: Active","depth":6,"bounds":{"left":0.83627,"top":0.058260176,"width":0.03357713,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Support","depth":7,"bounds":{"left":0.87450135,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Settings","depth":7,"bounds":{"left":0.889129,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Studio","depth":6,"bounds":{"left":0.9030917,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ask Gemini","depth":7,"bounds":{"left":0.91638964,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Google apps","depth":8,"bounds":{"left":0.9303524,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":6,"bounds":{"left":0.94763964,"top":0.058260176,"width":0.04720745,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":9,"bounds":{"left":0.9808843,"top":0.06464485,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Mail, 1731 unread messages","depth":3,"bounds":{"left":0.08361037,"top":0.11731844,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Mail","depth":3,"bounds":{"left":0.07962101,"top":0.14285715,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mail","depth":4,"bounds":{"left":0.087101065,"top":0.14325619,"width":0.0076462766,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat, 32 unread messages","depth":3,"bounds":{"left":0.08361037,"top":0.16839585,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"32 new messages","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Chat","depth":3,"bounds":{"left":0.07962101,"top":0.19393456,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Chat","depth":4,"bounds":{"left":0.0866024,"top":0.1943336,"width":0.008643617,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Meet","depth":3,"bounds":{"left":0.08361037,"top":0.21947326,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Meet","depth":3,"bounds":{"left":0.07962101,"top":0.24501197,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meet","depth":4,"bounds":{"left":0.086269945,"top":0.24541101,"width":0.00930851,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Compose","depth":3,"bounds":{"left":0.10621676,"top":0.10933759,"width":0.046043884,"height":0.044692736},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Inbox 1731 unread","depth":11,"bounds":{"left":0.12350399,"top":0.17238627,"width":0.012466756,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox","depth":12,"bounds":{"left":0.12350399,"top":0.17238627,"width":0.012466756,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1,731","depth":11,"bounds":{"left":0.17287233,"top":0.1735834,"width":0.009142287,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Starred","depth":12,"bounds":{"left":0.12350399,"top":0.19792499,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Starred","depth":13,"bounds":{"left":0.12350399,"top":0.19792499,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Snoozed","depth":12,"bounds":{"left":0.12350399,"top":0.22346368,"width":0.018284574,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Snoozed","depth":13,"bounds":{"left":0.12350399,"top":0.22346368,"width":0.018284574,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sent","depth":12,"bounds":{"left":0.12350399,"top":0.2490024,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sent","depth":13,"bounds":{"left":0.12350399,"top":0.2490024,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Drafts 3 unread","depth":12,"bounds":{"left":0.12350399,"top":0.2745411,"width":0.013796543,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Drafts","depth":13,"bounds":{"left":0.12350399,"top":0.2745411,"width":0.013796543,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":12,"bounds":{"left":0.17985372,"top":0.27573824,"width":0.0021609042,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Categories","depth":9,"bounds":{"left":0.105884306,"top":0.29928172,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Categories expanded","depth":11,"bounds":{"left":0.12350399,"top":0.30007982,"width":0.022938829,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Categories","depth":12,"bounds":{"left":0.12350399,"top":0.30007982,"width":0.022938829,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Purchases 2 unread has menu","depth":11,"bounds":{"left":0.12616356,"top":0.3256185,"width":0.023603724,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Purchases","depth":12,"bounds":{"left":0.12616356,"top":0.3256185,"width":0.023603724,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":11,"bounds":{"left":0.17985372,"top":0.32681563,"width":0.0021609042,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More labels","depth":7,"bounds":{"left":0.10621676,"top":0.34557062,"width":0.07978723,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"More","depth":9,"bounds":{"left":0.12350399,"top":0.35115722,"width":0.010804521,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Labels","depth":6,"bounds":{"left":0.11087101,"top":0.3934557,"width":0.06582447,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":7,"bounds":{"left":0.11087101,"top":0.3934557,"width":0.016456118,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create new label","depth":6,"bounds":{"left":0.17669548,"top":0.39385474,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"App emails has menu","depth":12,"bounds":{"left":0.12350399,"top":0.42498004,"width":0.023271276,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"App emails","depth":13,"bounds":{"left":0.12350399,"top":0.42498004,"width":0.023271276,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Github","depth":10,"bounds":{"left":0.105884306,"top":0.44972068,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Github 738 unread expanded has menu","depth":12,"bounds":{"left":0.12350399,"top":0.45051876,"width":0.015458777,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Github","depth":13,"bounds":{"left":0.12350399,"top":0.45051876,"width":0.015458777,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"738","depth":12,"bounds":{"left":0.17569813,"top":0.4517159,"width":0.0063164895,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-5073467909004622781
|
-1133394723012546363
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738...
|
81431
|
NULL
|
NULL
|
NULL
|
|
81431
|
2827
|
14
|
2026-05-28T08:14:55.882643+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956095882_m2.jpg...
|
Firefox
|
Text message from 087 787 8118 - lukas.kovalik@jim Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail — Work...
|
1
|
mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLq mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLqvLLsBfWCCDMqrP...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0018284575,"top":0.0518755,"width":0.038065158,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.039893616,"top":0.0518755,"width":0.037898935,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.09497207,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.10614525,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"BE upgrade libraries","depth":4,"bounds":{"left":0.0028257978,"top":0.13288109,"width":0.03939495,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":4,"bounds":{"left":0.0,"top":0.15203512,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.1632083,"width":0.12799202,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Text relay","depth":4,"bounds":{"left":0.0028257978,"top":0.18994413,"width":0.020279255,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0028257978,"top":0.21348763,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"bounds":{"left":0.015957447,"top":0.22466081,"width":0.04288564,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.2462091,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.25738227,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.27893057,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.29010376,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.31165203,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.32282522,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":4,"bounds":{"left":0.0028257978,"top":0.3443735,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":5,"bounds":{"left":0.015957447,"top":0.35554668,"width":0.1278258,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.37709498,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.38826814,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.40981644,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.42098963,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"bounds":{"left":0.0,"top":0.4425379,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"bounds":{"left":0.013297873,"top":0.4537111,"width":0.042220745,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.47525936,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.48643255,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login | Salesforce","depth":4,"bounds":{"left":0.0,"top":0.5079808,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login | Salesforce","depth":5,"bounds":{"left":0.013297873,"top":0.519154,"width":0.030917553,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":4,"bounds":{"left":0.0,"top":0.54070234,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":5,"bounds":{"left":0.013297873,"top":0.5518755,"width":0.24301861,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.5734238,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.013297873,"top":0.584597,"width":0.13248006,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.5806065,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":4,"bounds":{"left":0.0,"top":0.60614526,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.61731845,"width":0.08543883,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.6388667,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.6500399,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.6715882,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.6827614,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":4,"bounds":{"left":0.0,"top":0.70430964,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.71548283,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.7370311,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.7482043,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.7713488,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-2332488147477649572
|
-1133412314975768379
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81430
|
2826
|
11
|
2026-05-28T08:14:49.883359+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956089883_m1.jpg...
|
Firefox
|
Text message from 087 787 8118 - lukas.kovalik@jim Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail — Work...
|
1
|
mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLq mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLqvLLsBfWCCDMqrP...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738
app 5648 unread has menu
app
5,648
extension 217 unread has menu
extension
217
vuejs has menu
vuejs
JIRA has menu
JIRA
Collapse label: Notes
Notes expanded has menu
Notes
Shared has menu
Shared
Sentry 103246 unread has menu
Sentry
103,246
Utilities has menu
Utilities
New chat
New chat
Shortcuts
Shortcuts
Home
Mentions
star
Starred
Ask Gemini
Direct messages 31 unread messages
Direct messages...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"BE upgrade libraries","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Text relay","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login | Salesforce","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login | Salesforce","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Trimmed content expanded.","depth":2,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Using Jiminny Mail with screen readers","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Using Jiminny Mail with screen readers","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Main menu","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Gmail","depth":6,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Search mail","depth":6,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search mail","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Ask Gmail","depth":12,"on_screen":true,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Advanced search options","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search mail","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Status: Active","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Support","depth":7,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Settings","depth":7,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Studio","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ask Gemini","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Google apps","depth":8,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":9,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Mail, 1731 unread messages","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Mail","depth":3,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mail","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat, 32 unread messages","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"32 new messages","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Chat","depth":3,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Chat","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Meet","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Meet","depth":3,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meet","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Compose","depth":3,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Inbox 1731 unread","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1,731","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Starred","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Starred","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Snoozed","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Snoozed","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sent","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sent","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Drafts 3 unread","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Drafts","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Categories","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Categories expanded","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Categories","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Purchases 2 unread has menu","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Purchases","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More labels","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"More","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Labels","depth":6,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create new label","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"App emails has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"App emails","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Github","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Github 738 unread expanded has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Github","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"738","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app 5648 unread has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5,648","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"extension 217 unread has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"extension","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"217","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"vuejs has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"vuejs","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JIRA has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JIRA","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Notes","depth":10,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Notes expanded has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notes","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Shared has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Shared","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sentry 103246 unread has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sentry","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"103,246","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Utilities has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Utilities","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New chat","depth":2,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New chat","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Shortcuts","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Shortcuts","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Home","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mentions","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"star","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Starred","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Ask Gemini","depth":6,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Direct messages 31 unread messages","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Direct messages","depth":6,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false}]...
|
-8326857196070578662
|
-1133399121024454459
|
click
|
accessibility
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738
app 5648 unread has menu
app
5,648
extension 217 unread has menu
extension
217
vuejs has menu
vuejs
JIRA has menu
JIRA
Collapse label: Notes
Notes expanded has menu
Notes
Shared has menu
Shared
Sentry 103246 unread has menu
Sentry
103,246
Utilities has menu
Utilities
New chat
New chat
Shortcuts
Shortcuts
Home
Mentions
star
Starred
Ask Gemini
Direct messages 31 unread messages
Direct messages...
|
81428
|
NULL
|
NULL
|
NULL
|
|
81429
|
2827
|
13
|
2026-05-28T08:14:49.585495+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956089585_m2.jpg...
|
Firefox
|
Text message from 087 787 8118 - lukas.kovalik@jim Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail — Work...
|
1
|
mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLq mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLqvLLsBfWCCDMqrP...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738
app 5648 unread has menu
app
5,648
extension 217 unread has menu
extension
217
vuejs has menu
vuejs
JIRA has menu
JIRA
Collapse label: Notes
Notes expanded has menu
Notes
Shared has menu
Shared
Sentry 103246 unread has menu
Sentry
103,246
Utilities has menu
Utilities
New chat
New chat
Shortcuts
Shortcuts
Home
Mentions
star
Starred
Ask Gemini
Direct messages 31 unread messages
Direct messages
31
Unread Daily - Platform - May 28Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Daily - Platform - May 28
Meeting conversation
Open in a pop-up
Options
Unread [Platform] Refinement - May 26Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
[Platform] Refinement...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0018284575,"top":0.0518755,"width":0.038065158,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.039893616,"top":0.0518755,"width":0.037898935,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.09497207,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.10614525,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"BE upgrade libraries","depth":4,"bounds":{"left":0.0028257978,"top":0.13288109,"width":0.03939495,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":4,"bounds":{"left":0.0,"top":0.15203512,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.1632083,"width":0.12799202,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Text relay","depth":4,"bounds":{"left":0.0028257978,"top":0.18994413,"width":0.020279255,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0028257978,"top":0.21348763,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"bounds":{"left":0.015957447,"top":0.22466081,"width":0.04288564,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.2462091,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.25738227,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.27893057,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.29010376,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.31165203,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.32282522,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":4,"bounds":{"left":0.0028257978,"top":0.3443735,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":5,"bounds":{"left":0.015957447,"top":0.35554668,"width":0.1278258,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.37709498,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.38826814,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.40981644,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.42098963,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"bounds":{"left":0.0,"top":0.4425379,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"bounds":{"left":0.013297873,"top":0.4537111,"width":0.042220745,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.47525936,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.48643255,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login | Salesforce","depth":4,"bounds":{"left":0.0,"top":0.5079808,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login | Salesforce","depth":5,"bounds":{"left":0.013297873,"top":0.519154,"width":0.030917553,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":4,"bounds":{"left":0.0,"top":0.54070234,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":5,"bounds":{"left":0.013297873,"top":0.5518755,"width":0.24301861,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.5734238,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.013297873,"top":0.584597,"width":0.13248006,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.5806065,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":4,"bounds":{"left":0.0,"top":0.60614526,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.61731845,"width":0.08543883,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.6388667,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.6500399,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.6715882,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.6827614,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":4,"bounds":{"left":0.0,"top":0.70430964,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.71548283,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.7370311,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.7482043,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.7713488,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Trimmed content expanded.","depth":2,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Using Jiminny Mail with screen readers","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Using Jiminny Mail with screen readers","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Main menu","depth":5,"bounds":{"left":0.08294548,"top":0.058260176,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Gmail","depth":6,"bounds":{"left":0.1022274,"top":0.061452515,"width":0.043550532,"height":0.035115723},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Search mail","depth":6,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search mail","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Ask Gmail","depth":12,"bounds":{"left":0.20561835,"top":0.06943336,"width":0.18916224,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Advanced search options","depth":6,"bounds":{"left":0.40807846,"top":0.058260176,"width":0.01861702,"height":0.03671189},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search mail","depth":6,"bounds":{"left":0.18733378,"top":0.058260176,"width":0.01861702,"height":0.03671189},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Status: Active","depth":6,"bounds":{"left":0.83627,"top":0.058260176,"width":0.03357713,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Support","depth":7,"bounds":{"left":0.87450135,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Settings","depth":7,"bounds":{"left":0.889129,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Studio","depth":6,"bounds":{"left":0.9030917,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ask Gemini","depth":7,"bounds":{"left":0.91638964,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Google apps","depth":8,"bounds":{"left":0.9303524,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":6,"bounds":{"left":0.94763964,"top":0.058260176,"width":0.04720745,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":9,"bounds":{"left":0.9808843,"top":0.06464485,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Mail, 1731 unread messages","depth":3,"bounds":{"left":0.08361037,"top":0.11731844,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Mail","depth":3,"bounds":{"left":0.07962101,"top":0.14285715,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mail","depth":4,"bounds":{"left":0.087101065,"top":0.14325619,"width":0.0076462766,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat, 32 unread messages","depth":3,"bounds":{"left":0.08361037,"top":0.16839585,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"32 new messages","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Chat","depth":3,"bounds":{"left":0.07962101,"top":0.19393456,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Chat","depth":4,"bounds":{"left":0.0866024,"top":0.1943336,"width":0.008643617,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Meet","depth":3,"bounds":{"left":0.08361037,"top":0.21947326,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Meet","depth":3,"bounds":{"left":0.07962101,"top":0.24501197,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meet","depth":4,"bounds":{"left":0.086269945,"top":0.24541101,"width":0.00930851,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Compose","depth":3,"bounds":{"left":0.10621676,"top":0.10933759,"width":0.046043884,"height":0.044692736},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Inbox 1731 unread","depth":11,"bounds":{"left":0.12350399,"top":0.17238627,"width":0.012466756,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox","depth":12,"bounds":{"left":0.12350399,"top":0.17238627,"width":0.012466756,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1,731","depth":11,"bounds":{"left":0.17287233,"top":0.1735834,"width":0.009142287,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Starred","depth":12,"bounds":{"left":0.12350399,"top":0.19792499,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Starred","depth":13,"bounds":{"left":0.12350399,"top":0.19792499,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Snoozed","depth":12,"bounds":{"left":0.12350399,"top":0.22346368,"width":0.018284574,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Snoozed","depth":13,"bounds":{"left":0.12350399,"top":0.22346368,"width":0.018284574,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sent","depth":12,"bounds":{"left":0.12350399,"top":0.2490024,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sent","depth":13,"bounds":{"left":0.12350399,"top":0.2490024,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Drafts 3 unread","depth":12,"bounds":{"left":0.12350399,"top":0.2745411,"width":0.013796543,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Drafts","depth":13,"bounds":{"left":0.12350399,"top":0.2745411,"width":0.013796543,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":12,"bounds":{"left":0.17985372,"top":0.27573824,"width":0.0021609042,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Categories","depth":9,"bounds":{"left":0.105884306,"top":0.29928172,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Categories expanded","depth":11,"bounds":{"left":0.12350399,"top":0.30007982,"width":0.022938829,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Categories","depth":12,"bounds":{"left":0.12350399,"top":0.30007982,"width":0.022938829,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Purchases 2 unread has menu","depth":11,"bounds":{"left":0.12616356,"top":0.3256185,"width":0.023603724,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Purchases","depth":12,"bounds":{"left":0.12616356,"top":0.3256185,"width":0.023603724,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":11,"bounds":{"left":0.17985372,"top":0.32681563,"width":0.0021609042,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More labels","depth":7,"bounds":{"left":0.10621676,"top":0.34557062,"width":0.07978723,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"More","depth":9,"bounds":{"left":0.12350399,"top":0.35115722,"width":0.010804521,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Labels","depth":6,"bounds":{"left":0.11087101,"top":0.3934557,"width":0.06582447,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":7,"bounds":{"left":0.11087101,"top":0.3934557,"width":0.016456118,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create new label","depth":6,"bounds":{"left":0.17669548,"top":0.39385474,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"App emails has menu","depth":12,"bounds":{"left":0.12350399,"top":0.42498004,"width":0.023271276,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"App emails","depth":13,"bounds":{"left":0.12350399,"top":0.42498004,"width":0.023271276,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Github","depth":10,"bounds":{"left":0.105884306,"top":0.44972068,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Github 738 unread expanded has menu","depth":12,"bounds":{"left":0.12350399,"top":0.45051876,"width":0.015458777,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Github","depth":13,"bounds":{"left":0.12350399,"top":0.45051876,"width":0.015458777,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"738","depth":12,"bounds":{"left":0.17569813,"top":0.4517159,"width":0.0063164895,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app 5648 unread has menu","depth":12,"bounds":{"left":0.12616356,"top":0.47605747,"width":0.008643617,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":13,"bounds":{"left":0.12616356,"top":0.47605747,"width":0.008643617,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5,648","depth":12,"bounds":{"left":0.17204122,"top":0.4772546,"width":0.009973404,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"extension 217 unread has menu","depth":12,"bounds":{"left":0.12616356,"top":0.50159615,"width":0.021941489,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"extension","depth":13,"bounds":{"left":0.12616356,"top":0.50159615,"width":0.021941489,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"217","depth":12,"bounds":{"left":0.17619681,"top":0.5027933,"width":0.005817819,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"vuejs has menu","depth":12,"bounds":{"left":0.12616356,"top":0.5271349,"width":0.010804521,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"vuejs","depth":13,"bounds":{"left":0.12616356,"top":0.5271349,"width":0.010804521,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JIRA has menu","depth":12,"bounds":{"left":0.12350399,"top":0.5526736,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JIRA","depth":13,"bounds":{"left":0.12350399,"top":0.5526736,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Notes","depth":10,"bounds":{"left":0.105884306,"top":0.5774142,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Notes expanded has menu","depth":12,"bounds":{"left":0.12350399,"top":0.57821226,"width":0.012300532,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notes","depth":13,"bounds":{"left":0.12350399,"top":0.57821226,"width":0.012300532,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Shared has menu","depth":12,"bounds":{"left":0.12616356,"top":0.603751,"width":0.014960106,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Shared","depth":13,"bounds":{"left":0.12616356,"top":0.603751,"width":0.014960106,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sentry 103246 unread has menu","depth":12,"bounds":{"left":0.12350399,"top":0.6292897,"width":0.01512633,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sentry","depth":13,"bounds":{"left":0.12350399,"top":0.6292897,"width":0.01512633,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"103,246","depth":12,"bounds":{"left":0.16788563,"top":0.63048685,"width":0.01412899,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Utilities has menu","depth":12,"bounds":{"left":0.12350399,"top":0.6548284,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Utilities","depth":13,"bounds":{"left":0.12350399,"top":0.6548284,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New chat","depth":2,"bounds":{"left":0.08361037,"top":0.10933759,"width":0.045877658,"height":0.044692736},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New chat","depth":4,"bounds":{"left":0.1008976,"top":0.1245012,"width":0.020611702,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Shortcuts","depth":5,"bounds":{"left":0.08361037,"top":0.16839585,"width":0.005984043,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Shortcuts","depth":6,"bounds":{"left":0.08959442,"top":0.16520351,"width":0.06881649,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Home","depth":6,"bounds":{"left":0.10023271,"top":0.18994413,"width":0.013131649,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mentions","depth":6,"bounds":{"left":0.10023271,"top":0.2122905,"width":0.019281914,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"star","depth":6,"bounds":{"left":0.087932184,"top":0.23224261,"width":0.0066489363,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Starred","depth":6,"bounds":{"left":0.10023271,"top":0.23463687,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Ask Gemini","depth":6,"bounds":{"left":0.10023271,"top":0.25698325,"width":0.0234375,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Direct messages 31 unread messages","depth":5,"bounds":{"left":0.08361037,"top":0.28651237,"width":0.005984043,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Direct messages","depth":6,"bounds":{"left":0.08959442,"top":0.28332004,"width":0.059507977,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"31","depth":7,"bounds":{"left":0.15325798,"top":0.28731045,"width":0.003656915,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Unread Daily - Platform - May 28Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"bounds":{"left":0.087932184,"top":0.28172386,"width":0.0731383,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"bounds":{"left":0.099567816,"top":0.292498,"width":0.01761968,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Daily - Platform - May 28","depth":14,"bounds":{"left":0.09990027,"top":0.2857143,"width":0.059175532,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"bounds":{"left":0.099567816,"top":0.2932961,"width":0.05069814,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in a pop-up","depth":14,"bounds":{"left":0.15109707,"top":0.27334398,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Options","depth":14,"bounds":{"left":0.15641622,"top":0.2849162,"width":0.005319149,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Unread [Platform] Refinement - May 26Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"bounds":{"left":0.087932184,"top":0.30407023,"width":0.0731383,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"bounds":{"left":0.099567816,"top":0.31484437,"width":0.01761968,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[Platform] Refinement","depth":14,"bounds":{"left":0.09990027,"top":0.30806065,"width":0.05418883,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
2418128726909798993
|
-1133363936619859771
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738
app 5648 unread has menu
app
5,648
extension 217 unread has menu
extension
217
vuejs has menu
vuejs
JIRA has menu
JIRA
Collapse label: Notes
Notes expanded has menu
Notes
Shared has menu
Shared
Sentry 103246 unread has menu
Sentry
103,246
Utilities has menu
Utilities
New chat
New chat
Shortcuts
Shortcuts
Home
Mentions
star
Starred
Ask Gemini
Direct messages 31 unread messages
Direct messages
31
Unread Daily - Platform - May 28Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Daily - Platform - May 28
Meeting conversation
Open in a pop-up
Options
Unread [Platform] Refinement - May 26Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
[Platform] Refinement...
|
81427
|
NULL
|
NULL
|
NULL
|
|
81428
|
2826
|
10
|
2026-05-28T08:14:48.168313+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956088168_m1.jpg...
|
Firefox
|
Text message from 087 787 8118 - lukas.kovalik@jim Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail — Work...
|
1
|
mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLq mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLqvLLsBfWCCDMqrP...
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"BE upgrade libraries","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Text relay","depth":4,"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login | Salesforce","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login | Salesforce","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Trimmed content expanded.","depth":2,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Using Jiminny Mail with screen readers","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Using Jiminny Mail with screen readers","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Main menu","depth":5,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Gmail","depth":6,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Search mail","depth":6,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search mail","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Ask Gmail","depth":12,"on_screen":true,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Advanced search options","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search mail","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Status: Active","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Support","depth":7,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Settings","depth":7,"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Studio","depth":6,"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ask Gemini","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Google apps","depth":8,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":9,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Mail, 1731 unread messages","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Mail","depth":3,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mail","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat, 32 unread messages","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"32 new messages","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Chat","depth":3,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Chat","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Meet","depth":3,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Meet","depth":3,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meet","depth":4,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Compose","depth":3,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Inbox 1731 unread","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1,731","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Starred","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Starred","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Snoozed","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Snoozed","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sent","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sent","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Drafts 3 unread","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Drafts","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Categories","depth":9,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Categories expanded","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Categories","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Purchases 2 unread has menu","depth":11,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Purchases","depth":12,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":11,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More labels","depth":7,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"More","depth":9,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Labels","depth":6,"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":7,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create new label","depth":6,"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"App emails has menu","depth":12,"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"App emails","depth":13,"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-8516769069496189183
|
-1133394723012545851
|
click
|
accessibility
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81427
|
2827
|
12
|
2026-05-28T08:14:48.060257+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956088060_m2.jpg...
|
Firefox
|
Text message from 087 787 8118 - lukas.kovalik@jim Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail — Work...
|
1
|
mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLq mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLqvLLsBfWCCDMqrP...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0018284575,"top":0.0518755,"width":0.038065158,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.039893616,"top":0.0518755,"width":0.037898935,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.09497207,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.10614525,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"BE upgrade libraries","depth":4,"bounds":{"left":0.0028257978,"top":0.13288109,"width":0.03939495,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":4,"bounds":{"left":0.0,"top":0.15203512,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.1632083,"width":0.12799202,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Text relay","depth":4,"bounds":{"left":0.0028257978,"top":0.18994413,"width":0.020279255,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0028257978,"top":0.21348763,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"bounds":{"left":0.015957447,"top":0.22466081,"width":0.04288564,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.2462091,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.25738227,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.27893057,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.29010376,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.31165203,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.32282522,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":4,"bounds":{"left":0.0028257978,"top":0.3443735,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":5,"bounds":{"left":0.015957447,"top":0.35554668,"width":0.1278258,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.37709498,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.38826814,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.40981644,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.42098963,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"bounds":{"left":0.0,"top":0.4425379,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"bounds":{"left":0.013297873,"top":0.4537111,"width":0.042220745,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.47525936,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.48643255,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login | Salesforce","depth":4,"bounds":{"left":0.0,"top":0.5079808,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login | Salesforce","depth":5,"bounds":{"left":0.013297873,"top":0.519154,"width":0.030917553,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":4,"bounds":{"left":0.0,"top":0.54070234,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":5,"bounds":{"left":0.013297873,"top":0.5518755,"width":0.24301861,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.5734238,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.013297873,"top":0.584597,"width":0.13248006,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.5806065,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":4,"bounds":{"left":0.0,"top":0.60614526,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.61731845,"width":0.08543883,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.6388667,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.6500399,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.6715882,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.6827614,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":4,"bounds":{"left":0.0,"top":0.70430964,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.71548283,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.7370311,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.7482043,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.7713488,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Trimmed content expanded.","depth":2,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Using Jiminny Mail with screen readers","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Using Jiminny Mail with screen readers","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"}]...
|
-2396926609413277127
|
-1133412864732106555
|
click
|
accessibility
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81426
|
2827
|
11
|
2026-05-28T08:14:43.223278+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956083223_m2.jpg...
|
Firefox
|
Text message from 087 787 8118 - lukas.kovalik@jim Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail — Work...
|
1
|
mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLq mail.google.com/mail/u/0/#inbox/FMfcgzQgMCZWhvKZLqvLLsBfWCCDMqrP...
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738
app 5648 unread has menu
app
5,648
extension 217 unread has menu
extension
217
vuejs has menu
vuejs
JIRA has menu
JIRA
Collapse label: Notes
Notes expanded has menu
Notes
Shared has menu
Shared
Sentry 103246 unread has menu
Sentry
103,246
Utilities has menu
Utilities
New chat
New chat
Shortcuts
Shortcuts
Home
Mentions
star
Starred
Ask Gemini
Direct messages 31 unread messages
Direct messages
31
Unread Daily - Platform - May 28Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Daily - Platform - May 28
Meeting conversation
Open in a pop-up
Options
Unread [Platform] Refinement - May 26Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
[Platform] Refinement
- May 26
Meeting conversation
Open in a pop-up
Options
Unread AI chapter - May 20Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
AI chapter - May 20
Meeting conversation
Open in a pop-up
Options
Unread Daily - Platform - May 14Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Daily - Platform - May 14
Meeting conversation
Open in a pop-up
Options
Unread Sprint Review - May 13Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Sprint Review - May 13
Meeting conversation
Open in a pop-up
Options...
|
[{"role":"AXRadioButton","text [{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0018284575,"top":0.0518755,"width":0.038065158,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira","depth":4,"bounds":{"left":0.039893616,"top":0.0518755,"width":0.037898935,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"Pipelines - jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.09497207,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Pipelines - jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.10614525,"width":0.039228722,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"BE upgrade libraries","depth":4,"bounds":{"left":0.0028257978,"top":0.13288109,"width":0.03939495,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXRadioButton","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":4,"bounds":{"left":0.0,"top":0.15203512,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20613] Allow owner's role to be selected when setting up a trial - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.1632083,"width":0.12799202,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Text relay","depth":4,"bounds":{"left":0.0028257978,"top":0.18994413,"width":0.020279255,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXRadioButton","text":"Feed — jiminny — Sentry","depth":4,"bounds":{"left":0.0028257978,"top":0.21348763,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Feed — jiminny — Sentry","depth":5,"bounds":{"left":0.015957447,"top":0.22466081,"width":0.04288564,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.2462091,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.25738227,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.27893057,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.29010376,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"CloudWatch | us-east-2","depth":4,"bounds":{"left":0.0028257978,"top":0.31165203,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"CloudWatch | us-east-2","depth":5,"bounds":{"left":0.015957447,"top":0.32282522,"width":0.04138963,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":4,"bounds":{"left":0.0028257978,"top":0.3443735,"width":0.07679521,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app","depth":5,"bounds":{"left":0.015957447,"top":0.35554668,"width":0.1278258,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":4,"bounds":{"left":0.0,"top":0.37709498,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app","depth":5,"bounds":{"left":0.013297873,"top":0.38826814,"width":0.14245346,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":4,"bounds":{"left":0.0,"top":0.40981644,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"SevenShores\\Hubspot\\Exceptions\\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {\"status\":\"error\",\"message\":\"You have reached your secondly limit.\",\"errorType\":\"RATE_LIMIT","depth":5,"bounds":{"left":0.013297873,"top":0.42098963,"width":0.4644282,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Lukas Kovalik - Time Off","depth":4,"bounds":{"left":0.0,"top":0.4425379,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Lukas Kovalik - Time Off","depth":5,"bounds":{"left":0.013297873,"top":0.4537111,"width":0.042220745,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.47525936,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.48643255,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Login | Salesforce","depth":4,"bounds":{"left":0.0,"top":0.5079808,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Login | Salesforce","depth":5,"bounds":{"left":0.013297873,"top":0.519154,"width":0.030917553,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":4,"bounds":{"left":0.0,"top":0.54070234,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny\\Exceptions\\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app","depth":5,"bounds":{"left":0.013297873,"top":0.5518755,"width":0.24301861,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":4,"bounds":{"left":0.0,"top":0.5734238,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":true},{"role":"AXStaticText","text":"Text message from 087 787 8118 - lukas.kovalik@jiminny.com - Jiminny Mail","depth":5,"bounds":{"left":0.013297873,"top":0.584597,"width":0.13248006,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Close tab","depth":5,"bounds":{"left":0.06732048,"top":0.5806065,"width":0.007978723,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXRadioButton","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":4,"bounds":{"left":0.0,"top":0.60614526,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[JY-20979] Resolve PHP 8.5.5 deprications - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.61731845,"width":0.08543883,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.6388667,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.6500399,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":4,"bounds":{"left":0.0,"top":0.6715882,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.6827614,"width":0.10106383,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":4,"bounds":{"left":0.0,"top":0.70430964,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"[SRD-6881] [On demand] Transcription in saved search disappears - Jira","depth":5,"bounds":{"left":0.013297873,"top":0.71548283,"width":0.12699468,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXRadioButton","text":"Jiminny","depth":4,"bounds":{"left":0.0,"top":0.7370311,"width":0.07962101,"height":0.032721467},"on_screen":true,"help_text":"","role_description":"tab","subrole":"AXTabButton","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Jiminny","depth":5,"bounds":{"left":0.013297873,"top":0.7482043,"width":0.013131649,"height":0.010774142},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New Tab","depth":4,"bounds":{"left":0.0028257978,"top":0.7713488,"width":0.07413564,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Customize sidebar","depth":6,"bounds":{"left":0.0028257978,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open Google Gemini (⌃X)","depth":6,"bounds":{"left":0.013796543,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Tabs from other devices","depth":6,"bounds":{"left":0.024933511,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open history (⇧⌘H)","depth":6,"bounds":{"left":0.036070477,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXCheckBox","text":"Open bookmarks (⌘B)","depth":6,"bounds":{"left":0.04720745,"top":0.97007185,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Trimmed content expanded.","depth":2,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Skip to content","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Skip to content","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Using Jiminny Mail with screen readers","depth":4,"on_screen":false,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Using Jiminny Mail with screen readers","depth":5,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Main menu","depth":5,"bounds":{"left":0.08294548,"top":0.058260176,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXLink","text":"Gmail","depth":6,"bounds":{"left":0.1022274,"top":0.061452515,"width":0.043550532,"height":0.035115723},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Search mail","depth":6,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Search mail","depth":7,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXTextField","text":"Ask Gmail","depth":12,"bounds":{"left":0.20561835,"top":0.06943336,"width":0.18916224,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text field","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Advanced search options","depth":6,"bounds":{"left":0.40807846,"top":0.058260176,"width":0.01861702,"height":0.03671189},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Search mail","depth":6,"bounds":{"left":0.18733378,"top":0.058260176,"width":0.01861702,"height":0.03671189},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":false,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Status: Active","depth":6,"bounds":{"left":0.83627,"top":0.058260176,"width":0.03357713,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXMenuButton","text":"Support","depth":7,"bounds":{"left":0.87450135,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXMenuButton","text":"Settings","depth":7,"bounds":{"left":0.889129,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"menu button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXCheckBox","text":"Studio","depth":6,"bounds":{"left":0.9030917,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"toggle button","subrole":"AXToggle","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Ask Gemini","depth":7,"bounds":{"left":0.91638964,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Google apps","depth":8,"bounds":{"left":0.9303524,"top":0.061452515,"width":0.013297873,"height":0.031923383},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":6,"bounds":{"left":0.94763964,"top":0.058260176,"width":0.04720745,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Google Account: lukas.kovalik@jiminny.com","depth":9,"bounds":{"left":0.9808843,"top":0.06464485,"width":0.010638298,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Mail, 1731 unread messages","depth":3,"bounds":{"left":0.08361037,"top":0.11731844,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Mail","depth":3,"bounds":{"left":0.07962101,"top":0.14285715,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mail","depth":4,"bounds":{"left":0.087101065,"top":0.14325619,"width":0.0076462766,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Chat, 32 unread messages","depth":3,"bounds":{"left":0.08361037,"top":0.16839585,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"32 new messages","depth":5,"on_screen":false,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Chat","depth":3,"bounds":{"left":0.07962101,"top":0.19393456,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Chat","depth":4,"bounds":{"left":0.0866024,"top":0.1943336,"width":0.008643617,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Meet","depth":3,"bounds":{"left":0.08361037,"top":0.21947326,"width":0.01462766,"height":0.023942538},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Meet","depth":3,"bounds":{"left":0.07962101,"top":0.24501197,"width":0.022606382,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meet","depth":4,"bounds":{"left":0.086269945,"top":0.24541101,"width":0.00930851,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Compose","depth":3,"bounds":{"left":0.10621676,"top":0.10933759,"width":0.046043884,"height":0.044692736},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Inbox 1731 unread","depth":11,"bounds":{"left":0.12350399,"top":0.17238627,"width":0.012466756,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Inbox","depth":12,"bounds":{"left":0.12350399,"top":0.17238627,"width":0.012466756,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"1,731","depth":11,"bounds":{"left":0.17287233,"top":0.1735834,"width":0.009142287,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Starred","depth":12,"bounds":{"left":0.12350399,"top":0.19792499,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Starred","depth":13,"bounds":{"left":0.12350399,"top":0.19792499,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Snoozed","depth":12,"bounds":{"left":0.12350399,"top":0.22346368,"width":0.018284574,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Snoozed","depth":13,"bounds":{"left":0.12350399,"top":0.22346368,"width":0.018284574,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sent","depth":12,"bounds":{"left":0.12350399,"top":0.2490024,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sent","depth":13,"bounds":{"left":0.12350399,"top":0.2490024,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Drafts 3 unread","depth":12,"bounds":{"left":0.12350399,"top":0.2745411,"width":0.013796543,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Drafts","depth":13,"bounds":{"left":0.12350399,"top":0.2745411,"width":0.013796543,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"3","depth":12,"bounds":{"left":0.17985372,"top":0.27573824,"width":0.0021609042,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Categories","depth":9,"bounds":{"left":0.105884306,"top":0.29928172,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Categories expanded","depth":11,"bounds":{"left":0.12350399,"top":0.30007982,"width":0.022938829,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Categories","depth":12,"bounds":{"left":0.12350399,"top":0.30007982,"width":0.022938829,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Purchases 2 unread has menu","depth":11,"bounds":{"left":0.12616356,"top":0.3256185,"width":0.023603724,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Purchases","depth":12,"bounds":{"left":0.12616356,"top":0.3256185,"width":0.023603724,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"2","depth":11,"bounds":{"left":0.17985372,"top":0.32681563,"width":0.0021609042,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"More labels","depth":7,"bounds":{"left":0.10621676,"top":0.34557062,"width":0.07978723,"height":0.025538707},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"More","depth":9,"bounds":{"left":0.12350399,"top":0.35115722,"width":0.010804521,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXHeading","text":"Labels","depth":6,"bounds":{"left":0.11087101,"top":0.3934557,"width":0.06582447,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":7,"bounds":{"left":0.11087101,"top":0.3934557,"width":0.016456118,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Create new label","depth":6,"bounds":{"left":0.17669548,"top":0.39385474,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXHeading","text":"Labels","depth":7,"on_screen":false,"help_text":"","role_description":"heading","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Labels","depth":8,"on_screen":false,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"App emails has menu","depth":12,"bounds":{"left":0.12350399,"top":0.42498004,"width":0.023271276,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"App emails","depth":13,"bounds":{"left":0.12350399,"top":0.42498004,"width":0.023271276,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Github","depth":10,"bounds":{"left":0.105884306,"top":0.44972068,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Github 738 unread expanded has menu","depth":12,"bounds":{"left":0.12350399,"top":0.45051876,"width":0.015458777,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Github","depth":13,"bounds":{"left":0.12350399,"top":0.45051876,"width":0.015458777,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"738","depth":12,"bounds":{"left":0.17569813,"top":0.4517159,"width":0.0063164895,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"app 5648 unread has menu","depth":12,"bounds":{"left":0.12616356,"top":0.47605747,"width":0.008643617,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"app","depth":13,"bounds":{"left":0.12616356,"top":0.47605747,"width":0.008643617,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"5,648","depth":12,"bounds":{"left":0.17204122,"top":0.4772546,"width":0.009973404,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"extension 217 unread has menu","depth":12,"bounds":{"left":0.12616356,"top":0.50159615,"width":0.021941489,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"extension","depth":13,"bounds":{"left":0.12616356,"top":0.50159615,"width":0.021941489,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"217","depth":12,"bounds":{"left":0.17619681,"top":0.5027933,"width":0.005817819,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"vuejs has menu","depth":12,"bounds":{"left":0.12616356,"top":0.5271349,"width":0.010804521,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"vuejs","depth":13,"bounds":{"left":0.12616356,"top":0.5271349,"width":0.010804521,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"JIRA has menu","depth":12,"bounds":{"left":0.12350399,"top":0.5526736,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"JIRA","depth":13,"bounds":{"left":0.12350399,"top":0.5526736,"width":0.009640957,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Collapse label: Notes","depth":10,"bounds":{"left":0.105884306,"top":0.5774142,"width":0.0066489363,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXLink","text":"Notes expanded has menu","depth":12,"bounds":{"left":0.12350399,"top":0.57821226,"width":0.012300532,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Notes","depth":13,"bounds":{"left":0.12350399,"top":0.57821226,"width":0.012300532,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Shared has menu","depth":12,"bounds":{"left":0.12616356,"top":0.603751,"width":0.014960106,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Shared","depth":13,"bounds":{"left":0.12616356,"top":0.603751,"width":0.014960106,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Sentry 103246 unread has menu","depth":12,"bounds":{"left":0.12350399,"top":0.6292897,"width":0.01512633,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Sentry","depth":13,"bounds":{"left":0.12350399,"top":0.6292897,"width":0.01512633,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"103,246","depth":12,"bounds":{"left":0.16788563,"top":0.63048685,"width":0.01412899,"height":0.012370312},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Utilities has menu","depth":12,"bounds":{"left":0.12350399,"top":0.6548284,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Utilities","depth":13,"bounds":{"left":0.12350399,"top":0.6548284,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"New chat","depth":2,"bounds":{"left":0.08361037,"top":0.10933759,"width":0.045877658,"height":0.044692736},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"New chat","depth":4,"bounds":{"left":0.1008976,"top":0.1245012,"width":0.020611702,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Shortcuts","depth":5,"bounds":{"left":0.08361037,"top":0.16839585,"width":0.005984043,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Shortcuts","depth":6,"bounds":{"left":0.08959442,"top":0.16520351,"width":0.06881649,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Home","depth":6,"bounds":{"left":0.10023271,"top":0.18994413,"width":0.013131649,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Mentions","depth":6,"bounds":{"left":0.10023271,"top":0.2122905,"width":0.019281914,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"star","depth":6,"bounds":{"left":0.087932184,"top":0.23224261,"width":0.0066489363,"height":0.01915403},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Starred","depth":6,"bounds":{"left":0.10023271,"top":0.23463687,"width":0.015625,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Ask Gemini","depth":6,"bounds":{"left":0.10023271,"top":0.25698325,"width":0.0234375,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Direct messages 31 unread messages","depth":5,"bounds":{"left":0.08361037,"top":0.28651237,"width":0.005984043,"height":0.012769354},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":true},{"role":"AXButton","text":"Direct messages","depth":6,"bounds":{"left":0.08959442,"top":0.28332004,"width":0.059507977,"height":0.01915403},"on_screen":true,"role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"31","depth":7,"bounds":{"left":0.15325798,"top":0.28731045,"width":0.003656915,"height":0.011572227},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXLink","text":"Unread Daily - Platform - May 28Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"bounds":{"left":0.087932184,"top":0.28172386,"width":0.0731383,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"bounds":{"left":0.099567816,"top":0.292498,"width":0.01761968,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Daily - Platform - May 28","depth":14,"bounds":{"left":0.09990027,"top":0.2857143,"width":0.059175532,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"bounds":{"left":0.099567816,"top":0.2932961,"width":0.05069814,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in a pop-up","depth":14,"bounds":{"left":0.15109707,"top":0.27334398,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Options","depth":14,"bounds":{"left":0.15641622,"top":0.2849162,"width":0.005319149,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Unread [Platform] Refinement - May 26Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"bounds":{"left":0.087932184,"top":0.30407023,"width":0.0731383,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"bounds":{"left":0.099567816,"top":0.31484437,"width":0.01761968,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"[Platform] Refinement","depth":14,"bounds":{"left":0.09990027,"top":0.30806065,"width":0.05418883,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"- May 26","depth":14,"bounds":{"left":0.16023937,"top":0.30806065,"width":0.021775266,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"bounds":{"left":0.099567816,"top":0.31564245,"width":0.05069814,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in a pop-up","depth":14,"bounds":{"left":0.15109707,"top":0.29569036,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Options","depth":14,"bounds":{"left":0.15641622,"top":0.30726257,"width":0.005319149,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Unread AI chapter - May 20Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"bounds":{"left":0.087932184,"top":0.3264166,"width":0.0731383,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"bounds":{"left":0.099567816,"top":0.33719075,"width":0.01761968,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"AI chapter - May 20","depth":14,"bounds":{"left":0.09990027,"top":0.33040702,"width":0.046875,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"bounds":{"left":0.099567816,"top":0.33798882,"width":0.05069814,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in a pop-up","depth":14,"bounds":{"left":0.15109707,"top":0.3180367,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Options","depth":14,"bounds":{"left":0.15641622,"top":0.32960895,"width":0.005319149,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Unread Daily - Platform - May 14Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"bounds":{"left":0.087932184,"top":0.34876296,"width":0.0731383,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"bounds":{"left":0.099567816,"top":0.35953712,"width":0.01761968,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Daily - Platform - May 14","depth":14,"bounds":{"left":0.09990027,"top":0.3527534,"width":0.05867686,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"bounds":{"left":0.099567816,"top":0.3603352,"width":0.05069814,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in a pop-up","depth":14,"bounds":{"left":0.15109707,"top":0.34038308,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Options","depth":14,"bounds":{"left":0.15641622,"top":0.3519553,"width":0.005319149,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXLink","text":"Unread Sprint Review - May 13Meeting conversation Press tab for more options or Option + 0 to preview the last message.","depth":11,"bounds":{"left":0.087932184,"top":0.37110934,"width":0.0731383,"height":0.022346368},"on_screen":true,"help_text":"","role_description":"link","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXStaticText","text":"Unread","depth":13,"bounds":{"left":0.099567816,"top":0.38188347,"width":0.01761968,"height":0.016360734},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Sprint Review - May 13","depth":14,"bounds":{"left":0.09990027,"top":0.37509975,"width":0.053523935,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXStaticText","text":"Meeting conversation","depth":14,"bounds":{"left":0.099567816,"top":0.38268158,"width":0.05069814,"height":0.014764565},"on_screen":true,"help_text":"","role_description":"text","subrole":"AXUnknown"},{"role":"AXButton","text":"Open in a pop-up","depth":14,"bounds":{"left":0.15109707,"top":0.36272946,"width":0.015957447,"height":0.03830806},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false},{"role":"AXButton","text":"Options","depth":14,"bounds":{"left":0.15641622,"top":0.37430167,"width":0.005319149,"height":0.015961692},"on_screen":true,"help_text":"","role_description":"button","subrole":"AXUnknown","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
3865678746337815679
|
-841192773288136219
|
visual_change
|
accessibility
|
NULL
|
Platform Sprint 5 Q2 - Platform Team - Scrum Board Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[JY-20915] Add environment-specific email domains for text relay to prevent duplicate processing - Jira
Pipelines - jiminny/app
Pipelines - jiminny/app
BE upgrade libraries
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
[JY-20613] Allow owner's role to be selected when setting up a trial - Jira
Text relay
Feed — jiminny — Sentry
Feed — jiminny — Sentry
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
CloudWatch | us-east-2
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20915 fix strict casting by LakyLak · Pull Request #12134 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
JY-20963 fix deleted object import by LakyLak · Pull Request #12121 · jiminny/app
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
SevenShores\Hubspot\Exceptions\BadRequest: Client error: `POST https://api.hubapi.com/crm/v3/objects/contact/search` resulted in a `429 Too Many Requests` response: {"status":"error","message":"You have reached your secondly limit.","errorType":"RATE_LIMIT
Lukas Kovalik - Time Off
Lukas Kovalik - Time Off
Jiminny
Jiminny
Login | Salesforce
Login | Salesforce
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Jiminny\Exceptions\EmailActivityImportException: [Email Import] Failed for InboxEmail ID: 125695762: Error: Request failed — jiminny — app
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Text message from 087 787 8118 - [EMAIL] - Jiminny Mail
Close tab
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
[JY-20979] Resolve PHP 8.5.5 deprications - Jira
Jiminny
Jiminny
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 5 Q2 - Platform Team - Scrum Board - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
[SRD-6881] [On demand] Transcription in saved search disappears - Jira
Jiminny
Jiminny
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Trimmed content expanded.
Skip to content
Skip to content
Using Jiminny Mail with screen readers
Using Jiminny Mail with screen readers
Main menu
Gmail
Search mail
Search mail
Ask Gmail
Advanced search options
Search mail
Status: Active
Support
Settings
Studio
Ask Gemini
Google apps
Google Account: [EMAIL]
Google Account: [EMAIL]
Mail, 1731 unread messages
Mail
Mail
Chat, 32 unread messages
32 new messages
Chat
Chat
Meet
Meet
Meet
Compose
Labels
Labels
Inbox 1731 unread
Inbox
1,731
Starred
Starred
Snoozed
Snoozed
Sent
Sent
Drafts 3 unread
Drafts
3
Collapse label: Categories
Categories expanded
Categories
Purchases 2 unread has menu
Purchases
2
More labels
More
Labels
Labels
Create new label
Labels
Labels
App emails has menu
App emails
Collapse label: Github
Github 738 unread expanded has menu
Github
738
app 5648 unread has menu
app
5,648
extension 217 unread has menu
extension
217
vuejs has menu
vuejs
JIRA has menu
JIRA
Collapse label: Notes
Notes expanded has menu
Notes
Shared has menu
Shared
Sentry 103246 unread has menu
Sentry
103,246
Utilities has menu
Utilities
New chat
New chat
Shortcuts
Shortcuts
Home
Mentions
star
Starred
Ask Gemini
Direct messages 31 unread messages
Direct messages
31
Unread Daily - Platform - May 28Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Daily - Platform - May 28
Meeting conversation
Open in a pop-up
Options
Unread [Platform] Refinement - May 26Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
[Platform] Refinement
- May 26
Meeting conversation
Open in a pop-up
Options
Unread AI chapter - May 20Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
AI chapter - May 20
Meeting conversation
Open in a pop-up
Options
Unread Daily - Platform - May 14Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Daily - Platform - May 14
Meeting conversation
Open in a pop-up
Options
Unread Sprint Review - May 13Meeting conversation Press tab for more options or Option + 0 to preview the last message.
Unread
Sprint Review - May 13
Meeting conversation
Open in a pop-up
Options...
|
81425
|
NULL
|
NULL
|
NULL
|
|
81424
|
2826
|
9
|
2026-05-28T08:14:33.388397+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956073388_m1.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.019444445,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
327918965576722946
|
-3660159508063819383
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
81423
|
NULL
|
NULL
|
NULL
|
|
81425
|
2827
|
10
|
2026-05-28T08:14:32.152858+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956072152_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormEV faVsco,ls ~View$2 JY-20915-fix-mis:Cooc PhpStormEV faVsco,ls ~View$2 JY-20915-fix-mis:CoocRunWindow©InternetMessagelnterface.ph/©MailChannelService.php) Kernel.php© SyneMailbox.php© TextRelayServiceTest.phpclass TextReLayServiceMeetingGeneratorNotification#OAuth2Playbooks1—KecalAJotaeoStreaminga TeamTelephonyaUserPilot#Webhook© AbstractService.php©ActivityProviderFactory.php©ActivityService.php© ApiResponseService.phpg conferenceService.php©InsightSeatService.php© InstantMeetingService.php©IntercomService.php©lpapiClient.php©IpapiService.phpLog::warning ( messages ' (TextRelayService) Refused aessage: nissing X-Gn-Orsginal--=Wa holnore nhrInitialFrontendState.phpO tliminnv nhn1editV ACCApt Fle X- X Reject Fle oxaA SF giminny@localhost)[2826-05-28 07:47:50] Local.INFO: Sparamsnzscorylypes = nessageaddee(startHistoryId) => 359021-correlatzon10:%aC0/1105-C2415-4442-7826-85-28 87:47:58 Local.IWFU: SnessagchzscomvA console [PROD]# consoke leu.1d":*87f39623-3deb-4827-aBcf-b862acc93289**trace10: [CREDIT_CARD]-a8cт-0862aec93789-17876-85028 87rS857ocetsts oarane[historyTypes] => nessageAddedstartHistorytoll50%2cornellatiionsd"."ph67/206-7350-172h-6789-40a7456066550 "trace 3o".=62a0Rohp-d660-hcAu-h1de-R/3ceccba6at*[2826-85-28 07:50:58] Local.INF0: SnessageHistory{"correlation_id":"ebf74306-735e-472b-D780-f9a7f56e465S*, "trace_1d":*b2a98e4e-d669-4c04-b1de-843cecc6a601*}2924-85.28 87-55-17 | 1o601 TNEh• Soanans[nistoryTypes) => nessageAddedAwAtU&AtAAuTAl = 26002-4d4a-b84b-a8531ca414a7*}(2826-05-28 87:SS:12] Local.INF0: SmessageHistory{"correlation_id":"25971837-31f1-431b-adS9-e898eb48b0Sf*, "trace_id*:*c3f63c2c-d21b-4d4a-b84b-a8531ca414a7*}[2826-85-28 08:00:53] Local.INF0: Sparansmistorvnvoes => nessageadded[startHistoryid) => 359025connel atson o""naosynoo%ctheyerark -chn chcoaiieuooace aeuei aioowe ser.oSec-hsyiuhichthuierhooR neohieSa oetet shacsadel stom#[CREDIT_CARD].05cc-h52926Ac24h7"2924-85-28 AR-95-27| Toeol TNSh: SoananeTO0У L7oo inu comoy 11:14.54U TextRelayServiceTest~rner hensemwietcan I see it somewhere now on staging or production?Yes. The next time the sync runs and nits a message without X.e-oriainal-o, the warning will now include besaeis prescht in the log contextheckit in Cloud Watch or wherever woduet onlstac na loos go) Saarch tor• o[TextRelayService) Refused nessage: missing X-Ga-Original-To headerThe log entry will now look like:"Delivered-To: catch-all","To: catchesh"The Sync runs on a schedule = check kernel pho to see now trequendyTinecheoulettkewtnseo.ta.escneotlineoloworcortoihcommnotsrhorttexcrelav.swm=wotcan trigger it manually right now on staging/production to get the new log output immediately:docker exec -it docker_lamp_1 php artisan sailbox:text-relay:syncThen check the lons straight after. If there's a refused messace in the current history window vou'll see the new neaders, present array in the warning log immediately. No need to wait for the scheduler.AReinctalAcceot allCan I maybe fetch it form the gmail,- @ CodnAdhotvMModtur TatmeWokwhireht4 spad...
|
NULL
|
-8260083012580980957
|
NULL
|
click
|
ocr
|
NULL
|
PhpStormEV faVsco,ls ~View$2 JY-20915-fix-mis:Cooc PhpStormEV faVsco,ls ~View$2 JY-20915-fix-mis:CoocRunWindow©InternetMessagelnterface.ph/©MailChannelService.php) Kernel.php© SyneMailbox.php© TextRelayServiceTest.phpclass TextReLayServiceMeetingGeneratorNotification#OAuth2Playbooks1—KecalAJotaeoStreaminga TeamTelephonyaUserPilot#Webhook© AbstractService.php©ActivityProviderFactory.php©ActivityService.php© ApiResponseService.phpg conferenceService.php©InsightSeatService.php© InstantMeetingService.php©IntercomService.php©lpapiClient.php©IpapiService.phpLog::warning ( messages ' (TextRelayService) Refused aessage: nissing X-Gn-Orsginal--=Wa holnore nhrInitialFrontendState.phpO tliminnv nhn1editV ACCApt Fle X- X Reject Fle oxaA SF giminny@localhost)[2826-05-28 07:47:50] Local.INFO: Sparamsnzscorylypes = nessageaddee(startHistoryId) => 359021-correlatzon10:%aC0/1105-C2415-4442-7826-85-28 87:47:58 Local.IWFU: SnessagchzscomvA console [PROD]# consoke leu.1d":*87f39623-3deb-4827-aBcf-b862acc93289**trace10: [CREDIT_CARD]-a8cт-0862aec93789-17876-85028 87rS857ocetsts oarane[historyTypes] => nessageAddedstartHistorytoll50%2cornellatiionsd"."ph67/206-7350-172h-6789-40a7456066550 "trace 3o".=62a0Rohp-d660-hcAu-h1de-R/3ceccba6at*[2826-85-28 07:50:58] Local.INF0: SnessageHistory{"correlation_id":"ebf74306-735e-472b-D780-f9a7f56e465S*, "trace_1d":*b2a98e4e-d669-4c04-b1de-843cecc6a601*}2924-85.28 87-55-17 | 1o601 TNEh• Soanans[nistoryTypes) => nessageAddedAwAtU&AtAAuTAl = 26002-4d4a-b84b-a8531ca414a7*}(2826-05-28 87:SS:12] Local.INF0: SmessageHistory{"correlation_id":"25971837-31f1-431b-adS9-e898eb48b0Sf*, "trace_id*:*c3f63c2c-d21b-4d4a-b84b-a8531ca414a7*}[2826-85-28 08:00:53] Local.INF0: Sparansmistorvnvoes => nessageadded[startHistoryid) => 359025connel atson o""naosynoo%ctheyerark -chn chcoaiieuooace aeuei aioowe ser.oSec-hsyiuhichthuierhooR neohieSa oetet shacsadel stom#[CREDIT_CARD].05cc-h52926Ac24h7"2924-85-28 AR-95-27| Toeol TNSh: SoananeTO0У L7oo inu comoy 11:14.54U TextRelayServiceTest~rner hensemwietcan I see it somewhere now on staging or production?Yes. The next time the sync runs and nits a message without X.e-oriainal-o, the warning will now include besaeis prescht in the log contextheckit in Cloud Watch or wherever woduet onlstac na loos go) Saarch tor• o[TextRelayService) Refused nessage: missing X-Ga-Original-To headerThe log entry will now look like:"Delivered-To: catch-all","To: catchesh"The Sync runs on a schedule = check kernel pho to see now trequendyTinecheoulettkewtnseo.ta.escneotlineoloworcortoihcommnotsrhorttexcrelav.swm=wotcan trigger it manually right now on staging/production to get the new log output immediately:docker exec -it docker_lamp_1 php artisan sailbox:text-relay:syncThen check the lons straight after. If there's a refused messace in the current history window vou'll see the new neaders, present array in the warning log immediately. No need to wait for the scheduler.AReinctalAcceot allCan I maybe fetch it form the gmail,- @ CodnAdhotvMModtur TatmeWokwhireht4 spad...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81423
|
2826
|
8
|
2026-05-28T08:14:32.052797+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956072052_m1.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
SlackFileEditViewGoHistoryWindowHelpDOCKER₴81DEV ( SlackFileEditViewGoHistoryWindowHelpDOCKER₴81DEV (docker)₴2-zshscreenpipe"Digest:sha256:d6b7ebd9b71c0480b909d77fceada3df720c6594e4c853228765509b36f52db6Status: Imageis up to date for 438740370364.dkr.ecr.us-east-2.amazonaws.com/jiminny/app/‹438740370364.dkr.ecr.us-east-2.amazonaws.com/jiminny/app/qa:arm64v8-latestWhat's next:View a summary ofimage vulnerabilities and recommendations docker scoutquickviewlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix--config=.php-cs-fixer.dist.plPHP CS Fixer 3.95.1 Adalbertus by Fabien Potencier, Dariusz Ruminskiand contributors.PHP runtime: 8.5.5Loadedconfigdefault from ".php-cs-fixer.dist.php"Running analysis on 7cores with 10files perprocess.5698/5698100%1) app/Services/Mail/TextRelayService.php (statement_indentation)begin diff/home/jiminny/app/Services/Mail/TextRelayService.php+++/home/jiminny/app/Services/Mail/TextRelayService.php-67,7 +67,7 @eSjob = new EmailTextRelay(SmessageId, SrelayedText);Sjob-›onQueue(Constants: :QUEUE_EMAILS);dispatch($job);dispatch($job);Log: :info(' [TextRelayService]Successfully dispatched message', ["message_id'= SmessageId,end diffFixed 1 of 5698 files in 84.046 seconds, 701.18 MBmemory usedDetected deprecations in use (they will stop working in next major release)- Rule set"@PHP74Migration" is deprecated. Use"@PHP7x4Migration"instead.- Rule set "@PHP80Migration" is deprecated. Use "@PHP8x®Migration" instead.- Rule set "@PHP81Migration" is deprecated. Use "@PHP8x1Migration" instead.- Rule set "@PHP82Migration" is deprecated. Use "@PHP8x2Migration" instead.- Rule set "@PHP83Migration" is deprecated. Use "@PHP8x3Migration"instead.- Rule set "@PHP84Migration" is deprecated. Use "@PHP8x4Migration" instead.What's next:Try Docker Debug for seamless, persistent debugging tools in any containeror image »Learn more at https://docs.docker.com/go/debug-cli/lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-• •HomeDMsActivityFilesLater..•More+ED→Jiminny... vw Starrea& platform-backend-...platform-inner-teamChannels# ai-chapter# alerts# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday# jbu-team-info# jiminny-bg# platform-tickets# product_launches# random# releases# support# thank-yous# the_people_of_jimi...• Direct messages& Petko Kashinski% Galya Dimitrova&. Stefka Stoyanova: Todor StamatovStalivan Ganraiou100% <78• Thu 28 May 11:14:31Describe what you are looking for* & platform-inner-team& 106 0• MessagesP Channel OverviewMore v+Yesterday ~Resolve rrir v.oiotPreview in SlackStatusBacklogPriority= MediumAssigneeUnassignedAs of yesterday at 6:20 PM RefreshOpen in Jira*+ SummarizeToday ~NewNikolay Nikolov 10:52 AMФикс за пускане на remote commands за staging:https://github.com/jiminny/app/pull/12132#12132 Fix stage profile for remote commandsrunJIRA: JY-XXXXDeployment notes:• NoneComments1V1jiminny/app| May 26th Added by GitHubMessage & platform-inner-team...
|
NULL
|
9118370973394191574
|
NULL
|
click
|
ocr
|
NULL
|
SlackFileEditViewGoHistoryWindowHelpDOCKER₴81DEV ( SlackFileEditViewGoHistoryWindowHelpDOCKER₴81DEV (docker)₴2-zshscreenpipe"Digest:sha256:d6b7ebd9b71c0480b909d77fceada3df720c6594e4c853228765509b36f52db6Status: Imageis up to date for 438740370364.dkr.ecr.us-east-2.amazonaws.com/jiminny/app/‹438740370364.dkr.ecr.us-east-2.amazonaws.com/jiminny/app/qa:arm64v8-latestWhat's next:View a summary ofimage vulnerabilities and recommendations docker scoutquickviewlukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-docker exec -it docker_lamp_1 ./vendor/bin/php-cs-fixer fix--config=.php-cs-fixer.dist.plPHP CS Fixer 3.95.1 Adalbertus by Fabien Potencier, Dariusz Ruminskiand contributors.PHP runtime: 8.5.5Loadedconfigdefault from ".php-cs-fixer.dist.php"Running analysis on 7cores with 10files perprocess.5698/5698100%1) app/Services/Mail/TextRelayService.php (statement_indentation)begin diff/home/jiminny/app/Services/Mail/TextRelayService.php+++/home/jiminny/app/Services/Mail/TextRelayService.php-67,7 +67,7 @eSjob = new EmailTextRelay(SmessageId, SrelayedText);Sjob-›onQueue(Constants: :QUEUE_EMAILS);dispatch($job);dispatch($job);Log: :info(' [TextRelayService]Successfully dispatched message', ["message_id'= SmessageId,end diffFixed 1 of 5698 files in 84.046 seconds, 701.18 MBmemory usedDetected deprecations in use (they will stop working in next major release)- Rule set"@PHP74Migration" is deprecated. Use"@PHP7x4Migration"instead.- Rule set "@PHP80Migration" is deprecated. Use "@PHP8x®Migration" instead.- Rule set "@PHP81Migration" is deprecated. Use "@PHP8x1Migration" instead.- Rule set "@PHP82Migration" is deprecated. Use "@PHP8x2Migration" instead.- Rule set "@PHP83Migration" is deprecated. Use "@PHP8x3Migration"instead.- Rule set "@PHP84Migration" is deprecated. Use "@PHP8x4Migration" instead.What's next:Try Docker Debug for seamless, persistent debugging tools in any containeror image »Learn more at https://docs.docker.com/go/debug-cli/lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20915-fix-missing-header-text-• •HomeDMsActivityFilesLater..•More+ED→Jiminny... vw Starrea& platform-backend-...platform-inner-teamChannels# ai-chapter# alerts# backend# bugs# confusion-clinic# donut_time# engineering# general# happy_birthday# jbu-team-info# jiminny-bg# platform-tickets# product_launches# random# releases# support# thank-yous# the_people_of_jimi...• Direct messages& Petko Kashinski% Galya Dimitrova&. Stefka Stoyanova: Todor StamatovStalivan Ganraiou100% <78• Thu 28 May 11:14:31Describe what you are looking for* & platform-inner-team& 106 0• MessagesP Channel OverviewMore v+Yesterday ~Resolve rrir v.oiotPreview in SlackStatusBacklogPriority= MediumAssigneeUnassignedAs of yesterday at 6:20 PM RefreshOpen in Jira*+ SummarizeToday ~NewNikolay Nikolov 10:52 AMФикс за пускане на remote commands за staging:https://github.com/jiminny/app/pull/12132#12132 Fix stage profile for remote commandsrunJIRA: JY-XXXXDeployment notes:• NoneComments1V1jiminny/app| May 26th Added by GitHubMessage & platform-inner-team...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81422
|
2827
|
9
|
2026-05-28T08:14:26.929532+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956066929_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09740692,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8374335,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
-1752890267531063518
|
-6618672614029721119
|
visual_change
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
PhpStormEV faVsco,ls ~View$2 JY-20915-fix-mis:CoocRunWindow©InternetMessagelnterface.ph/©MailChannelService.phpMeetingGeneratorNotification#OAuth2Playbooks1—KecalAJotaeoStreaminga TeamTelephony#UserPilot#Webhook© AbstractService.php©ActivityProviderFactory.php©ActivityService.php© ApiResponseService.phpg conferenceService.php©InsightSeatService.php© InstantMeetingService.php@ IntercomService.php©lpapiClient.php©IpapiService.php© ParticipantShareService.phpPlanhatService.php© PlaybackService.php) Kernel.php© SyneMailbox.php© TextRelayServiceTest.phpclass TextReLayServiceprivate function isForCurrentEnvironnentCSEGRRRRARARARSESGASASBSREEstring SexpectedAlias,Snessage = $service->users_nessages-›get(Smaflbox, SnessageId):Log::warning( message:' [TextRelayService) Refused nessage: missing X-Gn-Original-e ValcationWa holnore nhrInitialFrontendState.phpelliminnv nha230te trete thesetefe atet trthe aectbtert, trhim apgectecste, strdhe aACCApt Fle X-X Reject Fle oxgA SF giminny@localhost]A console [PROD]# consoke leu.[2826-05-28 07:47:50] Local.INFO: Sparansnzscorylypes = nessageaddee(startHistoryId) => 359021-correlatzon10:%aC0/1105-C2415-4442-1d":*87f39623-3deb-4827-aBcf-b862acc93289**7826-85-28 87:47:58 Local.IWFU: Snessagchzscomvtrace10: [CREDIT_CARD]-a8cт-0862aec93789-17876-85028 87rS857ocetsts oarane[historyTypes] => nessageAddedstartHistorytoll50%2"ph67/306-7350-172h-6789-4017456066550 "tлaсe 30".-62a0Rp6p-1669-hcR-h1de-R/3ceccba6at*[2826-85-28 07:50:58] Local.INF0: SnessageHistory{"correlation_id":"ebf74306-735e-472b-D780-f9a7f56e465S*, "trace_1d":*b2a98e4e-d669-4c04-b1de-843cecc6a601*}2924-85.28 87-55-17 | 1o601 TNEh• Soanans[nistoryTypes) => nessageAddedAwAtU&AtAAuTAl = 26002-4d4a-b84b-a8531ca414a7*}(2826-05-28 87:SS:12] Local.INF0: SmessageHistory{"correlation_id":"25971837-31f1-431b-adS9-e898eb48b0Sf*, "trace_id*:*c3f63c2c-d21b-4d4a-b84b-a8531ca414a7*}[2826-85-28 08:00:53] Local. INF0: Sparansmistorvnvoes => nessageadded[startHistoryid) => 359025connel atson o""naosynoo%ctheyerark -chn chcoaiieuooace aeuei aioowe ser.oSec-hsyiuhichthuierhooR neohieSa oetet shacsadel stom#[CREDIT_CARD].05cc-h52926Ac24h7"2924-85-28 AR-95-27| Toeol TNSh: SoananeTO0У L7oo nu comoy 11-14.2oU TextRelayServiceTest~aler henwserwietcan I see it somewhere now on staging or production?Yes. The next time the sync runs and nits a message without X.e-oriainal-o, the warning will now include besaeis prescht in the log contextheckit in Cloud Watch or wherever woduet onlstac na loos go) Saarch tor• o[TextRelayService) Refused nessage: missing X-Ga-Original-To headerThe log entry will now look like:"Delivered-To: catch-all","To: catchesh"The Sync runs on a schedule = check kernel pho to see now trequendyTinecheoulettkewtnseo.ta.escneotlineoloworcortoihcommnotsrhorttexcrelav.swm=wotcan trigger it manually right now on staging/production to get the new log output immediately:docker exec -it docker_lamp_1 php artisan sailbox: text-relay:syncThen check the lons straight after. If there's a refused messace in the current history window vou'll see the new neaders, present array in the warning log immediately. No need to wait for the scheduler.AReinctalAcceot allCan I maybe fetch it form the gmail.I- @ CodnAdhotvNMdtur TatmeWohThie%t4 spad...
|
81421
|
NULL
|
NULL
|
NULL
|
|
81421
|
2827
|
8
|
2026-05-28T08:14:24.952463+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956064952_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
PhpStormViewNeweNNCCoocRurToolsWindowFV faVsco.|s PhpStormViewNeweNNCCoocRurToolsWindowFV faVsco.|s ~$ JY-20915-fix-ml) Kernelphp© SyncMailbox.php© InternetMessagelnterface.ph© MailChannelService.phgo Textrelkysewice.ond MeetingGeneratorda Notificationên OAuth2in Playbook.—KeCaLA—oeownyotaeeStreaminga TeamTelephony#UserPilotWebhookC Abstrac Semvice cho©ActivitvProviderFactory.ohoCActwiysemce.ono© ApiResponseService.ohog conferenceService.phgclineehCaatswies donC InstantMeetingService.phgcilntecomswneoeclass TextRelayServiceisForCurrentEnvironment(178C IpapiClient.php© IpapiService.phpC ParticipantShareService.phg©. PlanhatService.php© PlaybackService.phpPlaybackVideoOnlyService.pho© PlaybookCategoryService.php© PlaylistGeneratorinterface.phpUMMNSASRGREВR UUSEUНЕНННSD: bool fDockerfile©TextReiayServiceTest.phgSnessage = Sservice-›users_nessages-xget(Snailbox, SnessageId);Sheaders = Smessage->getPayLoad->getheadersO:foreach (Sheaders as Sheader)1f (Sheader->nanetas 'X-6n-Original-To') (Snatches = Sthis-›natchesExpectedRecipient(Sheader->value, SexpectedAlias, SexpectedHost):iSmarches)/ Sanitize Pll by renoving plus-tag content for loggingSsanitizedOriginalTo = explode( separator: ***, Sheader-›value) [01:Lootenfalt mescaoo.[TextRelayService) Refused message', l'message_id' => Snessageid,'original_to_sanitized' => SsanitizedOriginalTo,return Smatches:Log::warning( message:"(TextRelayService) Refused aessage: missing X-Gn-Oniginal-To header'.'nessage_id' => Smessageld,"headers_present' => array_nap(fn (Sh) => Sh->nane".explode( separator: "+* Sh->value) (el. Sheaders)'[TextRelayService) Failed to inspect message', tC ResolveTeamCrmConnection.ph© SimoleThrottleService.ohd© SlackService.oho© SoclalAccountService.oho)© SoftPhoneService.php©) TeamDeactivatedService.oho©) TeamOwnerService.ohoC) TeamService.oho©TranscodeParameterResolver.ptCUserService.ohdC Uuidloho> M Traitc@ UseCasesuuis>MVacationWa holnore nhr@ tnitislGrantondGtnto nhoO tliminnv nhnHHGRENENUUHA1 usagemivate sunctsm matches synecredeecmemotoim osamient stuim Coymmamano ctrina Cayasctsalocta ahonLLledit # Accept Fle x+ X Reject Fle 0xeACCeptRenect100% 142-• Thu 28 May 11:14:24TextRelayServiceTestcustom.log=IaravellogSF jiminny@localhostA HS_local (jiminny@localhost& console (PROD.de console [EU(2826-05-28 87:47:50) Zocal.INF0: $paranArraThistoryTypes = messageAddedistartHistory]d => 359821"correlation_id*:"9acc7183-e275-4442-a800-72ca0233a(2826-05-28 87:47:591 Zocal.TNF0: SnessageHistorSow/syoloeouroruo0/2a0.000/00cYs/8YAhirayf"coccelationsid*."9acc7183-e275-4642-a80e-72ca0233ad9d* "trace_5id*-*87439623-3deb-4827-aBcf-b862aec93209"}(2826-85-28 87:58:571 Zocoi, TNF0: SparansArrayhistoryTvoesl a> nessageAdded[startHistoryidl => 35982correlatond e0t74586-155e-4120-0480-4987S6e4655trace so.oap8ele-0669-4084-01de-845cecc69681"%12926-95-28 87•59•591 10001, TNED• SoessaoelkStonA console [STAGINGCacAonARGBAGSEGH=3388ЯВВ8ВЕЕНСШ.Oearraro.antlkahoksachnhohkAbtonghnhe-trace_1d":*02a98e4e-d669-4c84-bide-043cесс6а601")ArrayThictomutumsell as racesoo ddodTetantHictomiall s 76002{"correlation id":"25971837-31f1-431b-ad59-e890eb40b0Sf*, "trace id":*c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}(2026-05-28 87:55:17) Zocal.INF0: SnessageHistoryAcrayf"coccelatiions1d*.*25971837-3161-431b-ad59-e898eb48b85f(2826-85-28 88:89:531 10c01, TNF0: Sparantrace_1d*:*c3463c2c-d21b-406a-b84b-a8531ca414a7"}fbistoryTvoesl = nessaceAddedfstartHistonyidl => 35992.{*corcelattion sd*:*46957797-4328-4bf7-8933-961c8cca128c* =trace 3d*:*34082638-3962-45cf-95cc-b52026802£b3*](2826-05-28 88:00:541 10001, INE0= SnessagcHietorscorrelatcionid":41957[PHONE]-8133-961c8661128cPtmaGe•346:[CREDIT_CARD]-6579268621634GOAnK-AC-2R AR-AC-271 Jonol TNED+ SoananAnasyN Wodtur Teams AA0R UTE-R AiA enond...
|
NULL
|
-2785114712100070516
|
NULL
|
click
|
ocr
|
NULL
|
PhpStormViewNeweNNCCoocRurToolsWindowFV faVsco.|s PhpStormViewNeweNNCCoocRurToolsWindowFV faVsco.|s ~$ JY-20915-fix-ml) Kernelphp© SyncMailbox.php© InternetMessagelnterface.ph© MailChannelService.phgo Textrelkysewice.ond MeetingGeneratorda Notificationên OAuth2in Playbook.—KeCaLA—oeownyotaeeStreaminga TeamTelephony#UserPilotWebhookC Abstrac Semvice cho©ActivitvProviderFactory.ohoCActwiysemce.ono© ApiResponseService.ohog conferenceService.phgclineehCaatswies donC InstantMeetingService.phgcilntecomswneoeclass TextRelayServiceisForCurrentEnvironment(178C IpapiClient.php© IpapiService.phpC ParticipantShareService.phg©. PlanhatService.php© PlaybackService.phpPlaybackVideoOnlyService.pho© PlaybookCategoryService.php© PlaylistGeneratorinterface.phpUMMNSASRGREВR UUSEUНЕНННSD: bool fDockerfile©TextReiayServiceTest.phgSnessage = Sservice-›users_nessages-xget(Snailbox, SnessageId);Sheaders = Smessage->getPayLoad->getheadersO:foreach (Sheaders as Sheader)1f (Sheader->nanetas 'X-6n-Original-To') (Snatches = Sthis-›natchesExpectedRecipient(Sheader->value, SexpectedAlias, SexpectedHost):iSmarches)/ Sanitize Pll by renoving plus-tag content for loggingSsanitizedOriginalTo = explode( separator: ***, Sheader-›value) [01:Lootenfalt mescaoo.[TextRelayService) Refused message', l'message_id' => Snessageid,'original_to_sanitized' => SsanitizedOriginalTo,return Smatches:Log::warning( message:"(TextRelayService) Refused aessage: missing X-Gn-Oniginal-To header'.'nessage_id' => Smessageld,"headers_present' => array_nap(fn (Sh) => Sh->nane".explode( separator: "+* Sh->value) (el. Sheaders)'[TextRelayService) Failed to inspect message', tC ResolveTeamCrmConnection.ph© SimoleThrottleService.ohd© SlackService.oho© SoclalAccountService.oho)© SoftPhoneService.php©) TeamDeactivatedService.oho©) TeamOwnerService.ohoC) TeamService.oho©TranscodeParameterResolver.ptCUserService.ohdC Uuidloho> M Traitc@ UseCasesuuis>MVacationWa holnore nhr@ tnitislGrantondGtnto nhoO tliminnv nhnHHGRENENUUHA1 usagemivate sunctsm matches synecredeecmemotoim osamient stuim Coymmamano ctrina Cayasctsalocta ahonLLledit # Accept Fle x+ X Reject Fle 0xeACCeptRenect100% 142-• Thu 28 May 11:14:24TextRelayServiceTestcustom.log=IaravellogSF jiminny@localhostA HS_local (jiminny@localhost& console (PROD.de console [EU(2826-05-28 87:47:50) Zocal.INF0: $paranArraThistoryTypes = messageAddedistartHistory]d => 359821"correlation_id*:"9acc7183-e275-4442-a800-72ca0233a(2826-05-28 87:47:591 Zocal.TNF0: SnessageHistorSow/syoloeouroruo0/2a0.000/00cYs/8YAhirayf"coccelationsid*."9acc7183-e275-4642-a80e-72ca0233ad9d* "trace_5id*-*87439623-3deb-4827-aBcf-b862aec93209"}(2826-85-28 87:58:571 Zocoi, TNF0: SparansArrayhistoryTvoesl a> nessageAdded[startHistoryidl => 35982correlatond e0t74586-155e-4120-0480-4987S6e4655trace so.oap8ele-0669-4084-01de-845cecc69681"%12926-95-28 87•59•591 10001, TNED• SoessaoelkStonA console [STAGINGCacAonARGBAGSEGH=3388ЯВВ8ВЕЕНСШ.Oearraro.antlkahoksachnhohkAbtonghnhe-trace_1d":*02a98e4e-d669-4c84-bide-043cесс6а601")ArrayThictomutumsell as racesoo ddodTetantHictomiall s 76002{"correlation id":"25971837-31f1-431b-ad59-e890eb40b0Sf*, "trace id":*c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}(2026-05-28 87:55:17) Zocal.INF0: SnessageHistoryAcrayf"coccelatiions1d*.*25971837-3161-431b-ad59-e898eb48b85f(2826-85-28 88:89:531 10c01, TNF0: Sparantrace_1d*:*c3463c2c-d21b-406a-b84b-a8531ca414a7"}fbistoryTvoesl = nessaceAddedfstartHistonyidl => 35992.{*corcelattion sd*:*46957797-4328-4bf7-8933-961c8cca128c* =trace 3d*:*34082638-3962-45cf-95cc-b52026802£b3*](2826-05-28 88:00:541 10001, INE0= SnessagcHietorscorrelatcionid":41957[PHONE]-8133-961c8661128cPtmaGe•346:[CREDIT_CARD]-6579268621634GOAnK-AC-2R AR-AC-271 Jonol TNED+ SoananAnasyN Wodtur Teams AA0R UTE-R AiA enond...
|
NULL
|
NULL
|
NULL
|
NULL
|
|
81420
|
2827
|
7
|
2026-05-28T08:14:13.309358+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956053309_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
rapstomEV faVsco,ls ~ViewNavigateCooc$ JY-20915-fi rapstomEV faVsco,ls ~ViewNavigateCooc$ JY-20915-fix-missing-header-text-relTOOI-Window© InternetMessagelnterface.ph© MailChannelService.phpMeetingGeneratoranoucadorEOHUNPlaybooks—KeCaLAJotaeo#UserPilot#WebhookC Abstrac Semvice [EMAIL]©ActivityService.php©ApiResponseService.phpCeon arsncasaries doo©InsightSeatService.php© InstantMeetingService.php@ IntercomService.phpC IpapiClient.php©IpapiService.php© ParticipantShareService.phpPlanhatService.php© PlaybackService.php© PlaybackVideoOnlyService.php© PlaybookCategoryService.php©PlaylistGeneratorinterface.php© SimpleThrottle Service,phpCNUCKEVICHOnCNYWaNCOTMorono© SoftPhoneService.php© SyneMailbox.phpocctonelsthacr.syp0s→2Mawsonce wihhy servcost14@COSS EXKeLAYSENWCHHRAAHHGHASHHHARSHRAYGSpublic function -constructetexterellay isonabort_unless(file_exists(Scredentials),putenv( assignment:"GOOGLE_APPLICATION_CREDENTIALS: • Scredentials):* Fetch the latest messages since the last sync.public function sync(): arraySmailbox = config( key: "jininny.google_text_user');SexpectedAlias = config( key. 'jininny.deploy_region') «== 'eu' ? 'catch-all-eu' : 'catch-all';SexpectedHost = config( key."jininny-google_text_host');Log: :info( message:' (TextRelayService) Starting sync', ["nazloox > swarloox"expecreo aulas sexpecreonclasPDPCIPONOSES SOXORCNCOHOST1):Sservice = Sthis-›getService(Snailbox):© TeamOwnerService.phpceurtMichono©TranscodeParameterResolver.ptC UserSemce.on© Uuid.php› Oo Traits› @ UseCasessnessagenastory = schis-›gechistory(sserv.ce):(Ittuminate\Support\Facades\Log::channel('custon_channel')->info('SmessageHistory ' . PHP_EOL • print_r(SmessageHistory Accept ) RejectSnessadetds = mlelforeach (SnessageHistory as Shistories)Snessages = Shistories->nessagesAdded 72 (J:CUtils>E Va cationforeach (Snessages as Snessage) 4ConccaddTd = Senecnan.550609000550Afl SthiecsteConhunnontEoufconsant/Cconufoa Coaflhoy Conccaoald CaynoctodAline CaxosctodloetlWa holnore nhrInitialFrontendState.phpeillliminnv nhnV Accopt Flo x- X Reject Fle 0xe$0100% KSa- 8• Thu 28 May 11:14:12& Dockerfile© TextRelayServiceTest.phpE .envE custom.log xE laravellogSF jiminny@localhost)(2026-85-28 07:47:50] Local.INFO: SparansArraHS_Jocal ([iminny@localhost)A console [PROD)(historyTypes) => nessageAdded(startHistoryId) => 359821("correlation_id*: *9acc7183-e275-4442-a80e-72ca0233a(2026-85-28 07:47:50] Local. INFO: SnessageHistoryttw/070/0.0000.402/20609030/00CY5X0Y("correlation_id*:*9acc7183-e275-4442-a80e-72ca0233ad9d*, "trace_id*: *87439623-3deb-4827-a0cf-b862aec93209*}[2026-85-28 87:50:57] Local. INFO: SparansArrayhistoryTvoesl a> nessageAdded[startHistory1d] => 359821correlatond e0t74586-155e-4120-0480-4987S6e4655trace so.oap8ele-0669-4084-01de-845cecc69681"%12926-95-28 87•59•591 10001, TNED• SoessaoelkStonArrayOeanrahoanlaheksnchnhoh khbtonyhnhm-trace_1d":*02a98e4e-d669-4c84-bide-043cесс6а601")[2026-05-28 07:5S:17] Local.INFO: SparansArray(historyTypes] => nessageAdded[startHistory]d) => 359821("correlation_id": "25971837-31f1-431b-ad59-e890eb40b05f*, "trace_id": *c3f63c2c-d21b-404a-b84b-a8531ca414a7*}(2026-05-28 87:55:17) Zocal.INF0: SnessageHistoryArray("correlation_id":"25971837-31f1-431b-ad59-e898eb48b05f*[2026-85-28 08:00:53] Local.INFO: Sparanstrace_1d*:*c3463c2c-d21b-406a-b84b-a8531ca414a7"}[historyTypes) => messageAdded[startHistoryId) => 359825("correlation_id":*da957797-f328-4bf7-8a33-961c8cca128C, "trace_1d*:*34c82f30-39f2-45cf-95cc-b520268c2b3*}(2026-85-28 08:00:54] Local. INFO: SnessageHistoryAcnarcorralatcion1d":4957797- 328-4667-8433-961c8cca128c"Ptrace•346:[CREDIT_CARD]-6579268621634GOAnK-AC-2R AR-AC-271 Jonol TNEn+ SoananAnasy# console STAGNGN Wodtur Teams AA0R UTE-R AiA enond...
|
NULL
|
-3508572708559150976
|
NULL
|
click
|
ocr
|
NULL
|
rapstomEV faVsco,ls ~ViewNavigateCooc$ JY-20915-fi rapstomEV faVsco,ls ~ViewNavigateCooc$ JY-20915-fix-missing-header-text-relTOOI-Window© InternetMessagelnterface.ph© MailChannelService.phpMeetingGeneratoranoucadorEOHUNPlaybooks—KeCaLAJotaeo#UserPilot#WebhookC Abstrac Semvice [EMAIL]©ActivityService.php©ApiResponseService.phpCeon arsncasaries doo©InsightSeatService.php© InstantMeetingService.php@ IntercomService.phpC IpapiClient.php©IpapiService.php© ParticipantShareService.phpPlanhatService.php© PlaybackService.php© PlaybackVideoOnlyService.php© PlaybookCategoryService.php©PlaylistGeneratorinterface.php© SimpleThrottle Service,phpCNUCKEVICHOnCNYWaNCOTMorono© SoftPhoneService.php© SyneMailbox.phpocctonelsthacr.syp0s→2Mawsonce wihhy servcost14@COSS EXKeLAYSENWCHHRAAHHGHASHHHARSHRAYGSpublic function -constructetexterellay isonabort_unless(file_exists(Scredentials),putenv( assignment:"GOOGLE_APPLICATION_CREDENTIALS: • Scredentials):* Fetch the latest messages since the last sync.public function sync(): arraySmailbox = config( key: "jininny.google_text_user');SexpectedAlias = config( key. 'jininny.deploy_region') «== 'eu' ? 'catch-all-eu' : 'catch-all';SexpectedHost = config( key."jininny-google_text_host');Log: :info( message:' (TextRelayService) Starting sync', ["nazloox > swarloox"expecreo aulas sexpecreonclasPDPCIPONOSES SOXORCNCOHOST1):Sservice = Sthis-›getService(Snailbox):© TeamOwnerService.phpceurtMichono©TranscodeParameterResolver.ptC UserSemce.on© Uuid.php› Oo Traits› @ UseCasessnessagenastory = schis-›gechistory(sserv.ce):(Ittuminate\Support\Facades\Log::channel('custon_channel')->info('SmessageHistory ' . PHP_EOL • print_r(SmessageHistory Accept ) RejectSnessadetds = mlelforeach (SnessageHistory as Shistories)Snessages = Shistories->nessagesAdded 72 (J:CUtils>E Va cationforeach (Snessages as Snessage) 4ConccaddTd = Senecnan.550609000550Afl SthiecsteConhunnontEoufconsant/Cconufoa Coaflhoy Conccaoald CaynoctodAline CaxosctodloetlWa holnore nhrInitialFrontendState.phpeillliminnv nhnV Accopt Flo x- X Reject Fle 0xe$0100% KSa- 8• Thu 28 May 11:14:12& Dockerfile© TextRelayServiceTest.phpE .envE custom.log xE laravellogSF jiminny@localhost)(2026-85-28 07:47:50] Local.INFO: SparansArraHS_Jocal ([iminny@localhost)A console [PROD)(historyTypes) => nessageAdded(startHistoryId) => 359821("correlation_id*: *9acc7183-e275-4442-a80e-72ca0233a(2026-85-28 07:47:50] Local. INFO: SnessageHistoryttw/070/0.0000.402/20609030/00CY5X0Y("correlation_id*:*9acc7183-e275-4442-a80e-72ca0233ad9d*, "trace_id*: *87439623-3deb-4827-a0cf-b862aec93209*}[2026-85-28 87:50:57] Local. INFO: SparansArrayhistoryTvoesl a> nessageAdded[startHistory1d] => 359821correlatond e0t74586-155e-4120-0480-4987S6e4655trace so.oap8ele-0669-4084-01de-845cecc69681"%12926-95-28 87•59•591 10001, TNED• SoessaoelkStonArrayOeanrahoanlaheksnchnhoh khbtonyhnhm-trace_1d":*02a98e4e-d669-4c84-bide-043cесс6а601")[2026-05-28 07:5S:17] Local.INFO: SparansArray(historyTypes] => nessageAdded[startHistory]d) => 359821("correlation_id": "25971837-31f1-431b-ad59-e890eb40b05f*, "trace_id": *c3f63c2c-d21b-404a-b84b-a8531ca414a7*}(2026-05-28 87:55:17) Zocal.INF0: SnessageHistoryArray("correlation_id":"25971837-31f1-431b-ad59-e898eb48b05f*[2026-85-28 08:00:53] Local.INFO: Sparanstrace_1d*:*c3463c2c-d21b-406a-b84b-a8531ca414a7"}[historyTypes) => messageAdded[startHistoryId) => 359825("correlation_id":*da957797-f328-4bf7-8a33-961c8cca128C, "trace_1d*:*34c82f30-39f2-45cf-95cc-b520268c2b3*}(2026-85-28 08:00:54] Local. INFO: SnessageHistoryAcnarcorralatcion1d":4957797- 328-4667-8433-961c8cca128c"Ptrace•346:[CREDIT_CARD]-6579268621634GOAnK-AC-2R AR-AC-271 Jonol TNEn+ SoananAnasy# console STAGNGN Wodtur Teams AA0R UTE-R AiA enond...
|
81418
|
NULL
|
NULL
|
NULL
|
|
81419
|
2826
|
7
|
2026-05-28T08:14:13.197727+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956053197_m1.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_1
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.019444445,"height":0.02111111},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.015277778,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.014583333,"height":0.025555555},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","depth":4,"on_screen":true,"value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Mail;\n\nuse Carbon\\Carbon;\nuse Illuminate\\Support\\Facades\\Log;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Jobs\\Mailbox\\EmailTextRelay;\nuse Jiminny\\Models\\TextRelay;\nuse Google\\Service\\Gmail as GoogleGmail;\n\nclass TextRelayService\n{\n public function __construct()\n {\n $credentials = storage_path('text-relay.json');\n\n abort_unless(file_exists($credentials), 422);\n\n putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);\n }\n\n /**\n * Fetch the latest messages since the last sync.\n */\n public function sync(): array\n {\n $mailbox = config('jiminny.google_text_user');\n\n $expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';\n $expectedHost = config('jiminny.google_text_host');\n\n Log::info('[TextRelayService] Starting sync', [\n 'mailbox' => $mailbox,\n 'expected_alias' => $expectedAlias,\n 'expected_host' => $expectedHost,\n ]);\n\n $service = $this->getService($mailbox);\n\n $messageHistory = $this->getHistory($service);\n $messageIds = [];\n\n foreach ($messageHistory as $histories) {\n $messages = $histories->messagesAdded ?? [];\n\n foreach ($messages as $message) {\n $messageId = $message->message->id;\n\n if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {\n continue;\n }\n\n $relayedText = TextRelay::where('email_provider_id', $messageId)->first();\n\n if ($relayedText === null) {\n $relayedText = TextRelay::create([\n 'email_provider' => TextRelay::PROVIDER_GSUITE,\n 'email_provider_id' => $messageId,\n 'status' => TextRelay::STATUS_PROCESSING,\n ]);\n }\n\n $job = new EmailTextRelay($messageId, $relayedText);\n $job->onQueue(Constants::QUEUE_EMAILS);\n\n dispatch($job);\n\n Log::info('[TextRelayService] Successfully dispatched message', [\n 'message_id' => $messageId,\n 'text_relay_id' => $relayedText->id,\n 'queue' => Constants::QUEUE_EMAILS,\n ]);\n\n $messageIds[] = $messageId;\n }\n }\n\n Log::info('[TextRelayService] Sync completed', [\n 'mailbox' => $mailbox,\n 'messages_processed' => count($messageIds),\n 'message_ids' => array_slice($messageIds, 0, 10),\n ]);\n\n return $messageIds;\n }\n\n public function getHistory(GoogleGmail $service): array\n {\n $pageToken = null;\n $topic = config('jiminny.google_text_relay_topic');\n $messages = [];\n $historyId = \\Cache::get($topic);\n\n // If we have no history stored, WatchMailboxEvents must not have run yet :/\n if ($historyId == false) {\n $historyId = $this->refreshHistoryPoint($topic);\n }\n\n $params = [\n 'historyTypes' => 'messageAdded',\n 'startHistoryId' => $historyId,\n ];\n\n do {\n try {\n if ($pageToken) {\n $params['pageToken'] = $pageToken;\n }\n\n $historyResponse = $service->users_history->listUsersHistory(\n config('jiminny.google_text_user'),\n $params\n );\n\n $this->setHistoryPoint($topic, (int) $historyResponse->historyId);\n\n if ($historyResponse->getHistory()) {\n $messages = array_merge($messages, $historyResponse->getHistory());\n $pageToken = $historyResponse->getNextPageToken();\n }\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to fetch Gmail history', [\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n } while ($pageToken);\n\n return $messages;\n }\n\n protected function setHistoryPoint(string $topic, int $historyPoint): Carbon\n {\n $expiresAt = now()->addDay();\n\n \\Cache::put($topic, $historyPoint, $expiresAt);\n\n return $expiresAt;\n }\n\n public function getService(string $mailbox): GoogleGmail\n {\n $client = new \\Google_Client();\n $client->useApplicationDefaultCredentials();\n $client->addScope(GoogleGmail::GMAIL_MODIFY);\n $client->setAccessType('offline');\n $client->setSubject($mailbox);\n\n return new GoogleGmail($client);\n }\n\n public function refreshHistoryPoint(string $topic): int\n {\n $mailbox = config('jiminny.google_text_user');\n $service = $this->getService($mailbox);\n\n $watchRequest = new GoogleGmail\\WatchRequest();\n $watchRequest->setLabelIds(['INBOX']);\n $watchRequest->setLabelFilterAction('include');\n $watchRequest->setTopicName($topic);\n\n $watchResponse = $service->users->watch($mailbox, $watchRequest);\n\n //$expiryTimestamp = $watchResponse->expiration / 1000;\n $historyPoint = (int) $watchResponse->historyId;\n\n $this->setHistoryPoint($topic, $historyPoint);\n\n return $historyPoint;\n }\n\n private function isForCurrentEnvironment(\n GoogleGmail $service,\n string $mailbox,\n string $messageId,\n string $expectedAlias,\n string $expectedHost\n ): bool {\n try {\n $message = $service->users_messages->get($mailbox, $messageId);\n $headers = $message->getPayload()->getHeaders();\n\n foreach ($headers as $header) {\n if ($header->name === 'X-Gm-Original-To') {\n $matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);\n\n if (! $matches) {\n // Sanitize PII by removing plus-tag content for logging\n $sanitizedOriginalTo = explode('+', $header->value)[0];\n Log::info('[TextRelayService] Refused message', [\n 'message_id' => $messageId,\n 'original_to_sanitized' => $sanitizedOriginalTo,\n ]);\n }\n\n return $matches;\n }\n }\n\n Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [\n 'message_id' => $messageId,\n 'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),\n ]);\n } catch (\\Exception $e) {\n Log::error('[TextRelayService] Failed to inspect message', [\n 'message_id' => $messageId,\n 'exception' => $e->getMessage(),\n ]);\n \\Sentry::captureException($e);\n }\n\n return false;\n }\n\n private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool\n {\n $parts = explode('@', $recipient, 2);\n\n if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {\n return false;\n }\n\n $localBase = explode('+', $parts[0], 2)[0];\n\n return strcasecmp($localBase, $expectedAlias) === 0;\n }\n}","role_description":"text entry area","is_enabled":true,"is_focused":true,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.088194445,"height":0.027777778},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"6","depth":4,"on_screen":true,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXTextArea","text":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","depth":4,"on_screen":true,"value":"[2026-05-28 07:47:50] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:47:50] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"9acc7103-e275-4442-a80e-72ca0233ad9d\",\"trace_id\":\"87f39623-3deb-4827-a0cf-b862aec93209\"}\n[2026-05-28 07:50:57] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:50:58] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"ebf74306-735e-472b-b780-f9a7f56e4655\",\"trace_id\":\"b2a98e4e-d669-4c04-b1de-043cecc6a601\"}\n[2026-05-28 07:55:17] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359021\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 07:55:17] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"25971837-31f1-431b-ad59-e890eb40b05f\",\"trace_id\":\"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7\"}\n[2026-05-28 08:00:53] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:00:54] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"da957797-f328-4bf7-8a33-961c0cca120c\",\"trace_id\":\"34c82f30-39f2-45cf-95cc-b520260c2fb3\"}\n[2026-05-28 08:05:23] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:05:24] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"c5339dcc-46c1-45a4-81f9-ede0203ab266\",\"trace_id\":\"627196cf-0a09-465a-ab3e-10e546adfcde\"}\n[2026-05-28 08:10:44] local.INFO: $params \nArray\n(\n [historyTypes] => messageAdded\n [startHistoryId] => 359025\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}\n[2026-05-28 08:10:44] local.INFO: $messageHistory \nArray\n(\n)\n {\"correlation_id\":\"8667713e-79c8-46a6-b5a4-b13351d287da\",\"trace_id\":\"52ec740a-b685-4b96-abda-8e5ab1057ea6\"}","role_description":"text entry area","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"Project","depth":3,"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Project","depth":3,"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"New File or Directory…","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Expand Selected","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Collapse All","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Options","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.0,"top":0.0,"width":0.018055556,"height":0.026666667},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
327918965576722946
|
-3660159508063819383
|
click
|
accessibility
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
<?php
declare(strict_types=1);
namespace Jiminny\Services\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
use Jiminny\Component\Queue\Constants;
use Jiminny\Jobs\Mailbox\EmailTextRelay;
use Jiminny\Models\TextRelay;
use Google\Service\Gmail as GoogleGmail;
class TextRelayService
{
public function __construct()
{
$credentials = storage_path('text-relay.json');
abort_unless(file_exists($credentials), 422);
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $credentials);
}
/**
* Fetch the latest messages since the last sync.
*/
public function sync(): array
{
$mailbox = config('jiminny.google_text_user');
$expectedAlias = config('jiminny.deploy_region') === 'eu' ? 'catch-all-eu' : 'catch-all';
$expectedHost = config('jiminny.google_text_host');
Log::info('[TextRelayService] Starting sync', [
'mailbox' => $mailbox,
'expected_alias' => $expectedAlias,
'expected_host' => $expectedHost,
]);
$service = $this->getService($mailbox);
$messageHistory = $this->getHistory($service);
$messageIds = [];
foreach ($messageHistory as $histories) {
$messages = $histories->messagesAdded ?? [];
foreach ($messages as $message) {
$messageId = $message->message->id;
if (! $this->isForCurrentEnvironment($service, $mailbox, $messageId, $expectedAlias, $expectedHost)) {
continue;
}
$relayedText = TextRelay::where('email_provider_id', $messageId)->first();
if ($relayedText === null) {
$relayedText = TextRelay::create([
'email_provider' => TextRelay::PROVIDER_GSUITE,
'email_provider_id' => $messageId,
'status' => TextRelay::STATUS_PROCESSING,
]);
}
$job = new EmailTextRelay($messageId, $relayedText);
$job->onQueue(Constants::QUEUE_EMAILS);
dispatch($job);
Log::info('[TextRelayService] Successfully dispatched message', [
'message_id' => $messageId,
'text_relay_id' => $relayedText->id,
'queue' => Constants::QUEUE_EMAILS,
]);
$messageIds[] = $messageId;
}
}
Log::info('[TextRelayService] Sync completed', [
'mailbox' => $mailbox,
'messages_processed' => count($messageIds),
'message_ids' => array_slice($messageIds, 0, 10),
]);
return $messageIds;
}
public function getHistory(GoogleGmail $service): array
{
$pageToken = null;
$topic = config('jiminny.google_text_relay_topic');
$messages = [];
$historyId = \Cache::get($topic);
// If we have no history stored, WatchMailboxEvents must not have run yet :/
if ($historyId == false) {
$historyId = $this->refreshHistoryPoint($topic);
}
$params = [
'historyTypes' => 'messageAdded',
'startHistoryId' => $historyId,
];
do {
try {
if ($pageToken) {
$params['pageToken'] = $pageToken;
}
$historyResponse = $service->users_history->listUsersHistory(
config('jiminny.google_text_user'),
$params
);
$this->setHistoryPoint($topic, (int) $historyResponse->historyId);
if ($historyResponse->getHistory()) {
$messages = array_merge($messages, $historyResponse->getHistory());
$pageToken = $historyResponse->getNextPageToken();
}
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to fetch Gmail history', [
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
} while ($pageToken);
return $messages;
}
protected function setHistoryPoint(string $topic, int $historyPoint): Carbon
{
$expiresAt = now()->addDay();
\Cache::put($topic, $historyPoint, $expiresAt);
return $expiresAt;
}
public function getService(string $mailbox): GoogleGmail
{
$client = new \Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope(GoogleGmail::GMAIL_MODIFY);
$client->setAccessType('offline');
$client->setSubject($mailbox);
return new GoogleGmail($client);
}
public function refreshHistoryPoint(string $topic): int
{
$mailbox = config('jiminny.google_text_user');
$service = $this->getService($mailbox);
$watchRequest = new GoogleGmail\WatchRequest();
$watchRequest->setLabelIds(['INBOX']);
$watchRequest->setLabelFilterAction('include');
$watchRequest->setTopicName($topic);
$watchResponse = $service->users->watch($mailbox, $watchRequest);
//$expiryTimestamp = $watchResponse->expiration / 1000;
$historyPoint = (int) $watchResponse->historyId;
$this->setHistoryPoint($topic, $historyPoint);
return $historyPoint;
}
private function isForCurrentEnvironment(
GoogleGmail $service,
string $mailbox,
string $messageId,
string $expectedAlias,
string $expectedHost
): bool {
try {
$message = $service->users_messages->get($mailbox, $messageId);
$headers = $message->getPayload()->getHeaders();
foreach ($headers as $header) {
if ($header->name === 'X-Gm-Original-To') {
$matches = $this->matchesExpectedRecipient($header->value, $expectedAlias, $expectedHost);
if (! $matches) {
// Sanitize PII by removing plus-tag content for logging
$sanitizedOriginalTo = explode('+', $header->value)[0];
Log::info('[TextRelayService] Refused message', [
'message_id' => $messageId,
'original_to_sanitized' => $sanitizedOriginalTo,
]);
}
return $matches;
}
}
Log::warning('[TextRelayService] Refused message: missing X-Gm-Original-To header', [
'message_id' => $messageId,
'headers_present' => array_map(fn ($h) => $h->name . ': ' . explode('+', $h->value)[0], $headers),
]);
} catch (\Exception $e) {
Log::error('[TextRelayService] Failed to inspect message', [
'message_id' => $messageId,
'exception' => $e->getMessage(),
]);
\Sentry::captureException($e);
}
return false;
}
private function matchesExpectedRecipient(string $recipient, string $expectedAlias, string $expectedHost): bool
{
$parts = explode('@', $recipient, 2);
if (count($parts) !== 2 || strcasecmp($parts[1], $expectedHost) !== 0) {
return false;
}
$localBase = explode('+', $parts[0], 2)[0];
return strcasecmp($localBase, $expectedAlias) === 0;
}
}
Sync Changes
Hide This Notification
Code changed:
Hide
6
Previous Highlighted Error
Next Highlighted Error
[2026-05-28 07:47:50] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:47:50] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"9acc7103-e275-4442-a80e-72ca0233ad9d","trace_id":"87f39623-3deb-4827-a0cf-b862aec93209"}
[2026-05-28 07:50:57] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:50:58] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"ebf74306-735e-472b-b780-f9a7f56e4655","trace_id":"b2a98e4e-d669-4c04-b1de-043cecc6a601"}
[2026-05-28 07:55:17] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359021
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 07:55:17] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"25971837-31f1-431b-ad59-e890eb40b05f","trace_id":"c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}
[2026-05-28 08:00:53] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:00:54] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"da957797-f328-4bf7-8a33-961c0cca120c","trace_id":"34c82f30-39f2-45cf-95cc-b520260c2fb3"}
[2026-05-28 08:05:23] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:05:24] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"c5339dcc-46c1-45a4-81f9-ede0203ab266","trace_id":"627196cf-0a09-465a-ab3e-10e546adfcde"}
[2026-05-28 08:10:44] local.INFO: $params
Array
(
[historyTypes] => messageAdded
[startHistoryId] => 359025
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
[2026-05-28 08:10:44] local.INFO: $messageHistory
Array
(
)
{"correlation_id":"8667713e-79c8-46a6-b5a4-b13351d287da","trace_id":"52ec740a-b685-4b96-abda-8e5ab1057ea6"}
Project
Project
New File or Directory…
Expand Selected
Collapse All
Options
Hide...
|
81417
|
NULL
|
NULL
|
NULL
|
|
81418
|
2827
|
6
|
2026-05-28T08:14:09.899442+00:00
|
/Users/lukas/.screenpipe/data/data/2026-05-28/1779 /Users/lukas/.screenpipe/data/data/2026-05-28/1779956049899_m2.jpg...
|
PhpStorm
|
faVsco.js – TextRelayService.php
|
1
|
NULL
|
monitor_2
|
NULL
|
NULL
|
NULL
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error...
|
[{"role":"AXButton","text" [{"role":"AXButton","text":"Project: faVsco.js, menu","depth":5,"bounds":{"left":0.025930852,"top":0.019952115,"width":0.03856383,"height":0.025538707},"on_screen":true,"help_text":"~/jiminny/app","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JY-20915-fix-missing-header-text-relay, menu","depth":5,"bounds":{"left":0.064494684,"top":0.019952115,"width":0.09740692,"height":0.025538707},"on_screen":true,"help_text":"Git Branch: JY-20915-fix-missing-header-text-relay","role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Start Listening for PHP Debug Connections","depth":5,"bounds":{"left":0.8374335,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"TextRelayServiceTest","depth":6,"bounds":{"left":0.85272604,"top":0.019952115,"width":0.062832445,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Run 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9155585,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Debug 'TextRelayServiceTest'","depth":6,"bounds":{"left":0.9268617,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"More Actions","depth":6,"bounds":{"left":0.9381649,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"JetBrains AI","depth":5,"bounds":{"left":0.96609044,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Search Everywhere","depth":5,"bounds":{"left":0.9773936,"top":0.019952115,"width":0.011303191,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"IDE and Project Settings","depth":5,"bounds":{"left":0.9886968,"top":0.019952115,"width":0.011303186,"height":0.025538707},"on_screen":true,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Code changed:","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.042220745,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Sync Changes","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Hide This Notification","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.008643617,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":false,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXStaticText","text":"1","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXStaticText","text":"17","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00930851,"height":0.0},"on_screen":false,"role_description":"text"},{"role":"AXButton","text":"Previous Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.00731383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false},{"role":"AXButton","text":"Next Highlighted Error","depth":4,"bounds":{"left":0.27027926,"top":1.0,"width":0.006981383,"height":0.0},"on_screen":false,"role_description":"button","is_enabled":true,"is_focused":false,"is_selected":false,"is_expanded":false}]...
|
7042218098364550308
|
-7767212893431190582
|
click
|
hybrid
|
NULL
|
Project: faVsco.js, menu
JY-20915-fix-missing-head Project: faVsco.js, menu
JY-20915-fix-missing-header-text-relay, menu
Start Listening for PHP Debug Connections
TextRelayServiceTest
Run 'TextRelayServiceTest'
Debug 'TextRelayServiceTest'
More Actions
JetBrains AI
Search Everywhere
IDE and Project Settings
Code changed:
Hide
Sync Changes
Hide This Notification
1
17
Previous Highlighted Error
Next Highlighted Error
=rapstomViewCoocTOOI-WindowFV faVsco.s ~$ JY-20915-fix-missing-header-text-r© InternetMessagelnterface.ph© MailChannelService.phgd MeetingGeneratoranoucadorEOHUNDn PlaybookJotaeoWebhookC Abstrac Semvice cho©ActivitvProviderFactory.oho© ActivityService.php© ApiResponseService.ohoCeonarsneasaries oodg InsightSeatService.phpC InstantMeetingService.phgcilntecomswneoeC IpapiClient.phpC IpapiService.phpeoarcionn shiryMoon©. PlanhatService.php© PlaybackService.phpPlaybackVideoOnlyService.pho© PlaybookCategoryService.php© PlaylistGeneratorinterface.php© Simole ThrottleService.ohdCNYWaNCOTMoronoC SoftPhoneService.ohoC TeamOwnerService.ohoceurtMichonoC) TranscodeParameterResolver.ohC lUse semce.ond© Uuid.php> M TraitcE UseCases>E Va cationWa holnore nhr@ tnitislGrantondGtnto nhoelliminnv nha© SyncMailbox.php©TextReiayServiceTest.phgsectorelsuner.tyoes-soce wwny serwces hork› ise ...HasseXKelayserweEANGBEBSSpublic function -constructe)Scredentals = storace pathtetexterelay.1son"aborsunlesssileexaistsscredentalsrouteny assionment•'GOOGLE_APPLICATION_CREDENTIALS=" . Scredentials)*sareh tho araer mecsooes exoce the noer euoelpublic function sync): arraySmailbox = config( key: "nininny.google_text-user")SexpectedAlias = config( key: "jininny-deploy_region') === 'eu' ? 'catch-all-eu' : "catch-all':SexpectedHost = config(key: "jininny.google_text_host');Log::info( message: '(TextRelayService) Starting sync',marloox > Shazloox'expected alias' => SexpectedALiasPXORCKOONOST LPXORCKROHOSTD):servrens-o@berCheoxIlluminate Suppont| Facades) Lockschannel ( channet: "custon channel")-sinfo("SmessageHistory • , PHP EOL onint r(Snessacelistory.roturn: toue) 3Foreach Smessagelsistory as Shistontes)Smessades = Shistontes-snessanesAdded.ohiefoneach (Smessages as hessace)BeGEas$6 (1 SthisosisforGuncentEnvironnent (Ssorvice, Seailbox. Seessageid. SoxocctedAlias. SexocctodHost))‹contsnulo-X Reject Fle oxc100% 142-• Thu 28 May 11:14:09custom.log=IaravellogSF jiminny@localhostA HS_local (jiminny@localhost& conboeirros.(2826-05-28 87:47:50) Zocal.INF0: $paranArraThistoryTypes = messageAddedistartHistory]d => 359821"correlation_id*:"9acc7183-e275-4442-a800-72ca0233a(2826-05-28 87:47:591 Zocal.TNF0: SnessageHistorttw/070/0.0000.402/20609030/00CY5X0Yf"coccelationsid*."9acc7183-e275-4642-a80e-72ca0233ad9d* "trace_5id*-*87439623-3deb-4827-aBcf-b862aec93209"}(2826-85-28 87:58:571 Zocoi,TNF0: SparanArrayhistoryTvoesl a> nessageAdded[startHistoryidl => 35982correlatond e0t74586-155e-4120-0480-4987S6e4655trace so.oap8ele-0669-4084-01de-845cecc69681"%12926-95-28 87•59•591 10001, TNED• SoessaoelkSton# console STAGNGCeanahoantkilekSn.hnhomhibtonhnd-trace_1d":*02a98e4e-d669-4c84-bide-043cесс6а601")ArrayThictomutumsell ss racesas ddodTetantHictomiall s 76002{"correlation id":"25971837-31f1-431b-ad59-e890eb40b0Sf*, "trace id":*c3f63c2c-d21b-4d4a-b84b-a8531ca414a7"}(2026-05-28 87:55:17) Zocal.INF0: SnessageHistoryAcray"coccelatiions1d*.*25971837-3161-431b-ad59-e898eb48b85f(2826-85-28 88:89:531 1oc01, INF0: Sparantrace_1d*:*c3463c2c-d21b-406a-b84b-a8531ca414a7"}fbistoryTvoesl = nessaceAddedfstartHistonyidl => 35992.{*corcelattion sd*:*46957797-4328-4bf7-8933-961c8cca128c* =trace 3d*:*34082638-3962-45cf-95cc-b52026802£b3*](2826-05-28 88:00:541 10001, INE0= SnessagcHietorsAcnarcorrelatcionid":4957[PHONE]-8133-961c866a128c"PtraceX40:2139-3912-4567-95c6-657826862163"GOAnK-AC-2R AR-AC-271 Jonol TNED+ SoananAnasy1 Wodeud Tesme 4405 UTE-R AiA enond...
|
NULL
|
NULL
|
NULL
|
NULL
|