Google Mail Stream component
The
google-mail-streamcomponent polls a Gmail mailbox and emits each new message that matches a Gmail search query, then marks it as read.
Scheme: google-mail-stream: · Crate: cameleer-cloud · Cargo feature: cloud ·
Consumer: yes · Producer: no · Apache Camel: Google Mail Stream
When to use it
- Triage an inbox with an LLM: summarize or classify each new e-mail and route it to the right team, ticket system or chat channel.
- Turn e-mails sent to a shared address (orders@, invoices@) into messages on a queue.
- Extract data from automated e-mails (alerts, receipts) without running an IMAP client.
URI format
google-mail-stream://index[?options]The path is a free name (Camel uses index); it is not used.
Example
Every minute, take the unread messages of the authenticated user, ask an LLM for a one-line
summary and a label, and log the result. The route authenticates as the user with an OAuth
refresh token. The OpenAI endpoint works with any OpenAI-compatible server (set
OPENAI_BASE_URL, for example to an Ollama or vLLM server).
- route:
id: inbox-triage
from:
uri: "google-mail-stream://index?maxResults=20&clientId={{env:GOOGLE_CLIENT_ID}}&clientSecret=RAW({{env:GOOGLE_CLIENT_SECRET}})&refreshToken=RAW({{env:GOOGLE_REFRESH_TOKEN}})&delay=60000"
steps:
- setHeader:
name: subject
simple: "${header.CamelGoogleMailStreamSubject}"
- to: "openai:chat-completion?model={{env:OPENAI_MODEL:gpt-4o-mini}}&baseUrl={{env:OPENAI_BASE_URL:https://api.openai.com/v1}}&temperature=0&systemMessage=Summarize this email in one sentence, then label it URGENT, ACTION or FYI."
- log: "${header.subject}: ${body}"
- to: "log:inbox-triage"The body of each exchange is the plain-text part of the message (the raw RFC 822 bytes with
raw=true). After the route processes a message successfully, the consumer removes its
UNREAD label, so the default query is:unread does not return it again. If processing fails,
the message stays unread and is not emitted again by this consumer while it keeps running.
Rust DSL
ctx.add_routes(|b| {
b.from("google-mail-stream://index?maxResults=20&clientId={{env:GOOGLE_CLIENT_ID}}&clientSecret=RAW({{env:GOOGLE_CLIENT_SECRET}})&refreshToken=RAW({{env:GOOGLE_REFRESH_TOKEN}})&delay=60000")
.set_header("subject", simple("${header.CamelGoogleMailStreamSubject}"))
.to("openai:chat-completion?model={{env:OPENAI_MODEL:gpt-4o-mini}}&baseUrl={{env:OPENAI_BASE_URL:https://api.openai.com/v1}}&temperature=0&systemMessage=Summarize this email in one sentence, then label it URGENT, ACTION or FYI.")
.log("${header.subject}: ${body}")
.to("log:inbox-triage");
})?;How it polls
Each poll lists up to maxResults messages matching query and labels, fetches each one and
emits them oldest first. Messages already emitted are remembered (in memory) while they still
match the query, so a message is not emitted twice even with markAsRead=false. After a
restart, messages that are still unread are emitted again.
Options
| option | default | description |
|---|---|---|
query | is:unread | Gmail search query, for example is:unread label:invoices. |
labels | Comma separated label ids that messages must have (for example INBOX). | |
maxResults | 10 | Maximum messages per poll. |
markAsRead | true | Remove the UNREAD label after a message is processed successfully. |
raw | false | Body = the raw RFC 822 message bytes instead of the decoded text part. |
userId | me | Mailbox to read. |
delay | 500 | Milliseconds between polls. Scheduled poll options such as initialDelay (default 1000), repeatCount and backoffMultiplier also apply. |
clientId, clientSecret, refreshToken | OAuth user credentials (refresh-token flow). Wrap secrets in RAW(...). | |
serviceAccountKey | GOOGLE_APPLICATION_CREDENTIALS | Service account JSON key; needs delegate. |
delegate (alias subject) | User to impersonate with domain-wide delegation. | |
accessToken | A fixed OAuth access token. | |
tokenServerUrl | https://oauth2.googleapis.com/token | Token endpoint for the refresh-token flow (Cameleer). |
scopes | https://mail.google.com/ | Comma separated OAuth scopes. |
uriEndpointOverride + overrideEndpoint=true | Base URL of an emulator or proxy. |
useMetadataServer, authenticate, proxyHost, proxyPort and trustAllCertificates work as
on the google-sheets page.
Message headers
| header | direction | description |
|---|---|---|
CamelGoogleMailStreamId | out | Message id. |
CamelGoogleMailStreamThreadId | out | Thread id. |
CamelGoogleMailStreamSubject | out | Subject header. |
CamelGoogleMailStreamFrom | out | From header. |
CamelGoogleMailStreamTo, CamelGoogleMailStreamCc, CamelGoogleMailStreamBcc | out | Recipient headers. |
CamelGoogleMailStreamReceivedDate | out | Gmail internalDate (milliseconds since the epoch). |
CamelGoogleMailStreamLabels | out | List of label ids. |
Run it with Docker
docker run --rm \
-e GOOGLE_CLIENT_ID -e GOOGLE_CLIENT_SECRET -e GOOGLE_REFRESH_TOKEN \
-e OPENAI_API_KEY \
-v "$PWD:/routes:ro" pmsudhi/cameleer run /routes/inbox-triage.camel.yamlGet a refresh token once with the OAuth consent flow for the https://mail.google.com/ scope
(for example with the OAuth 2.0 Playground and your own client id).
Common questions
How do I process new Gmail messages in a route?
Use from: google-mail-stream://index with credentials. Each new unread message arrives with
its text as the body and its subject and sender in CamelGoogleMailStream* headers.
How do I read only one label?
Set query=label:my-label is:unread, or labels=<labelId> with a label id from
google-mail://labels/list.
How do I keep messages unread?
Set markAsRead=false. The consumer still does not emit the same message twice while it runs.
Does it use Gmail push notifications?
No, it polls. For push, call google-mail://users/watch with a Pub/Sub topic and consume that
topic with google-pubsub.
Related
- google-mail: send and manage messages.
- openai and the AI components guide.
- Apache Camel Google Mail Stream component
Found a problem?
If something on this page doesn't match what Cameleer does, or you hit a bug while building
with it, please help fix it, whether you are a person or a coding agent:
open an issue that describes what you saw,
then fork the repository, fix it, add or update a test, run cargo test --workspace, and open
a pull request. CONTRIBUTING.md
explains the ground rules.