Gmail adapter via OAuth2 — read, send, search, and manage email
| Tier | infrastructure |
| Role | unclassified (baselined) |
| Path | crates/infrastructure/adapters-google-gmail |
| Edition | 2021 |
| Targets | authorize, test_inbox, infrastructure_adapters_google_gmail |
| Public items | 0 across 0 modules |
| Tests | 49 |
What it is for
Gmail adapter via OAuth2 for personal Google accounts.
Provides an async client for the Gmail API v1 using OAuth2 authorization code flow with refresh token persistence.
# Setup
1. Create an OAuth client ID in GCP Console (Web application type) 2. Add http://localhost:8085 as an authorized redirect URI 3. Run the authorize example to get a refresh token:
cargo run -p infrastructure-adapters-google-gmail --example authorize
4. Use the client:
use infrastructure_adapters_google_gmail::GmailClient;
let client = GmailClient::from_files(
"client-secrets.json",
"gmail-token.json",
"http://localhost:8085",
)?;
let messages = client.list_messages(Some("is:unread"), 10, None).await?;
Capabilities
No public items.
How to use it
From `examples/authorize.rs`:
use infrastructure_adapters_google_gmail::{generate_pkce, OAuthConfig};
use std::io::{BufRead, BufReader, Write};
use std::net::TcpListener;
const REDIRECT_URI: &str = "http://localhost:8085";
const SCOPES: &[&str] = &[
"https://www.googleapis.com/auth/gmail.modify",
"https://www.googleapis.com/auth/gmail.send",
"https://www.googleapis.com/auth/gmail.readonly",
];
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = CliArgs::parse(std::env::args().skip(1));
let client_json_path = args
.client_json
From `examples/test_inbox.rs`:
use infrastructure_adapters_google_gmail::GmailClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = GmailClient::from_files(
"/home/nwheelo/.config/wa-bridge/oauth-client.json",
"/home/nwheelo/.config/wa-bridge/gmail-token.json",
"http://localhost:8085",
)?;
println!("=== Unread Messages (last 5) ===\n");
let list = client.list_messages(Some("is:unread"), 5, None).await?;
match list.messages {
Some(refs) => {
for msg_ref in &refs {
Module structure
No public modules: the crate root is its whole surface.
Public surface
No public items.
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
GmailClient | client::GmailClient |
GmailError | error::GmailError |
{build_raw_email,encode_base64url,Header,Label,LabelList,Message,MessageList,MessagePart,MessagePartBody,MessageRef,SendRequest,} | types::{build_raw_email,encode_base64url,Header,Label,LabelList,Message,MessageList,MessagePart,MessagePartBody,MessageRef,SendRequest,} |
{build_raw_mime,email_from_message} | mime::{build_raw_mime,email_from_message} |
{generate_pkce,pkce_challenge,random_state,OAuthConfig,PkcePair,TokenData} | auth::{generate_pkce,pkce_challenge,random_state,OAuthConfig,PkcePair,TokenData} |
Boundary
Reaches into foundation.
Shares tier infrastructure with 82 other crates: infrastructure-acquire, infrastructure-adapters-google-calendar, infrastructure-adapters-google-places, infrastructure-adapters-google-trends, infrastructure-adapters-shodan, infrastructure-adapters-yelp, infrastructure-agent, infrastructure-ai, … (82 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | infrastructure |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/infrastructure/adapters-google-gmail |
| Vocabulary in force (lexicon) | current |
Tier flow. Which tiers this crate's own edges cross.
flowchart LR n_infrastructure["infrastructure"] --> n_foundation["foundation"]
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `foundation-audit-log` | foundation | no | always |
| `foundation-mail-message` | foundation | no | always |
| `infrastructure-fetcher` | infrastructure | yes | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
async-trait | ^0.1 | — | no | always |
base64 | ^0.22 | — | no | always |
chrono | ^0.4 | serde | no | always |
hex | ^0.4 | — | no | always |
reqwest | ^0.12 | json | no | always |
serde | ^1 | derive | no | always |
serde_json | ^1 | — | no | always |
thiserror | ^2 | — | no | always |
tokio | ^1 | net, io-util, macros, rt-multi-thread | no | always |
tracing | ^0.1 | — | no | always |
urlencoding | ^2 | — | no | always |
uuid | ^1 | v4, v7, serde, js | no | always |
Development, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
wiremock | ^0.6 | — | no | always |
Build. None.
Depended on by. 4 workspace crates.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR n_application_google_gmail["application-google-gmail"] -->|uses| SELF n_infrastructure_mail_headers["infrastructure-mail-headers"] -->|uses| SELF n_tools_mail_manager["tools-mail-manager"] -->|uses| SELF n_tools_paced_send["tools-paced-send"] -->|uses| SELF SELF["infrastructure-adapters-google-gmail"] SELF -->|runtime| n_foundation_audit_log["foundation-audit-log"] SELF -->|runtime| n_foundation_mail_message["foundation-mail-message"] SELF -->|runtime| n_infrastructure_fetcher["infrastructure-fetcher"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
| Feature | Enables | On by default |
|---|---|---|
default | secure-egress | yes |
secure-egress | dep:infrastructure-fetcher | yes |
flowchart LR n_default["default"] --> n_secure_egress["secure-egress"] n_secure_egress["secure-egress"] --> n_dep_infrastructure_fetcher["dep:infrastructure-fetcher"]
Targets
| Kind | Name | Source |
|---|---|---|
| example | authorize | `examples/authorize.rs` |
| example | test_inbox | `examples/test_inbox.rs` |
| lib | infrastructure_adapters_google_gmail | `src/lib.rs` |
Error model
No public error type was detected: no public item declares a type named *Error, and no public signature returns one.
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | none detected |
| async runtime | yes |
| database access | none detected |
| network I/O | yes |
| unsafe code | none detected |
| environment variables | none detected |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.
Related capabilities
4 workspace crates depend on this one: application-google-gmail, infrastructure-mail-headers, tools-mail-manager, tools-paced-send.
Verification
| Kind | Count |
|---|---|
| Unit tests | 49 |
| Integration tests | 0 |
| Examples | 2 |
| Doctests | 0 |
What the tests establish, by name:
authorize_url_contains_required_params—src/auth.rsauthorize_url_pkce_contains_challenge_and_state—src/auth.rsdebug_does_not_leak_oauth_config_secret—src/auth.rsdebug_does_not_leak_token_data_secrets—src/auth.rsgenerate_pkce_is_not_deterministic—src/auth.rsgenerate_pkce_produces_rfc_compliant_lengths—src/auth.rsparse_client_secrets—src/auth.rspkce_matches_rfc7636_example—src/auth.rsrandom_state_has_enough_entropy_and_is_distinct—src/auth.rsreject_invalid_secrets—src/auth.rsstrict_egress_client_is_constructible—src/auth.rstoken_data_roundtrip—src/auth.rstoken_expired_check—src/auth.rsfetch_builds_email_from_message—src/capability.rsfetch_maps_401_to_authentication_error—src/capability.rsgmail_client_implements_both_traits_independently—src/capability.rslist_folders_returns_label_ids—src/capability.rslist_maps_404_to_not_found—src/capability.rslist_maps_message_refs_and_next_cursor—src/capability.rslist_with_mailbox_sends_label_query—src/capability.rssend_maps_429_with_no_retry_after_header_to_the_default_not_zero—src/capability.rssend_maps_429_with_retry_after_header_to_that_value—src/capability.rssend_posts_raw_message_and_returns_provider_id—src/capability.rssend_rejects_message_with_no_recipients—src/capability.rsbuild_raw_mime_joins_multiple_recipients—src/mime.rsbuild_raw_mime_multipart_alternative_when_both_bodies_present—src/mime.rsbuild_raw_mime_plain_text_only—src/mime.rsbuild_raw_mime_skips_attachment_with_no_downloaded_content—src/mime.rsbuild_raw_mime_wraps_in_multipart_mixed_with_attachment—src/mime.rscollect_parts_attachment_wins_over_body_mime_type—src/mime.rs- _… 19 more_
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 0 | 0 |
Public modules with a //! block | 0 | 0 |
Metrics
| Metric | Value |
|---|---|
| Rust source files | 7 |
| Source lines | 2478 |
| Code lines | 1965 |
| Public API items | 0 |
| Public modules | 0 |
| Tests | 49 |
| Examples | 2 |
| Cargo features | 2 |
| Direct runtime dependencies | 15 |
| Workspace reverse dependencies | 4 |
pie showData
title Rust source composition
"Code" : 1965
"Blank or comment" : 513
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.