
- 20 installs
- Updated May 12, 2026
- tryshift-sh/skills-store
Helps with ai & agent building tasks.
About
mail is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- AI & Agent Building
- AI-coding skill
Mail by the numbers
- 20 all-time installs (skills.sh)
- Ranked #10,442 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 27, 2026 (Skillselion catalog sync)
npx skills add https://github.com/tryshift-sh/skills-store --skill mailAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 20 |
|---|---|
| Last updated | May 12, 2026 |
| Repository | tryshift-sh/skills-store ↗ |
What it does
Helps with ai & agent building tasks.
Files
Gmail Mail
Use this skill to list recent Gmail threads, inspect a thread in detail, or send a new email.
When to use
- review recent inbox threads
- inspect a thread before replying manually
- send a plain text or HTML email
Authentication
This skill uses the connected gmail provider credential for the current agent.
Invocation
Read actions can be called through Shift's Skill Router:
curl -X POST "$SHIFT_LOCAL_GATEWAY/skill-router/invoke" \
-H "Content-Type: application/json" \
-d '{
"skillProvider": "gmail",
"skill": "mail",
"action": "list-threads",
"input": {
"maxResults": 10
}
}'For sending email, use the precompiled code skill:
node dist/index.js '{"operation":"send","to":"user@example.com","subject":"Hello","text":"Hi from Shift."}'Examples
List threads:
{
"skillProvider": "gmail",
"skill": "mail",
"action": "list-threads",
"input": {
"query": "label:inbox newer_than:7d",
"maxResults": 10
}
}Get a thread:
{
"skillProvider": "gmail",
"skill": "mail",
"action": "get-thread",
"input": {
"threadId": "THREAD_ID"
}
}Send plain text:
node dist/index.js '{"operation":"send","to":"user@example.com","subject":"Launch update","text":"The launch is on track."}'Send HTML:
node dist/index.js '{"operation":"send","to":["user@example.com"],"subject":"Weekly Digest","html":"<h1>Weekly Digest</h1><p>Everything is on track.</p>"}'// src/index.ts
function asAddressList(value) {
if (!value) return [];
return Array.isArray(value) ? value.filter(Boolean) : [value];
}
function toBase64Url(input) {
return Buffer.from(input, "utf8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
}
function encodeMimeHeader(value) {
return /[^\x20-\x7E]/.test(value) ? `=?UTF-8?B?${Buffer.from(value, "utf8").toString("base64")}?=` : value;
}
function buildMultipartAlternative(text, html) {
const boundary = `shift-gmail-${Date.now().toString(36)}`;
return {
contentType: `multipart/alternative; boundary="${boundary}"`,
body: [
`--${boundary}`,
"Content-Type: text/plain; charset=UTF-8",
"",
text,
`--${boundary}`,
"Content-Type: text/html; charset=UTF-8",
"",
html,
`--${boundary}--`,
""
].join("\r\n")
};
}
function buildRawMessage(input) {
const to = asAddressList(input.to);
if (to.length === 0) {
throw new Error("send requires at least one recipient in `to`.");
}
if (!input.subject?.trim()) {
throw new Error("send requires `subject`.");
}
if (!input.text && !input.html) {
throw new Error("send requires `text` or `html`.");
}
const headers = [
`To: ${to.join(", ")}`,
`Subject: ${encodeMimeHeader(input.subject)}`,
"MIME-Version: 1.0"
];
const cc = asAddressList(input.cc);
const bcc = asAddressList(input.bcc);
if (cc.length > 0) headers.push(`Cc: ${cc.join(", ")}`);
if (bcc.length > 0) headers.push(`Bcc: ${bcc.join(", ")}`);
let body = "";
if (input.text && input.html) {
const multipart = buildMultipartAlternative(input.text, input.html);
headers.push(`Content-Type: ${multipart.contentType}`);
body = multipart.body;
} else if (input.html) {
headers.push("Content-Type: text/html; charset=UTF-8");
body = input.html;
} else {
headers.push("Content-Type: text/plain; charset=UTF-8");
body = input.text ?? "";
}
return toBase64Url(`${headers.join("\r\n")}\r
\r
${body}`);
}
async function invokeSend(input) {
const gatewayBase = process.env.SHIFT_LOCAL_GATEWAY;
if (!gatewayBase) {
throw new Error("SHIFT_LOCAL_GATEWAY is required.");
}
const response = await fetch(`${gatewayBase}/skill-router/invoke`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
skillProvider: "gmail",
skill: "mail",
action: "send",
input: {
raw: buildRawMessage(input),
threadId: input.threadId
}
})
});
const responseText = await response.text();
const parsed = responseText ? JSON.parse(responseText) : null;
if (!response.ok) {
const errorMessage = parsed?.error ?? parsed?.message ?? `Skill Router returned ${response.status}.`;
throw new Error(errorMessage);
}
if (!parsed?.ok) {
throw new Error(parsed?.error ?? `Provider returned ${parsed?.status ?? "unknown status"}.`);
}
return parsed.data;
}
async function main() {
const rawPayload = process.argv[2];
if (!rawPayload) {
throw new Error("Pass a single JSON payload string as the first argument.");
}
const input = JSON.parse(rawPayload);
const operation = input.operation ?? "send";
if (operation !== "send") {
throw new Error(`Unsupported operation: ${operation}`);
}
const result = await invokeSend(input);
process.stdout.write(`${JSON.stringify({ operation, ...result }, null, 2)}
`);
}
main().catch((error) => {
const message = error instanceof Error ? error.message : String(error);
process.stderr.write(`${message}
`);
process.exitCode = 1;
});
{
"id": "mail",
"name": "Mail",
"description": "List Gmail threads, inspect thread details, and send email.",
"actions": {
"list-threads": {
"upstream": {
"method": "GET",
"baseUrl": "https://gmail.googleapis.com",
"path": "/gmail/v1/users/me/threads",
"auth": { "mode": "bearer" }
},
"input": {
"queryTemplate": {
"q": "$input.query",
"labelIds": "$input.labelId",
"maxResults": {
"$value": "$input.maxResults",
"$default": 10
},
"pageToken": "$input.pageToken",
"includeSpamTrash": {
"$value": "$input.includeSpamTrash",
"$default": false
}
}
},
"output": {
"responseMap": {
"count": {
"$length": "$response.threads"
},
"nextPageToken": "$response.nextPageToken",
"resultSizeEstimate": "$response.resultSizeEstimate",
"items": {
"$each": "$response.threads",
"map": {
"threadId": "$item.id",
"snippet": "$item.snippet",
"historyId": "$item.historyId"
}
}
}
}
},
"get-thread": {
"upstream": {
"method": "GET",
"baseUrl": "https://gmail.googleapis.com",
"path": "/gmail/v1/users/me/threads/{threadId}",
"auth": { "mode": "bearer" }
},
"input": {
"pathParams": {
"threadId": "$input.threadId"
},
"queryTemplate": {
"format": {
"$value": "$input.format",
"$default": "full"
}
}
},
"output": {
"responseMap": {
"threadId": "$response.id",
"historyId": "$response.historyId",
"snippet": "$response.snippet",
"messages": {
"$each": "$response.messages",
"map": {
"messageId": "$item.id",
"threadId": "$item.threadId",
"labelIds": "$item.labelIds",
"snippet": "$item.snippet",
"internalDate": "$item.internalDate",
"headers": "$item.payload.headers",
"mimeType": "$item.payload.mimeType"
}
}
}
}
},
"send": {
"upstream": {
"method": "POST",
"baseUrl": "https://gmail.googleapis.com",
"path": "/gmail/v1/users/me/messages/send",
"auth": { "mode": "bearer" }
},
"input": {
"bodyTemplate": {
"raw": "$input.raw",
"threadId": "$input.threadId"
}
},
"output": {
"responseMap": {
"messageId": "$response.id",
"threadId": "$response.threadId",
"labelIds": "$response.labelIds"
}
}
}
}
}
Related skills
AI & Agent Buildingagents