Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Send transactional email, batch sends, scheduled delivery, templates, email validation, and track delivery events.
SkillScore100/100
Test results 100
3/3 safety checks passed
29%100%of test cases passed +71 pts
Passed 24 of 24 test cases with the skill, and 7 of 24 without it.
17 fixed7 passed either way0 still failing0 broke
One test taskWrite a curl command to fetch overall recipient-level email statistics (counts and rates) for our Telnyx account for July 2026.
Adds every file the skill has, pinned so you can update it. No account needed.
pip install "decimalai>=0.15"
decimalai skills add team-telnyx-telnyx-email-curl
claude --plugin-dir "$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.agents"The same install from Python, for a notebook or a setup script.
pip install "decimalai>=0.15"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("team-telnyx-telnyx-email-curl")
if result.exit_code:
raise SystemExit(result.problems)
print("Added. Start Claude Code with the command below.")claude --plugin-dir "$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.agents"Type these in a Claude Code session. No account or API key. It writes the skill and any files it bundles, and asks first when its safety rating isn't Passed.
/plugin marketplace add decimal-labs/decimalai-claude-plugins
/plugin install decimalai@decimalai/decimalai:install team-telnyx-telnyx-email-curlThe plugin needs python3 and curl on your machine.
No Python or account. Claude Code picks it up in its next session.
curl -fsSL --create-dirs -o .claude/skills/team-telnyx-telnyx-email-curl/SKILL.md https://app.decimal.ai/s/team-telnyx-telnyx-email-curl/SKILL.md.claude/skills/team-telnyx-telnyx-email-curl/SKILL.mdThis gets SKILL.md only. The skill's 1 other file is not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[claude-agent-sdk]"
decimalai skills add team-telnyx-telnyx-email-curlimport asyncio
from claude_agent_sdk import ClaudeAgentOptions
from decimalai.claude_agent_sdk import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
async def main():
question = "Hello!"
async with support.run(question) as run: # one user message, one run
async for message in run.query(options=ClaudeAgentOptions(max_turns=6)):
print(message)
# In a notebook or async code: await main()
asyncio.run(main())Prefer its own loader? ClaudeAgentOptions(plugins=[skills_plugin()], skills="all") reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[claude-agent-sdk]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("team-telnyx-telnyx-email-curl")
if result.exit_code:
raise SystemExit(result.problems)
import asyncio
from claude_agent_sdk import ClaudeAgentOptions
from decimalai.claude_agent_sdk import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
async def main():
question = "Hello!"
async with support.run(question) as run: # one user message, one run
async for message in run.query(options=ClaudeAgentOptions(max_turns=6)):
print(message)
# In a notebook or async code: await main()
asyncio.run(main())Prefer its own loader? ClaudeAgentOptions(plugins=[skills_plugin()], skills="all") reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/team-telnyx-telnyx-email-curl/SKILL.mdimport asyncio
from pathlib import Path
from claude_agent_sdk import ClaudeAgentOptions, query
skill = Path(".agents/skills/team-telnyx-telnyx-email-curl/SKILL.md").read_text()
options = ClaudeAgentOptions(system_prompt=f"You are a helpful agent.\n\n{skill}", max_turns=6)
async def main():
async for message in query(prompt="Hello!", options=options):
print(message)
# In a notebook or async code: await main()
asyncio.run(main())This gets SKILL.md only. The skill's 1 other file is not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai-agents]"
decimalai skills add team-telnyx-telnyx-email-curlfrom agents import Agent, Runner
from decimalai.openai_agents import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
name=support.name,
instructions=support.instructions, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# In a notebook or async code: result = await Runner.run(agent, "Hello!")
result = Runner.run_sync(agent, "Hello!")Using SandboxAgent? capabilities=[Shell(), decimalai.openai_agents.sandbox_skills()] points its own loader at the same folder.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai-agents]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("team-telnyx-telnyx-email-curl")
if result.exit_code:
raise SystemExit(result.problems)
from agents import Agent, Runner
from decimalai.openai_agents import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
name=support.name,
instructions=support.instructions, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# In a notebook or async code: result = await Runner.run(agent, "Hello!")
result = Runner.run_sync(agent, "Hello!")Using SandboxAgent? capabilities=[Shell(), decimalai.openai_agents.sandbox_skills()] points its own loader at the same folder.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/team-telnyx-telnyx-email-curl/SKILL.mdfrom pathlib import Path
from agents import Agent, Runner
skill = Path(".agents/skills/team-telnyx-telnyx-email-curl/SKILL.md").read_text()
agent = Agent(name="support", instructions=f"You are a helpful agent.\n\n{skill}")
# In a notebook or async code: result = await Runner.run(agent, "Hello!")
result = Runner.run_sync(agent, "Hello!")This gets SKILL.md only. The skill's 1 other file is not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai]"
decimalai skills add team-telnyx-telnyx-email-curlimport json
from openai import OpenAI
from decimalai.openai import agent_config
client = OpenAI()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your tools, as Chat Completions takes them, and what answers each.
TOOLS = [{"type": "function", "function": {
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
# run.system is your prompt, then the skill list.
messages = [{"role": "system", "content": run.system}, run.message]
while True:
reply = client.chat.completions.create(
model="gpt-5.4-mini",
messages=messages,
tools=[*TOOLS, *run.tools], # your tools, then load_skill
).choices[0].message
messages.append(reply)
if not reply.tool_calls:
break
for call in reply.tool_calls:
# load_skill is answered by the run; your own tools by FUNCTIONS.
function = FUNCTIONS.get(call.function.name)
messages.append(run.tool_result(call) or {
"role": "tool", "tool_call_id": call.id,
"content": function(**json.loads(call.function.arguments)) if function
else f"There is no tool named {call.function.name!r}.",
})
print(reply.content)
# The Responses API takes run.response_tools instead of run.tools.The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("team-telnyx-telnyx-email-curl")
if result.exit_code:
raise SystemExit(result.problems)
import json
from openai import OpenAI
from decimalai.openai import agent_config
client = OpenAI()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your tools, as Chat Completions takes them, and what answers each.
TOOLS = [{"type": "function", "function": {
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
# run.system is your prompt, then the skill list.
messages = [{"role": "system", "content": run.system}, run.message]
while True:
reply = client.chat.completions.create(
model="gpt-5.4-mini",
messages=messages,
tools=[*TOOLS, *run.tools], # your tools, then load_skill
).choices[0].message
messages.append(reply)
if not reply.tool_calls:
break
for call in reply.tool_calls:
# load_skill is answered by the run; your own tools by FUNCTIONS.
function = FUNCTIONS.get(call.function.name)
messages.append(run.tool_result(call) or {
"role": "tool", "tool_call_id": call.id,
"content": function(**json.loads(call.function.arguments)) if function
else f"There is no tool named {call.function.name!r}.",
})
print(reply.content)
# The Responses API takes run.response_tools instead of run.tools.No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/team-telnyx-telnyx-email-curl/SKILL.mdfrom pathlib import Path
from openai import OpenAI
client = OpenAI()
skill = Path(".agents/skills/team-telnyx-telnyx-email-curl/SKILL.md").read_text()
reply = client.chat.completions.create(
model="gpt-5.4-mini",
messages=[
{"role": "system", "content": f"You are a helpful agent.\n\n{skill}"},
{"role": "user", "content": "Hello!"},
],
).choices[0].messageThis gets SKILL.md only. The skill's 1 other file is not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[anthropic]"
decimalai skills add team-telnyx-telnyx-email-curlimport anthropic
from decimalai.anthropic import agent_config
client = anthropic.Anthropic()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
question = "Hello!"
with support.run(question) as run: # one user message, one run
answer = client.beta.messages.tool_runner(
model="claude-haiku-5-5",
max_tokens=4096,
system=run.system, # your prompt, then the skill list
tools=[*run.tools], # your tools, then load_skill
messages=[{"role": "user", "content": question}],
).until_done()The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[anthropic]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("team-telnyx-telnyx-email-curl")
if result.exit_code:
raise SystemExit(result.problems)
import anthropic
from decimalai.anthropic import agent_config
client = anthropic.Anthropic()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
question = "Hello!"
with support.run(question) as run: # one user message, one run
answer = client.beta.messages.tool_runner(
model="claude-haiku-5-5",
max_tokens=4096,
system=run.system, # your prompt, then the skill list
tools=[*run.tools], # your tools, then load_skill
messages=[{"role": "user", "content": question}],
).until_done()No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/team-telnyx-telnyx-email-curl/SKILL.mdfrom pathlib import Path
import anthropic
client = anthropic.Anthropic()
skill = Path(".agents/skills/team-telnyx-telnyx-email-curl/SKILL.md").read_text()
reply = client.messages.create(
model="claude-haiku-5-5",
max_tokens=4096,
system=f"You are a helpful agent.\n\n{skill}",
messages=[{"role": "user", "content": "Hello!"}],
)This gets SKILL.md only. The skill's 1 other file is not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[google-genai]"
decimalai skills add team-telnyx-telnyx-email-curlfrom google import genai
from google.genai import types
from decimalai.google_genai import agent_config
client = genai.Client()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your functions, as declarations for the model, and what answers each.
TOOLS = [{"function_declarations": [{
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters_json_schema": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}]}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
config = types.GenerateContentConfig(
system_instruction=run.system, # your prompt, then the skill list
tools=[*TOOLS, *run.tool_params], # your functions, then load_skill
)
contents = [{"role": "user", "parts": [{"text": run.message}]}]
while True:
response = client.models.generate_content(
model="gemini-3.5-flash", contents=contents, config=config,
)
if not response.function_calls:
break
contents.append(response.candidates[0].content)
parts = []
for call in response.function_calls:
# load_skill is answered by the run; your own functions by FUNCTIONS.
# A loop of your own rather than automatic function calling, which
# raises KeyError when the model asks for a function you did not pass.
function = FUNCTIONS.get(call.name)
parts.append(run.tool_result(call) or {"function_response": {
"name": call.name, "id": call.id,
"response": {"result": function(**(call.args or {})) if function
else f"There is no function named {call.name!r}."},
}})
contents.append({"role": "user", "parts": parts})
print(response.text)The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[google-genai]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("team-telnyx-telnyx-email-curl")
if result.exit_code:
raise SystemExit(result.problems)
from google import genai
from google.genai import types
from decimalai.google_genai import agent_config
client = genai.Client()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your functions, as declarations for the model, and what answers each.
TOOLS = [{"function_declarations": [{
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters_json_schema": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}]}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
config = types.GenerateContentConfig(
system_instruction=run.system, # your prompt, then the skill list
tools=[*TOOLS, *run.tool_params], # your functions, then load_skill
)
contents = [{"role": "user", "parts": [{"text": run.message}]}]
while True:
response = client.models.generate_content(
model="gemini-3.5-flash", contents=contents, config=config,
)
if not response.function_calls:
break
contents.append(response.candidates[0].content)
parts = []
for call in response.function_calls:
# load_skill is answered by the run; your own functions by FUNCTIONS.
# A loop of your own rather than automatic function calling, which
# raises KeyError when the model asks for a function you did not pass.
function = FUNCTIONS.get(call.name)
parts.append(run.tool_result(call) or {"function_response": {
"name": call.name, "id": call.id,
"response": {"result": function(**(call.args or {})) if function
else f"There is no function named {call.name!r}."},
}})
contents.append({"role": "user", "parts": parts})
print(response.text)No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/team-telnyx-telnyx-email-curl/SKILL.mdfrom pathlib import Path
from google import genai
from google.genai import types
client = genai.Client()
skill = Path(".agents/skills/team-telnyx-telnyx-email-curl/SKILL.md").read_text()
response = client.models.generate_content(
model="gemini-3.5-flash",
contents="Hello!",
config=types.GenerateContentConfig(system_instruction=f"You are a helpful agent.\n\n{skill}"),
)This gets SKILL.md only. The skill's 1 other file is not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[langchain]" langchain-openai
decimalai skills add team-telnyx-telnyx-email-curlfrom langchain.agents import create_agent
from decimalai.langchain import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = create_agent(
"openai:gpt-5.4-mini",
tools=[], # your tools; the middleware adds load_skill
middleware=[support.middleware], # your prompt, the skill list and load_skill
)
result = agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[langchain]" langchain-openaifrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("team-telnyx-telnyx-email-curl")
if result.exit_code:
raise SystemExit(result.problems)
from langchain.agents import create_agent
from decimalai.langchain import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = create_agent(
"openai:gpt-5.4-mini",
tools=[], # your tools; the middleware adds load_skill
middleware=[support.middleware], # your prompt, the skill list and load_skill
)
result = agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/team-telnyx-telnyx-email-curl/SKILL.mdfrom pathlib import Path
from langchain.agents import create_agent
skill = Path(".agents/skills/team-telnyx-telnyx-email-curl/SKILL.md").read_text()
agent = create_agent("openai:gpt-5.4-mini", tools=[], system_prompt=f"You are a helpful agent.\n\n{skill}")
result = agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})This gets SKILL.md only. The skill's 1 other file is not included.
CrewAI's own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" "crewai>=1.15.27"
decimalai skills add team-telnyx-telnyx-email-curlimport decimalai.crewai
from crewai import Agent, Crew, Task
agent = Agent(
role="Support", goal="Help the customer", backstory="A support agent.",
llm=llm, # your own CrewAI LLM
skills=decimalai.crewai.skills(), # the installed folder
tools=decimalai.crewai.skill_tools(), # reads a skill's files, runs its scripts
)
task = Task(description="Hello!", expected_output="an answer", agent=agent)
Crew(agents=[agent], tasks=[task]).kickoff()The same install from Python, for a notebook or a setup script. CrewAI's own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" "crewai>=1.15.27"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("team-telnyx-telnyx-email-curl")
if result.exit_code:
raise SystemExit(result.problems)
import decimalai.crewai
from crewai import Agent, Crew, Task
agent = Agent(
role="Support", goal="Help the customer", backstory="A support agent.",
llm=llm, # your own CrewAI LLM
skills=decimalai.crewai.skills(), # the installed folder
tools=decimalai.crewai.skill_tools(), # reads a skill's files, runs its scripts
)
task = Task(description="Hello!", expected_output="an answer", agent=agent)
Crew(agents=[agent], tasks=[task]).kickoff()No DecimalAI package. CrewAI's own loader reads the folder, and nothing pins or updates it.
.agents/skills/team-telnyx-telnyx-email-curl/SKILL.mdfrom pathlib import Path
from crewai import Agent, Crew, Task
agent = Agent(
role="Support", goal="Help the customer", backstory="A support agent.",
llm=llm, # your own CrewAI LLM
skills=[Path(".agents/skills")],
)
task = Task(description="Hello!", expected_output="an answer", agent=agent)
Crew(agents=[agent], tasks=[task]).kickoff()This gets SKILL.md only. The skill's 1 other file is not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[adk]"
decimalai skills add team-telnyx-telnyx-email-curlfrom decimalai.adk import DecimalAIPlugin, agent_config
from google.adk.agents import LlmAgent
from google.adk.apps import App
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = LlmAgent(
name="support", # ADK needs a Python identifier
model="gemini-3.5-flash",
instruction=support.instruction, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# DecimalAIPlugin makes each invocation of runners built from this App one trace.
app = App(name="support", root_agent=agent, plugins=[DecimalAIPlugin()])Prefer ADK's own loader? SkillToolset(skills=decimalai.adk.skills(), ...) reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[adk]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("team-telnyx-telnyx-email-curl")
if result.exit_code:
raise SystemExit(result.problems)
from decimalai.adk import DecimalAIPlugin, agent_config
from google.adk.agents import LlmAgent
from google.adk.apps import App
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = LlmAgent(
name="support", # ADK needs a Python identifier
model="gemini-3.5-flash",
instruction=support.instruction, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# DecimalAIPlugin makes each invocation of runners built from this App one trace.
app = App(name="support", root_agent=agent, plugins=[DecimalAIPlugin()])Prefer ADK's own loader? SkillToolset(skills=decimalai.adk.skills(), ...) reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/team-telnyx-telnyx-email-curl/SKILL.mdfrom pathlib import Path
from google.adk.agents import LlmAgent
skill = Path(".agents/skills/team-telnyx-telnyx-email-curl/SKILL.md").read_text()
agent = LlmAgent(
name="support",
model="gemini-3.5-flash",
instruction=lambda context: f"You are a helpful agent.\n\n{skill}",
)This gets SKILL.md only. The skill's 1 other file is not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[pydantic-ai]"
decimalai skills add team-telnyx-telnyx-email-curlfrom pydantic_ai import Agent
from decimalai.pydantic_ai import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
"openai:gpt-4o-mini",
name=support.name,
tools=[], # your tools; the capability adds load_skill
capabilities=[support], # your prompt, the skill list and load_skill
)
# In a notebook or async code: result = await agent.run("Hello!")
result = agent.run_sync("Hello!")On the Pydantic AI harness, decimalai.pydantic_ai_harness.skills(project) uses its own loader instead.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[pydantic-ai]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("team-telnyx-telnyx-email-curl")
if result.exit_code:
raise SystemExit(result.problems)
from pydantic_ai import Agent
from decimalai.pydantic_ai import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
"openai:gpt-4o-mini",
name=support.name,
tools=[], # your tools; the capability adds load_skill
capabilities=[support], # your prompt, the skill list and load_skill
)
# In a notebook or async code: result = await agent.run("Hello!")
result = agent.run_sync("Hello!")On the Pydantic AI harness, decimalai.pydantic_ai_harness.skills(project) uses its own loader instead.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/team-telnyx-telnyx-email-curl/SKILL.mdfrom pathlib import Path
from pydantic_ai import Agent
skill = Path(".agents/skills/team-telnyx-telnyx-email-curl/SKILL.md").read_text()
agent = Agent("openai:gpt-4o-mini", instructions=f"You are a helpful agent.\n\n{skill}")
# In a notebook or async code: result = await agent.run("Hello!")
result = agent.run_sync("Hello!")This gets SKILL.md only. The skill's 1 other file is not included.
Deep Agents' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" deepagents
decimalai skills add team-telnyx-telnyx-email-curlimport decimalai.deepagents
from deepagents import create_deep_agent
agent = create_deep_agent(model=model, **decimalai.deepagents.skills())
agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})The default backend reads files but has no shell. To run a skill's scripts, pass skills(backend=LocalShellBackend(...)).
The same install from Python, for a notebook or a setup script. Deep Agents' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" deepagentsfrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("team-telnyx-telnyx-email-curl")
if result.exit_code:
raise SystemExit(result.problems)
import decimalai.deepagents
from deepagents import create_deep_agent
agent = create_deep_agent(model=model, **decimalai.deepagents.skills())
agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})The default backend reads files but has no shell. To run a skill's scripts, pass skills(backend=LocalShellBackend(...)).
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/team-telnyx-telnyx-email-curl/SKILL.mdfrom pathlib import Path
from deepagents import create_deep_agent
skill = Path(".agents/skills/team-telnyx-telnyx-email-curl/SKILL.md").read_text()
agent = create_deep_agent(model=model, system_prompt=f"You are a helpful agent.\n\n{skill}")
agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})This gets SKILL.md only. The skill's 1 other file is not included.
Its own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" agent-framework
decimalai skills add team-telnyx-telnyx-email-curlimport asyncio
from agent_framework import Agent
from decimalai.agent_framework import skills
agent = Agent(client=client, instructions="You are a helpful agent.", context_providers=[skills()])
asyncio.run(agent.run("Hello!"))It caches skills by default, so restart the process after decimalai skills update.
The same install from Python, for a notebook or a setup script. Its own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" agent-frameworkfrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("team-telnyx-telnyx-email-curl")
if result.exit_code:
raise SystemExit(result.problems)
import asyncio
from agent_framework import Agent
from decimalai.agent_framework import skills
agent = Agent(client=client, instructions="You are a helpful agent.", context_providers=[skills()])
asyncio.run(agent.run("Hello!"))It caches skills by default, so restart the process after decimalai skills update.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/team-telnyx-telnyx-email-curl/SKILL.mdimport asyncio
from pathlib import Path
from agent_framework import Agent
skill = Path(".agents/skills/team-telnyx-telnyx-email-curl/SKILL.md").read_text()
agent = Agent(client=client, instructions=f"You are a helpful agent.\n\n{skill}")
asyncio.run(agent.run("Hello!"))This gets SKILL.md only. The skill's 1 other file is not included.
Strands' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" strands-agents
decimalai skills add team-telnyx-telnyx-email-curlfrom decimalai.strands import skills
from strands import Agent
from strands.vended_tools import file_editor, shell
agent = Agent(
model=model, # always pass one: the default is Amazon Bedrock
system_prompt="You are a helpful agent.",
tools=[file_editor, shell],
plugins=[skills()],
)
agent("Hello!")Strands loads an agent's skills once, so build the agent again after decimalai skills update.
The same install from Python, for a notebook or a setup script. Strands' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" strands-agentsfrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("team-telnyx-telnyx-email-curl")
if result.exit_code:
raise SystemExit(result.problems)
from decimalai.strands import skills
from strands import Agent
from strands.vended_tools import file_editor, shell
agent = Agent(
model=model, # always pass one: the default is Amazon Bedrock
system_prompt="You are a helpful agent.",
tools=[file_editor, shell],
plugins=[skills()],
)
agent("Hello!")Strands loads an agent's skills once, so build the agent again after decimalai skills update.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/team-telnyx-telnyx-email-curl/SKILL.mdfrom pathlib import Path
from strands import Agent
skill = Path(".agents/skills/team-telnyx-telnyx-email-curl/SKILL.md").read_text()
agent = Agent(model=model, system_prompt=f"You are a helpful agent.\n\n{skill}")
agent("Hello!")This gets SKILL.md only. The skill's 1 other file is not included.
Adds every file the skill has, pinned so you can update it. Cursor, Copilot, Cline and Warp read this folder by themselves.
pip install "decimalai>=0.15"
decimalai skills add team-telnyx-telnyx-email-curlNo Python or account. Cursor, Copilot, Cline and Warp read this folder by themselves.
curl -fsSL --create-dirs -o .agents/skills/team-telnyx-telnyx-email-curl/SKILL.md https://app.decimal.ai/s/team-telnyx-telnyx-email-curl/SKILL.md.agents/skills/team-telnyx-telnyx-email-curl/SKILL.mdThis gets SKILL.md only. The skill's 1 other file is not included.
Give its text to your model as instructions. No account or package needed.
This gets SKILL.md only. The skill's 1 other file is not included.
Raw files: /s/team-telnyx-telnyx-email-curl/SKILL.md · /s/team-telnyx-telnyx-email-curl.json
Fetch the file and give its text to your model as instructions. No account or package needed.
curl -fsSL https://app.decimal.ai/s/team-telnyx-telnyx-email-curl/SKILL.mdcurl -fsSL https://app.decimal.ai/s/team-telnyx-telnyx-email-curl.jsonThis gets SKILL.md only. The skill's 1 other file is not included.
You don't need a DecimalAI account.
Saved prompts and skills, pinned versions, and traces of this skill in your runs. DecimalAI does not run your agent.
Claude Code reads files from your project, so DecimalAI config does not apply there.
On Claude Agent SDK: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On OpenAI Agents: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On OpenAI SDK: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Anthropic: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Gemini API: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On LangChain: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On CrewAI: set DECIMAL_API_KEY and add decimalai.init(crewai=True) to record runs and skill use. The skill version stays in your repo.
On Google ADK: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Pydantic AI: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Deep Agents: set DECIMAL_API_KEY and add decimalai.init(langchain=True) to record runs and skill use. The skill version stays in your repo.
On Microsoft Agent Framework: set DECIMAL_API_KEY and add decimalai.init(otel=True, agent_name="support") to record runs and skill use. The skill version stays in your repo.
On Strands: set DECIMAL_API_KEY and add decimalai.init(otel=True, agent_name="support") to record runs and skill use. The skill version stays in your repo.
Editors read files from your project, so DecimalAI config does not apply there.
Anywhere else: record a use with decimalai.log_skill_activation() inside a trace.
# curl is pre-installed on macOS, Linux, and Windows 10+
# jq is required for examples that capture IDs from responses:
# macOS: brew install jq
# Debian/Ubuntu: sudo apt-get install jq
export TELNYX_API_KEY="YOUR_API_KEY_HERE"
All examples below use $TELNYX_API_KEY for authentication.
All API calls can fail with network errors, rate limits (429), validation errors (400 or 422), or authentication errors (401). Preserve the response body so the Telnyx error code and detail remain available:
curl --fail-with-body \
-X POST \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": {"email": "[email protected]", "name": "Example"},
"to": ["[email protected]"],
"subject": "Hello from Telnyx",
"text_body": "Your notification is ready."
}' \
"https://api.telnyx.com/v2/email_messages"
Common errors: 401 invalid credentials, 403 sender-domain eligibility or
verification failure, 404 resource not found, 413 body larger than
8,000,000 bytes, and 422 semantic validation or template-rendering failure.
A 429 is not necessarily a transient throughput limit: it can indicate a
reputation suspension or a daily policy limit. Inspect .errors[0].code and
.errors[0].detail before deciding whether to retry. See the full taxonomy in
references/api-details.md.
https://api.telnyx.com/v2.from.email must be authorized and ready for sending. A successful create response means accepted, queued, scheduled, or sandbox-created—not delivered.from, reply_to, and each to/cc/bcc item accept either a string address or { "email": "...", "name": "..." }. Recipients must be unique across to, cc, and bcc after case-insensitive normalization.subject is required unless template_id is supplied. With template_id, do not also send subject, html_body, or text_body; pass Liquid values in template_variables.page_size (default 25, maximum 100) and the opaque page_cursor returned in .meta.page_cursor. Do not translate these names to page[size] or page[cursor].GET /email_messages currently implements cursor pagination only; no message filters are exposed. Event and recipient list endpoints have their own filters.scheduled_at is the preferred field. If it is invalid or in the past, the API silently sends immediately. Validate the timestamp before sending and confirm the response status is scheduled when future delivery is required. The legacy send_at alias remains accepted; if both are present, scheduled_at wins.POST /email_messages/batch always returns 207 Multi-Status, including when every item succeeds. It does not return 202.Idempotency-Key is supported only on POST /email_messages, POST /email_messages/batch, POST /email_templates, POST /email_validations, and POST /email_validations/batch. Do not send it universally.sandbox_mode: true skips Kafka/MTA delivery, but still persists the message, emits a sandbox event, and creates a non-billable Email Detail Record (EDR). Batch-level sandbox_mode overrides every per-message value.disposition: "inline" plus content_id supports CID references. The whole request body is limited to 8,000,000 bytes (approximately 8 MB), including Base64 overhead.tags are immutable billing/reporting attribution propagated to EDRs and Mission Control. Mailbox labels are mutable workflow state and are a different concept.in_reply_to_message_id, reply_to_all, and forward_of_message_id are accepted by single send but not by batch send. in_reply_to_message_id and forward_of_message_id are mutually exclusive.Do not invent Telnyx parameters, enums, response fields, or webhook fields.
## Additional Operations, read the parameter reference and the response schemas.Primary outbound transactional-email flow.
POST /v2/email_messages
| Parameter | Type | Required | Description |
|---|---|---|---|
from | string or email-address object | Yes | Sender address; object form supports email and optional name. |
to | array[string or email-address object] | Yes | At least one recipient. Must be unique across all recipient lists. |
from_name | string | No | Display name for string from; overrides from.name. |
cc | array[string or object] | No | Carbon-copy recipients. |
bcc | array[string or object] | No | Blind-copy recipients. |
reply_to | string or object | No | Reply-to address; an object name is ignored when stored. |
subject | string | Conditional | Required without template_id. Do not use with a template. |
html_body | string | No | HTML body. Returned only by the message detail endpoint. |
text_body | string | No | Plain-text body. Returned only by the message detail endpoint. |
headers | object[string,string] | No | Custom headers; write-only. |
attachments | array[attachment] | No | Embedded Base64 attachment objects. |
tags | array[string] | No | Immutable EDR/reporting attribution; not returned by the API. |
group_id | UUID or null | No | Unsubscribe group for suppression checks. |
ignore_suppression | boolean | No | Default false; requires email:override scope and cannot override hard-bounce, complaint, or invalid-address suppressions. |
metadata | object | No | Custom write-only metadata. |
tracking_settings | object | No | open_tracking and/or click_tracking; omitted values inherit domain settings. |
template_id | UUID | No | Liquid template ID. Makes subject/body fields mutually exclusive. |
template_variables | object | No | Liquid values; defaults to {}. |
scheduled_at | date-time or null | No | Preferred future send time; invalid/past values send immediately. |
send_at | date-time | No | Deprecated alias for scheduled_at. |
inline_css | boolean | No | Inline CSS before sending; default false. |
sandbox_mode | boolean | No | Persist and emit sandbox records without MTA delivery; default false. |
in_reply_to_message_id | UUID or null | No | Parent Telnyx message; adds RFC 5322 threading headers. |
reply_to_all | boolean or null | No | Reply-all intent; meaningful only with in_reply_to_message_id. |
forward_of_message_id | UUID or null | No | Forward provenance; mutually exclusive with in_reply_to_message_id. |
Idempotency-Key | header string | No | 1–255 letters, digits, _, or -; reuse only for the identical retry. |
curl --fail-with-body \
-X POST \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8e03978e-40d5-43e8-bc93-6894a57f9326" \
-d '{
"from": {"email": "[email protected]", "name": "Example Orders"},
"to": [{"email": "[email protected]", "name": "Customer"}],
"subject": "Order received",
"html_body": "<p>Thanks for your order.</p>",
"text_body": "Thanks for your order.",
"tags": ["order-confirmation"],
"tracking_settings": {"open_tracking": true, "click_tracking": true}
}' \
"https://api.telnyx.com/v2/email_messages"
Primary response fields:
.data.id, .data.status, .data.from, .data.to, .data.cc, .data.bcc.data.subject, .data.template_id, .data.attachments, .data.events.data.created_at, optional .data.scheduled_at, .data.sandbox, and .data.recipient_statuses.suppressed[] when some recipients were removed but at least one remainsSend 1–50 independent messages. Always parse both success and error arrays from
the 207 response.
POST /v2/email_messages/batch
| Parameter | Type | Required | Description |
|---|---|---|---|
messages | array[message] | Yes | 1–50 items; each has the single-send shape except the three threading fields. |
sandbox_mode | boolean | No | Applies to every item and overrides item-level sandbox_mode. |
Idempotency-Key | header string | No | Idempotency for the whole batch request. |
curl --fail-with-body \
-X POST \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: d6aa99f9-df20-4caf-99aa-e4c8b67e80ef" \
-d '{
"messages": [
{
"from": "[email protected]",
"to": ["[email protected]"],
"subject": "Alert one",
"text_body": "First alert"
},
{
"from": "[email protected]",
"to": ["[email protected]"],
"subject": "Alert two",
"text_body": "Second alert"
}
]
}' \
"https://api.telnyx.com/v2/email_messages/batch"
Primary response fields:
.data[] — successful EmailMessage objects.errors[] — failed items with zero-based index, code, and message.meta.total, .meta.succeeded, .meta.failedScheduling uses the normal create endpoint. Confirm .data.status == "scheduled";
an invalid or past timestamp is silently treated as an immediate send.
# Schedule for a future time (compute dynamically so the value is always in the future)
SCHEDULED_AT=$(date -u -v+1H '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date -u -d '+1 hour' '+%Y-%m-%dT%H:%M:%SZ')
# Capture the scheduled message ID for cancellation below
EMAIL_ID=$(curl -s --fail-with-body \
-X POST \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "[email protected]",
"to": ["[email protected]"],
"subject": "Appointment reminder",
"text_body": "Your appointment is tomorrow.",
"scheduled_at": "'$SCHEDULED_AT'"
}' \
"https://api.telnyx.com/v2/email_messages" | jq -r '.data.id')
Cancel only while the message is still scheduled:
DELETE /v2/email_messages/{email_id}/schedule
curl --fail-with-body \
-X DELETE \
-H "Authorization: Bearer $TELNYX_API_KEY" \
"https://api.telnyx.com/v2/email_messages/$EMAIL_ID/schedule"
The cancel response is an EmailMessageResponse whose .data.status is
cancelled.
GET /v2/email_messages uses newest-first cursor pagination and has no filters.
GET /v2/email_messages/{id} returns the detail shape, including bodies.
| Parameter | Type | Required | Description |
|---|---|---|---|
page_size | integer 1–100 | No | Defaults to 25; invalid values are clamped. |
page_cursor | string | No | Opaque URL-safe Base64 cursor from the preceding response. |
id | UUID path | For get | Email message ID. |
curl --get --fail-with-body \
-H "Authorization: Bearer $TELNYX_API_KEY" \
--data-urlencode "page_size=25" \
--data-urlencode "page_cursor=$PAGE_CURSOR" \
"https://api.telnyx.com/v2/email_messages"
curl --fail-with-body \
-H "Authorization: Bearer $TELNYX_API_KEY" \
"https://api.telnyx.com/v2/email_messages/$EMAIL_ID"
The detail response adds .data.text_body and .data.html_body. Continue
pagination only when .meta.page_cursor is present.
Telnyx signs webhook requests with Ed25519 using the
telnyx-signature-ed25519 and telnyx-timestamp headers.
# Verify the signature against: telnyx-timestamp + "|" + the exact raw body.
# Do not parse/re-serialize JSON before verification.
# Reject timestamps more than 5 minutes outside your server clock.
# Deduplicate by event ID, then enqueue work and return 2xx quickly.
# Ed25519 is asymmetric signature verification; it is not HMAC.
Outbound lifecycle normally progresses:
email.queued → email.sending → email.sent → email.delivered
Terminal alternatives include email.bounced and email.failed; deferred,
complaint, engagement, unsubscribe, sandbox, and scheduled events may also occur.
Email webhooks are configured per sender domain—not per message—using
POST /v2/email_domains/{domain_id}/webhooks. A message-send request has no
webhook_url parameter.
Telnyx retries webhook deliveries on timeout or non-2xx responses. Keep your endpoint idempotent — the same event may be delivered multiple times. Return 2xx within 10 seconds to acknowledge receipt.
curl --fail-with-body \
-X POST \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.net/webhooks/email",
"events": ["email.sent", "email.delivered", "email.bounced", "email.failed"]
}' \
"https://api.telnyx.com/v2/email_domains/$DOMAIN_ID/webhooks"
The webhook subscription enum is not the pollable event enum. In particular,
cancelled, daily_limit_exceeded, and system/gateway rejection events can be
pollable without being subscribable webhooks. Conversely, subscriptions use
email.* names while polling uses unprefixed values such as delivered. Read
the complete enum and payload reference
before writing event dispatch code.
Use these when the core send flow needs a follow-up, reusable content, reporting, or pre-send address checks.
| Operation | Endpoint | Optional query parameters |
|---|---|---|
| List one message's events | GET /v2/email_messages/{email_id}/events | page_size, page_cursor |
| List one message's recipients | GET /v2/email_messages/{email_id}/recipients | page_size, page_cursor, status, kind |
| Get one recipient | GET /v2/email_messages/{email_id}/recipients/{recipient_id} | None |
curl --get --fail-with-body \
-H "Authorization: Bearer $TELNYX_API_KEY" \
--data-urlencode "page_size=25" \
--data-urlencode "page_cursor=$PAGE_CURSOR" \
"https://api.telnyx.com/v2/email_messages/$EMAIL_ID/events"
curl --get --fail-with-body \
-H "Authorization: Bearer $TELNYX_API_KEY" \
--data-urlencode "status=bounced" \
--data-urlencode "kind=to" \
"https://api.telnyx.com/v2/email_messages/$EMAIL_ID/recipients"
curl --get --fail-with-body \
-H "Authorization: Bearer $TELNYX_API_KEY" \
"https://api.telnyx.com/v2/email_messages/$EMAIL_ID/recipients/$RECIPIENT_ID"
Recipient status values are queued, sending, sent, deferred,
delivered, bounced, failed, gw_reject, and cancelled. Each recipient
has its own billable flag and SMTP outcome. BCC addresses are returned as null.
Templates use Liquid. Create requires only name; subject and bodies may be
null, and variables are auto-extracted when omitted.
| Operation | Endpoint | Body or query |
|---|---|---|
| Create | POST /v2/email_templates | name; optional subject, html_body, text_body, variables |
| List | GET /v2/email_templates | optional page_size, page_cursor |
| Get | GET /v2/email_templates/{id} | None |
| Replace | PUT /v2/email_templates/{id} | Any template fields; despite the name, behaves identically to PATCH |
| Update | PATCH /v2/email_templates/{id} | One or more template fields |
| Delete | DELETE /v2/email_templates/{id} | None; success is 204 with no body |
| Render | POST /v2/email_templates/{id}/render | optional template_variables; defaults to {} |
curl --fail-with-body \
-X POST \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 91f8e70a-9ca0-43c7-b77c-594935493637" \
-d '{
"name": "order-confirmation",
"subject": "Order {{ order_id }} received",
"html_body": "<p>Hello {{ customer_name }}</p>"
}' \
"https://api.telnyx.com/v2/email_templates"
curl --get --fail-with-body \
-H "Authorization: Bearer $TELNYX_API_KEY" \
--data-urlencode "page_size=25" \
--data-urlencode "page_cursor=$PAGE_CURSOR" \
"https://api.telnyx.com/v2/email_templates"
curl --get --fail-with-body \
-H "Authorization: Bearer $TELNYX_API_KEY" \
"https://api.telnyx.com/v2/email_templates/$TEMPLATE_ID"
curl --fail-with-body \
-X PUT \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "order-confirmation-v2",
"subject": "Order {{ order_id }} confirmed"
}' \
"https://api.telnyx.com/v2/email_templates/$TEMPLATE_ID"
curl --fail-with-body \
-X PATCH \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"subject":"Order {{ order_id }} is confirmed"}' \
"https://api.telnyx.com/v2/email_templates/$TEMPLATE_ID"
curl --fail-with-body \
-X DELETE \
-H "Authorization: Bearer $TELNYX_API_KEY" \
"https://api.telnyx.com/v2/email_templates/$TEMPLATE_ID"
curl --fail-with-body \
-X POST \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"template_variables":{"order_id":"A-100","customer_name":"Ada"}}' \
"https://api.telnyx.com/v2/email_templates/$TEMPLATE_ID/render"
GET /v2/email_events lists account events oldest first. It accepts
event_type as comma-separated or repeated parameters, plus email_id, from,
and to. GET /v2/email_events/stats accepts from and to and returns
recipient-level counts and rates.
curl --get --fail-with-body \
-H "Authorization: Bearer $TELNYX_API_KEY" \
--data-urlencode "event_type=delivered,bounced" \
--data-urlencode "from=2026-07-01T00:00:00Z" \
--data-urlencode "to=2026-07-31T23:59:59Z" \
"https://api.telnyx.com/v2/email_events"
curl --get --fail-with-body \
-H "Authorization: Bearer $TELNYX_API_KEY" \
--data-urlencode "from=2026-07-01T00:00:00Z" \
--data-urlencode "to=2026-07-31T23:59:59Z" \
"https://api.telnyx.com/v2/email_events/stats"
When from is omitted it defaults to 30 days ago. If from is present without
to, the end defaults to from + 30 days.
Single validation is synchronous. Batch validation accepts 1–1,000 addresses, deduplicates them, and returns an asynchronous job to poll.
curl --fail-with-body \
-X POST \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]"}' \
"https://api.telnyx.com/v2/email_validations"
curl --fail-with-body \
-X POST \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"emails":["[email protected]","[email protected]"],
"webhook_url":"https://example.net/webhooks/validation-complete"
}' \
"https://api.telnyx.com/v2/email_validations/batch"
curl --fail-with-body \
-H "Authorization: Bearer $TELNYX_API_KEY" \
"https://api.telnyx.com/v2/email_validations/batch/$BATCH_ID"
Poll until .data.status is completed or failed; completed results are in
.data.results, keyed by the original address.
The 20 operations below are reachable. Use the core tasks above first, then consult references/api-details.md for every optional parameter and full response shape. There are no curl SDK methods: use the raw HTTP method and path shown here.
| Operation | HTTP method | Endpoint | Use when | Required params |
|---|---|---|---|---|
| Send, schedule, or sandbox an email | POST | /v2/email_messages | Create one outbound message. | from, to; subject unless template_id |
| Send a batch | POST | /v2/email_messages/batch | Create up to 50 messages and parse per-item outcomes. | messages |
| List messages | GET | /v2/email_messages | Page through account messages. | None |
| Get a message | GET | /v2/email_messages/{id} | Retrieve bodies and current state. | id |
| Cancel a scheduled message | DELETE | /v2/email_messages/{email_id}/schedule | Stop a message that is still scheduled. | email_id |
| List message events | GET | /v2/email_messages/{email_id}/events | Poll one message's lifecycle. | email_id |
| List message recipients | GET | /v2/email_messages/{email_id}/recipients | Inspect per-recipient outcomes. | email_id |
| Get one message recipient | GET | /v2/email_messages/{email_id}/recipients/{recipient_id} | Inspect one recipient's SMTP state. | email_id, recipient_id |
| Create a template | POST | /v2/email_templates | Store reusable Liquid content. | name |
| List templates | GET | /v2/email_templates | Select or audit templates. | None |
| Get a template | GET | /v2/email_templates/{id} | Retrieve one template. | id |
| Replace a template | PUT | /v2/email_templates/{id} | Compatibility update; behaves like PATCH. | id |
| Update a template | PATCH | /v2/email_templates/{id} | Modify selected template fields. | id |
| Delete a template | DELETE | /v2/email_templates/{id} | Remove reusable content. | id |
| Render a template | POST | /v2/email_templates/{id}/render | Preview Liquid output. | id |
| List account events | GET | /v2/email_events | Query pollable lifecycle/engagement events. | None |
| Get event statistics | GET | /v2/email_events/stats | Compute recipient-level counts and rates. | None |
| Validate one address | POST | /v2/email_validations | Synchronously assess deliverability. | email |
| Create a validation batch | POST | /v2/email_validations/batch | Validate up to 1,000 addresses asynchronously. | emails |
| Get a validation batch | GET | /v2/email_validations/batch/{id} | Poll batch status and retrieve results. | id |
These OpenAPI operations are not routed in the deployed API. Do not call them or count them among the 20 reachable operations:
| Operation | Endpoint | Status |
|---|---|---|
| Delete all account email data matching an address | DELETE /v2/email_messages?address=... | Not yet available |
| Delete one email message and retained child data | DELETE /v2/email_messages/{id} | Not yet available |
For exhaustive operation parameters, complete response schemas, webhook fields, and error codes, see references/api-details.md.
Automatically, when a task matches. Use for notifications, alerts, and automated email workflows.
Published skills whose descriptions closely match this one, with their headline benchmark lift.
Skills that do similar work, with their headline benchmark lift.
From the same source and nearby in the catalogue.