Gmail Integration API
Complete reference for integrating Gmail functionality into Lokus. The Gmail API provides OAuth authentication and comprehensive email operations.
Overview
The Gmail integration allows Lokus to:
- Authenticate with Google OAuth 2.0
- Read and send emails
- Search and filter messages
- Manage labels and folders
- Convert emails to notes
- Queue operations for offline support
Authentication
OAuth Flow
The Gmail integration uses OAuth 2.0 for secure authentication.
Initiate Authentication
import { invoke } from '@tauri-apps/api/core';
const authUrl = await invoke<string>('gmail_initiate_auth');
// Open authUrl in browser for user authorization
Returns: Authorization URL to open in browser
Process:
- Generate authorization URL
- User authorizes in browser
- Google redirects with authorization code
- Complete authentication with code
Check for Auth Callback
Poll for OAuth callback completion:
const callback = await invoke<[string, string] | null>('gmail_check_auth_callback');
if (callback) {
const [code, state] = callback;
// Complete authentication
const profile = await invoke('gmail_complete_auth', { code, state });
}
Returns: [code, state]
tuple or null
Complete Authentication
interface GmailProfile {
email: string;
name: string | null;
picture: string | null;
}
const profile = await invoke<GmailProfile>('gmail_complete_auth', {
code: authorizationCode,
state: stateParameter
});
Parameters:
code
(string) - OAuth authorization codestate
(string) - OAuth state parameter
Returns: User profile
Check Authentication Status
const isAuthenticated = await invoke<boolean>('gmail_is_authenticated');
if (isAuthenticated) {
// User is logged in
}
Get User Profile
const profile = await invoke<GmailProfile | null>('gmail_get_profile');
if (profile) {
console.log(`Logged in as: ${profile.email}`);
}
Logout
await invoke('gmail_logout');
Clears stored credentials and tokens.
Email Operations
List Emails
Lists emails with optional filtering.
interface EmailListOptions {
max_results?: number;
page_token?: string;
label_ids?: string[];
include_spam_trash?: boolean;
}
const emails = await invoke<EmailMessage[]>('gmail_list_emails', {
max_results: 50,
page_token: null,
label_ids: ['INBOX'],
include_spam_trash: false
});
Parameters:
max_results
- Maximum number of emails (1-500, default: 50)page_token
- Pagination token for next pagelabel_ids
- Filter by label IDs (e.g., [‘INBOX’, ‘UNREAD’])include_spam_trash
- Include spam and trash (default: false)
Returns: Array of EmailMessage objects
Search Emails
Search emails using Gmail’s search syntax.
const emails = await invoke<EmailMessage[]>('gmail_search_emails', {
query: 'from:example@gmail.com is:unread',
max_results: 20,
page_token: null,
include_spam_trash: false
});
Parameters:
query
- Gmail search query stringmax_results
- Maximum resultspage_token
- Pagination tokeninclude_spam_trash
- Include spam/trash
Gmail Search Syntax:
from:sender@example.com
- From specific senderto:recipient@example.com
- To specific recipientsubject:keyword
- Subject contains keywordis:unread
- Unread messagesis:starred
- Starred messageshas:attachment
- Has attachmentsafter:2024/01/01
- After datebefore:2024/12/31
- Before datelabel:labelname
- Has specific label
Get Single Email
const email = await invoke<EmailMessage>('gmail_get_email', {
message_id: '18f4c2d1a2b3c4d5'
});
Parameters:
message_id
- Email message ID
Returns: Complete EmailMessage with body
Email Message Structure
interface EmailMessage {
id: string;
thread_id: string;
label_ids: string[];
snippet: string;
from: EmailAddress;
to: EmailAddress[];
cc?: EmailAddress[];
subject: string;
body_text?: string;
body_html?: string;
date: string; // ISO 8601 timestamp
is_read: boolean;
is_starred: boolean;
has_attachments: boolean;
}
interface EmailAddress {
name?: string;
email: string;
}
Sending Emails
Send New Email
const messageId = await invoke<string>('gmail_send_email', {
to: [
{ email: 'recipient@example.com', name: 'Recipient Name' }
],
subject: 'Hello from Lokus',
bodyText: 'This is a plain text message.',
bodyHtml: '<p>This is an <strong>HTML</strong> message.</p>',
cc: [
{ email: 'cc@example.com' }
],
bcc: null
});
Parameters:
to
- Array of recipients (required)subject
- Email subject (required)bodyText
- Plain text body (required)bodyHtml
- HTML body (optional)cc
- CC recipients (optional)bcc
- BCC recipients (optional)
Returns: Sent message ID
Notes:
- At least plain text body is required
- HTML body is optional but recommended
- Both plain text and HTML can be provided
Reply to Email
const replyId = await invoke<string>('gmail_reply_email', {
message_id: '18f4c2d1a2b3c4d5',
to: [
{ email: 'original-sender@example.com' }
],
subject: 'Re: Original Subject',
bodyText: 'This is my reply.',
bodyHtml: null,
cc: null
});
Parameters:
message_id
- ID of message being replied toto
- Recipientssubject
- Reply subject (typically “Re: Original”)bodyText
- Reply textbodyHtml
- Reply HTML (optional)cc
- CC recipients (optional)
Returns: Reply message ID
Forward Email
const forwardId = await invoke<string>('gmail_forward_email', {
message_id: '18f4c2d1a2b3c4d5',
to: [
{ email: 'forward-to@example.com' }
],
subject: 'Fwd: Original Subject',
bodyText: 'See forwarded email below.',
bodyHtml: null
});
Parameters:
message_id
- ID of message being forwardedto
- Recipientssubject
- Forward subject (typically “Fwd: Original”)bodyText
- Additional message textbodyHtml
- Additional HTML (optional)
Returns: Forwarded message ID
Email Management
Mark as Read/Unread
// Mark as read
await invoke('gmail_mark_as_read', {
message_ids: ['msg1', 'msg2', 'msg3']
});
// Mark as unread
await invoke('gmail_mark_as_unread', {
message_ids: ['msg1']
});
Parameters:
message_ids
- Array of message IDs
Returns: void
Star/Unstar Emails
// Add star
await invoke('gmail_star_emails', {
message_ids: ['msg1', 'msg2']
});
// Remove star
await invoke('gmail_unstar_emails', {
message_ids: ['msg1']
});
Archive Emails
Removes emails from inbox (moves to All Mail).
await invoke('gmail_archive_emails', {
message_ids: ['msg1', 'msg2']
});
Delete Emails
Moves emails to trash.
await invoke('gmail_delete_emails', {
message_ids: ['msg1']
});
Note: Emails in trash are permanently deleted after 30 days.
Labels
Get All Labels
interface EmailLabel {
id: string;
name: string;
type: string;
message_list_visibility?: string;
label_list_visibility?: string;
}
const labels = await invoke<EmailLabel[]>('gmail_get_labels');
Returns: Array of all Gmail labels
System Labels:
INBOX
- InboxSENT
- Sent mailDRAFT
- DraftsSPAM
- SpamTRASH
- TrashUNREAD
- UnreadSTARRED
- StarredIMPORTANT
- Important
Operation Queue
The Gmail integration includes an operation queue for offline support and rate limiting.
Get Queue Statistics
interface QueueStats {
pending: number;
processing: number;
failed: number;
completed: number;
}
const stats = await invoke<QueueStats>('gmail_get_queue_stats');
Force Process Queue
Manually trigger queue processing.
await invoke('gmail_force_process_queue');
Clear Queue
Clears all pending operations.
await invoke('gmail_clear_queue');
Warning: This discards pending operations permanently.
Integration Examples
Convert Email to Note
async function emailToNote(messageId: string, workspacePath: string) {
// Get email
const email = await invoke<EmailMessage>('gmail_get_email', {
message_id: messageId
});
// Create note content
const content = `# ${email.subject}
**From:** ${email.from.name} <${email.from.email}>
**Date:** ${new Date(email.date).toLocaleString()}
**To:** ${email.to.map(t => t.email).join(', ')}
---
${email.body_text || email.body_html}
`;
// Save as note
const notePath = `${workspacePath}/emails/${email.id}.md`;
await invoke('write_file_content', {
path: notePath,
content
});
return notePath;
}
Email Search Dashboard
interface EmailDashboard {
unread: EmailMessage[];
starred: EmailMessage[];
recent: EmailMessage[];
}
async function buildDashboard(): Promise<EmailDashboard> {
const [unread, starred, recent] = await Promise.all([
invoke<EmailMessage[]>('gmail_search_emails', {
query: 'is:unread',
max_results: 10
}),
invoke<EmailMessage[]>('gmail_search_emails', {
query: 'is:starred',
max_results: 10
}),
invoke<EmailMessage[]>('gmail_list_emails', {
max_results: 20,
label_ids: ['INBOX']
})
]);
return { unread, starred, recent };
}
Bulk Email Operations
async function archiveOldEmails(daysOld: number) {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysOld);
const query = `before:${cutoffDate.toISOString().split('T')[0]}`;
const emails = await invoke<EmailMessage[]>('gmail_search_emails', {
query,
max_results: 500
});
const messageIds = emails.map(e => e.id);
// Archive in batches of 100
for (let i = 0; i < messageIds.length; i += 100) {
const batch = messageIds.slice(i, i + 100);
await invoke('gmail_archive_emails', { message_ids: batch });
}
}
Email Templates
interface EmailTemplate {
subject: string;
body: string;
}
const templates: Record<string, EmailTemplate> = {
meeting: {
subject: 'Meeting Invitation',
body: `Hi {name},
I'd like to schedule a meeting to discuss {topic}.
When: {date} at {time}
Where: {location}
Please let me know if this works for you.
Best regards,
{sender}`
},
followup: {
subject: 'Following up on {topic}',
body: `Hi {name},
Just following up on our discussion about {topic}.
{content}
Let me know if you have any questions.
Best,
{sender}`
}
};
function fillTemplate(
templateName: string,
variables: Record<string, string>
): EmailTemplate {
const template = templates[templateName];
let subject = template.subject;
let body = template.body;
for (const [key, value] of Object.entries(variables)) {
subject = subject.replace(`{${key}}`, value);
body = body.replace(new RegExp(`\\{${key}\\}`, 'g'), value);
}
return { subject, body };
}
// Usage
const email = fillTemplate('meeting', {
name: 'John',
topic: 'Project Planning',
date: '2024-01-15',
time: '2:00 PM',
location: 'Conference Room A',
sender: 'Jane'
});
await invoke('gmail_send_email', {
to: [{ email: 'john@example.com', name: 'John' }],
subject: email.subject,
bodyText: email.body
});
Error Handling
Authentication Errors
try {
await invoke('gmail_initiate_auth');
} catch (error) {
if (error.includes('OAuth server failed')) {
console.error('OAuth server error:', error);
// Restart OAuth server or show error to user
}
}
API Rate Limits
Gmail API has usage quotas:
- 1 billion requests/day
- 250 quota units per user per second
The integration handles rate limiting automatically with the operation queue.
Network Errors
try {
const emails = await invoke('gmail_list_emails', options);
} catch (error) {
if (error.includes('Network')) {
// Network error - operations will be queued
console.log('Operation queued for when online');
} else {
// Other error
console.error('Gmail API error:', error);
}
}
Security
Token Storage
OAuth tokens are stored securely using platform-specific secure storage:
- macOS: Keychain
- Windows: Windows Credential Manager
- Linux: Secret Service API / libsecret
Tokens are never stored in plain text.
Permissions
The integration requests these OAuth scopes:
https://www.googleapis.com/auth/gmail.readonly
- Read emailshttps://www.googleapis.com/auth/gmail.send
- Send emailshttps://www.googleapis.com/auth/gmail.modify
- Modify labels and statehttps://www.googleapis.com/auth/userinfo.email
- User profilehttps://www.googleapis.com/auth/userinfo.profile
- User profile
Token Refresh
Access tokens are automatically refreshed when they expire. The refresh token is stored securely and used to obtain new access tokens.
Performance
Batch Operations
Use bulk operations to reduce API calls:
// Good - Single API call
await invoke('gmail_mark_as_read', {
message_ids: ['msg1', 'msg2', 'msg3']
});
// Bad - Multiple API calls
for (const id of ['msg1', 'msg2', 'msg3']) {
await invoke('gmail_mark_as_read', { message_ids: [id] });
}
Pagination
For large result sets, use pagination:
async function getAllEmails() {
const allEmails: EmailMessage[] = [];
let pageToken: string | null = null;
do {
const response = await invoke<EmailMessage[]>('gmail_list_emails', {
max_results: 500,
page_token: pageToken
});
allEmails.push(...response);
// Get next page token from response metadata
pageToken = getNextPageToken(response);
} while (pageToken);
return allEmails;
}
Caching
Cache frequently accessed data:
class EmailCache {
private cache = new Map<string, EmailMessage>();
private ttl = 5 * 60 * 1000; // 5 minutes
async getEmail(id: string): Promise<EmailMessage> {
const cached = this.cache.get(id);
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.data;
}
const email = await invoke<EmailMessage>('gmail_get_email', {
message_id: id
});
this.cache.set(id, {
data: email,
timestamp: Date.now()
});
return email;
}
}
Troubleshooting
OAuth Fails
- Check OAuth credentials are configured
- Verify redirect URI is correct
- Check network connectivity
- Clear cached credentials and retry
Emails Not Syncing
- Check authentication status
- Verify network connection
- Check operation queue for errors
- Review API quota usage
Missing Emails
- Check label filters
- Verify
include_spam_trash
setting - Check date range filters
- Review search query syntax
Next Steps
- Tauri Commands - Backend API
- Configuration - Configure Gmail integration
- Advanced Customization - Custom email workflows