Send User Messages

Su Suriya Ravichandran Updated 10 Nov 2026
10 min read ·Lesson 18 of 200

What a User Message Is

A user message is a turn in the conversation that comes from the user side: the question, task, or data you want Claude to respond to. Every request contains at least one:

messages = [
    {"role": "user", "content": "Explain what a Python virtual environment is."}
]

Each message is a dictionary with two keys:

KeyValueMeaning
role"user"This turn comes from the user side of the conversation
contentA string or a list of content blocksWhat the user is saying or providing

"User side" does not always mean a human typed it. Your application often writes user messages itself, for example by combining a fixed instruction with a document to summarize. The user role simply marks the input Claude should respond to. Your application's persistent rules belong in the system parameter (Lesson 17).


Rules for the messages List

The messages parameter is a list of turns in chronological order. A few structural rules apply.

1. The Roles

{"role": "user", "content": "..."}        # Input to respond to
{"role": "assistant", "content": "..."}   # Claude's earlier replies

These two roles make up the conversation itself. On current models, a third role, "system", can also appear inside messages to add an operator instruction partway through a conversation, subject to placement rules (Lessons 17 and 23). Any other value, such as "human", "bot", or "ai", is rejected.

2. Conversations Start with a User Message

The first turn is the user's input. Claude's replies follow it. Instructions that apply from the start go in the top-level system parameter, not in the first message.

3. The Conversation Must End with User Input

On current models (Claude 4.6 and later), the conversation must end with user input. That is normally a user message, or a mid-conversation system message placed directly after a user message. Ending with an assistant message, a technique called prefill that older models supported, returns a 400 invalid_request_error:

This model does not support assistant message prefill. The conversation must end with a user message.

Why it matters: older tutorials sometimes end the list with a partial assistant message to force the start of the reply, such as {"role": "assistant", "content": "{"} to force JSON. That no longer works on current models. Use instructions or structured outputs (Lessons 101–110) instead.

4. Turns Normally Alternate

A conversation usually alternates user, assistant, user, assistant, … If you send two consecutive messages with the same role, the API combines them into a single turn. It is clearer to build one message with several content blocks yourself.

messages = [
    {"role": "user", "content": "My name is Priya."},
    {"role": "assistant", "content": "Nice to meet you, Priya!"},
    {"role": "user", "content": "What is my name?"},   # Last message: user
]

Multi-turn conversations are covered in Lessons 23–28. This lesson focuses on writing and building the user messages themselves.


The Two Forms of content

String Form

{"role": "user", "content": "Summarize the benefits of unit testing."}

This is the simplest form, for messages that are just text.

Block Form

{
    "role": "user",
    "content": [
        {"type": "text", "text": "Summarize the benefits of unit testing."},
    ],
}

As Lesson 16 explained, the string is shorthand for a list containing one text block. Use the block form when a message needs:

  • several parts, such as an instruction plus data,
  • non-text content, such as images or documents (Lessons 111–120),
  • per-block settings, such as prompt caching (Lessons 121–130), or
  • tool results (Lesson 87).
{
    "role": "user",
    "content": [
        {"type": "image", "source": {"type": "url", "url": "https://example.com/diagram.png"}},
        {"type": "text", "text": "Explain what this architecture diagram shows."},
    ],
}

Writing Effective User Messages

The quality of Claude's answer depends heavily on the quality of the request. The Prompting section (Lessons 31–40) covers this in depth. Three principles matter from the start.

1. Be Specific About the Task

Vague:     Tell me about lists.
Specific:  Explain Python lists to a beginner: how to create one, add items, and loop
           over it. Include one short code example for each.

The specific version states the audience, the scope, and the expected output, so Claude does not have to guess.

2. Separate Instructions from Data

When a message contains both an instruction and data, such as a document, an email, or code, mark clearly where the data begins and ends. XML-style tags work very well with Claude:

email = "Hi team, the deploy is moved to Friday. Please update your tickets. – Ravi"

content = f"""Extract the action items from the email below.

<email>
{email}
</email>

Return a numbered list."""

Why delimiters help:

  • Claude knows exactly which text is the data and which is the instruction.
  • If the data itself contains instruction-like sentences, such as "Ignore previous instructions", they are clearly marked as content to analyze, not commands. This reduces the risk of prompt injection (Lessons 40 and 175).
  • Tag names such as <email> and <document> let you refer to the data precisely: "in the email above…"

3. Put Long Data Before the Question

When sending a long document, place the document first and the question or instruction after it:

content = f"""<report>
{long_report_text}
</report>

Based on the report above, what were the three biggest risks identified?"""

Placing the question at the end of long inputs tends to produce better answers, and it reads naturally: first the material, then the question about it.


Building User Messages from Real Input

In applications, user messages are built from text typed by people, uploaded files, or database records. That input must be validated before it is sent.

MAX_CHARS = 20_000


def build_user_message(text: str) -> dict:
    """Validate raw user input and return a user message."""
    cleaned = text.strip()

    if not cleaned:
        raise ValueError("Message cannot be empty.")
    if len(cleaned) > MAX_CHARS:
        raise ValueError(f"Message is too long ({len(cleaned)} characters; limit {MAX_CHARS}).")

    return {"role": "user", "content": cleaned}

Why each check exists:

CheckReason
.strip()Removes accidental leading and trailing whitespace and newlines
Empty checkThe API rejects empty text content with a 400 error. Catching it first gives the user a clear message without a wasted request.
Length limitPrevents accidental or abusive huge inputs that increase cost and latency. The right limit depends on your application.

A character limit is a simple first protection. For precise control, count tokens before sending (Lesson 46).


Practical Example: Ask Questions About a Text File

This tool reads a text file, wraps it in tags, and asks Claude a question about it. It combines validation, the instruction/data separation, and the document-first layout.

"""ask_file.py: ask Claude a question about the contents of a text file.

Usage:
    python ask_file.py notes.txt "What decisions were made?"
"""

import sys
from pathlib import Path

import anthropic
from anthropic import Anthropic
from anthropic.types import Message

MAX_CHARS = 200_000

SYSTEM_PROMPT = (
    "You answer questions about documents. Base answers only on the document provided. "
    "If the document does not contain the answer, say so clearly."
)


def get_text(message: Message) -> str:
    return "".join(block.text for block in message.content if block.type == "text")


def build_question_message(filename: str, document: str, question: str) -> dict:
    document = document.strip()
    question = question.strip()

    if not document:
        raise ValueError("The file is empty.")
    if not question:
        raise ValueError("The question is empty.")
    if len(document) > MAX_CHARS:
        raise ValueError(f"The file is too large ({len(document):,} characters).")

    content = f"""<document name="{filename}">
{document}
</document>

Answer this question about the document above:
<question>{question}</question>"""

    return {"role": "user", "content": content}


def main() -> int:
    if len(sys.argv) != 3:
        print('Usage: python ask_file.py <file> "<question>"', file=sys.stderr)
        return 1

    path, question = Path(sys.argv[1]), sys.argv[2]

    try:
        document = path.read_text(encoding="utf-8")
        user_message = build_question_message(path.name, document, question)
    except (OSError, UnicodeDecodeError, ValueError) as error:
        print(f"Error: {error}", file=sys.stderr)
        return 1

    client = Anthropic()
    try:
        reply = client.messages.create(
            model="claude-opus-5-5",
            max_tokens=4096,
            system=SYSTEM_PROMPT,
            messages=[user_message],
        )
    except anthropic.APIError as error:
        print(f"API error: {error}", file=sys.stderr)
        return 1

    print(get_text(reply).strip())
    return 0


if __name__ == "__main__":
    sys.exit(main())

Create a sample file meeting.txt:

Sprint review – 3 Nov
- Payment page redesign approved; Anita leads it.
- Mobile release delayed one week due to a login bug.
- Decision: move weekly sync from Monday to Tuesday.

Run:

python ask_file.py meeting.txt "What decisions were made, and who owns them?"

Example output:

Two decisions were made:
1. The payment page redesign was approved, and Anita leads it.
2. The weekly sync moves from Monday to Tuesday (no owner is named).

The mobile release was also delayed by one week because of a login bug.

How It Works

  1. Reading the file uses encoding="utf-8". Errors such as a missing file (OSError) or a non-text file (UnicodeDecodeError) produce a clear message instead of a traceback.
  2. build_question_message() validates both inputs, then builds the content with the document first, wrapped in <document name="..."> tags, and the question last, in <question> tags. The name attribute tells Claude which file it is reading.
  3. The system prompt tells Claude to rely only on the document and to say when the answer is not there. This reduces the chance of answers based on assumptions rather than the file.
  4. messages=[user_message] sends a single user turn. This is a one-shot question, not a conversation.
  5. anthropic.APIError catches every API-related failure in one place, which is enough for a small tool.

Notice the answer: it reports that no owner is named for one decision instead of inventing one. That is the effect of the grounding instruction in the system prompt.


Common Mistakes

Ending the List with an Assistant Message

What happens: 400 invalid_request_error: the model does not support assistant message prefill.

Fix: make sure the conversation ends with user input. To control output format, use instructions or structured outputs instead of prefill.

Sending Empty Content

What happens: an empty string or whitespace-only input causes a 400 error.

Fix: validate and strip input before sending.

Wrong Role Names or Shapes

{"role": "human", "content": "Hi"}     # Invalid role
{"role": "user", "text": "Hi"}         # Wrong key: must be "content"
messages={"role": "user", "content": "Hi"}   # Must be a list

Fix: messages=[{"role": "user", "content": "Hi"}].

Mixing Instructions and Data Without Delimiters

What happens: Claude may confuse the data with your instructions, especially if the data contains imperative sentences. That is a prompt-injection risk.

Fix: wrap data in clearly named tags and keep your instructions outside them.

Putting Persistent Rules in Every User Message

What happens: repetition, higher token usage, and weaker priority than system instructions.

Fix: move rules that always apply into the system parameter.

Sending Unnecessary Personal or Sensitive Data

What happens: private data is transmitted and processed without need.

Fix: send only the data the task requires. Remove or mask personal identifiers that the task does not need.


Best Practices

  • Validate input: strip it, reject empty messages, and limit size.
  • Be specific about the task, the audience, and the expected output.
  • Separate data from instructions with XML-style tags.
  • Place long documents first and the question last.
  • End every request with user input.
  • Use the block form for images, documents, multi-part content, and caching.
  • Keep rules in the system prompt and the specific request in the user message.
  • Send only the data you need.

Exercise

  1. Rewrite this vague request into a specific one: "Help me with my code." Include a code snippet in <code> tags.
  2. Send a request whose messages list ends with an assistant message. Record the error, then fix the request.
  3. Extend build_user_message() to reject input that consists only of punctuation or symbols, such as "???".
  4. Modify ask_file.py to accept two files and compare them, wrapping each one in its own <document> tag.
Show answers for steps 2–4

Step 2: the API returns 400 invalid_request_error, saying the model does not support assistant message prefill and that the conversation must end with a user message. Fix it by removing the trailing assistant message, or by adding a user message after it.

Step 3:

if not any(character.isalnum() for character in cleaned):
    raise ValueError("Message must contain letters or numbers.")

str.isalnum() is true for letters and digits in any script, so non-English input is still accepted.

Step 4 (core part):

content = f"""<document name="{first.name}">
{first_text}
</document>

<document name="{second.name}">
{second_text}
</document>

Compare the two documents above. List what they agree on and where they differ."""

Both documents come first, each in its own named tag, followed by the instruction. Claude can then refer to each document by name in its answer.

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Send User Messages and get answers drawn from it.

Signed-in readers only.