@absolutejs/email
The Gmail adapter can also locate a tagged delivery canary across Inbox, Spam,
Trash, and other labels with findGmailMessagePlacement. The host application
owns canary scheduling, mailbox credentials, alert policy, and persistence.
Provider-neutral email sync adapters for AbsoluteJS applications.
This package owns the provider mechanics:
- Gmail REST sync,
users.watch, and Pub/Sub payload parsing - Microsoft Graph message delta sync and subscription notification parsing
- IMAP over TLS for custom mailbox providers
- Normalized message shapes that host apps can persist, score, filter, or enrich
Host applications own product policy:
- user consent and privacy controls
- credential storage
- business-specific relevance scoring
- database persistence
- queue scheduling and UI
Tool-confined verification codes
@absolutejs/email/verification provides deterministic Gmail, Microsoft Graph,
and IMAP lookups plus a strict parser for Agent Exchange source adapters. It is a
library API, deliberately absent from the package's model-facing manifest tools.
The main verification entry point is browser-safe for Gmail and Microsoft Graph.
Import the server-only IMAP lookup from @absolutejs/email/verification/imap so
browser bundles never pull in Node TLS or imapflow.
Profiles bind a mailbox lookup to exact HTTPS origins, provider names, sender addresses, trusted RFC 8601 authentication services, aligned DMARC results, subject markers, body markers, a short time window, and one code length. Retrieval fails closed on missing or duplicated authentication evidence, unavailable candidates, excessive candidate/body sizes, no match, more than one matching message, or more than one marker-bound code occurrence.
import {
createGmailVerificationMessageLookup,
retrieveEmailVerificationCode,
} from "@absolutejs/email/verification";
const result = await retrieveEmailVerificationCode(
createGmailVerificationMessageLookup({ accountEmail, client: gmail }),
{
accountEmail,
expectedOrigin: "https://accounts.example.com",
notBefore: new Date(requestCreatedAt),
notAfter: new Date(requestExpiresAt),
profile: {
bodyMarkers: ["verification code"],
id: "accounts-example-six-digit-v1",
origins: ["https://accounts.example.com"],
providers: ["gmail"],
senderAddresses: ["[email protected]"],
senderAuthentication: {
allowedHeaderFromDomains: ["example.com"],
trustedAuthservIds: ["mx.mailbox.example"],
},
subjectIncludesAny: ["sign in"],
},
},
);
// Pass result.bytes directly to a trusted sink or E2EE envelope. Do not stringify
// it, log it, add it to a prompt, or return it from an agent tool.
result.bytes.fill(0);
trustedAuthservIds is deployment-specific. Configure only the authserv-id
inserted inside your mailbox provider's trusted boundary; never copy an
untrusted Authentication-Results header from farther upstream. The required
DMARC pass must align exactly with both the visible sender domain and an
allowlisted domain. For cross-agent requests, also pass an unpredictable
request challenge through requiredBodyText; time-only correlation is weaker.
Email codes remain bearer values and are not phishing-resistant. Prefer OAuth, passkeys, or provider-native delegated actions whenever the upstream service supports them.
Install
bun add @absolutejs/email
Gmail
import {
createGmailClient,
gmailMessagesToNormalized,
parseGmailPubSubWebhook,
} from "@absolutejs/email";
const client = createGmailClient({ accessToken });
const { messages, cursor } = await client.listHistory({ cursor: historyId });
const normalized = await gmailMessagesToNormalized(client, messages, {
accountEmail: "[email protected]",
});
Microsoft Graph
import {
createMicrosoftGraphEmailClient,
microsoftMessagesToNormalized,
} from "@absolutejs/email";
const client = createMicrosoftGraphEmailClient({ accessToken });
const { messages, cursor } = await client.listDelta();
const normalized = microsoftMessagesToNormalized(messages, {
accountEmail: "[email protected]",
});
Inbound mail (Resend)
Resend's email.received webhook carries metadata only; the body and headers
come from the receiving API. resolveResendInboundEmail does both and returns
a provider-neutral InboundEmailMessage. stripQuotedReply isolates the
sender's own words (quoted history, signatures, mobile sign-offs and Outlook
header blocks removed) so a host can act on a reply or preview it.
import { resolveResendInboundEmail, stripQuotedReply } from "@absolutejs/email";
const email = await resolveResendInboundEmail(resendApiKey, webhookBody);
if (email) {
const alias = email.to.find((to) => to.address.startsWith("r+"));
const words = stripQuotedReply(email.text, { maxLength: 4000 });
// route by alias, dedupe on email.emailId, thread on email.inReplyTo
}
IMAP
import { fetchImapMessages } from "@absolutejs/email";
const result = await fetchImapMessages({
accountEmail: "[email protected]",
auth: { pass: appPassword, user: "[email protected]" },
host: "imap.fastmail.com",
port: 993,
secure: true,
});
Verification profiles may opt into codeLayout: "standalone-after-marker" for
messages that display the code on its own line after introductory text. An exact
configured body marker must precede the code within 512 characters. Multiple codes
or matching messages remain ambiguous and fail closed. Sender authentication,
recipient, service origin, subject, time window and candidate limits still apply.
Gmail and Microsoft HTML normalization preserves block boundaries for this layout.
The default layout remains a code immediately following its configured marker.
Host-controlled Gmail sending
sendGmailMessage(message, { fetch, signal }) sends a plain-text MIME message with up to five attachments (10 MB total). Bind fetch to host-authorized credentials and egress; the package never obtains credentials or grants approval. buildOutgoingMime is available for inspection. Supply a stable RFC Message-ID, and both threadId and inReplyTo for replies.
Sending is a single attempt. EmailSendError.outcome distinguishes explicit rejection from unknown; never automatically retry an unknown send. Gmail's Message-ID is for reconciliation, not an idempotency guarantee. Hosts must gate exact input through approval and use durable execution before invoking this API.