Skip to main content
Glama

Create a self-mailer (BILLABLE)

lob_self_mailers_create
DestructiveIdempotent

Commit a self-mailer send by providing inside and outside content, recipient, and sender addresses. Requires a confirmation token from preview in live mode.

Instructions

Commit a self-mailer send. Billable in live mode. Requires a confirmation_token from lob_self_mailers_preview that matches the current payload (live mode only). Sizes: 6x18_bifold (default), 11x9_bifold.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
descriptionNoInternal description (max 255 chars).
toYesRecipient address. Either a saved address ID (`adr_…`) or an inline address.
fromYesSender (return) address. Either a saved address ID (`adr_…`) or an inline address.
send_dateNoISO 8601 timestamp (e.g. '2026-05-01T00:00:00Z') to schedule the send. Must be at most 180 days in the future.
mail_typeNoMail class. Defaults to usps_first_class for most pieces.
merge_variablesNoKey/value pairs substituted into Handlebars-style {{variables}} in your HTML/template content.
metadataNoUp to 20 string key/value pairs to attach to the resource.
billing_group_idNoBilling group ID (`bg_…`) to attribute the charge to.
use_typeNoRequired for some mail classes. 'marketing' for promotional, 'operational' for transactional.
insideYesInside-of-self-mailer content source.
outsideYesOutside-of-self-mailer content source.
sizeNoSelf-mailer size. Defaults to 6x18_bifold.
idempotency_keyNoIdempotency key (max 256 chars). If omitted, the server auto-generates a value derived from the confirmation_token when present, otherwise a fresh UUIDv4. Lob deduplicates identical keys for 24 hours.
extraNoAdditional Lob API parameters not enumerated above. Merged into the request body verbatim. See https://docs.lob.com for the full parameter list per resource.
confirmation_tokenNoToken from lob_self_mailers_preview. Required in live mode (LOB_LIVE_MODE=true).

Implementation Reference

  • The handler for lob_self_mailers_create is `pc.commit` — the commit function returned by buildPreviewCommit. When invoked, it validates the confirmation_token (required in live mode), checks payload hash matches the preview, derives idempotency key, calls beforeDispatch (piece-counter), and finally makes a POST to /self_mailers via callCommit.
    registerTool(server, {
      name: "lob_self_mailers_create",
      annotations: {
        title: "Create a self-mailer (BILLABLE)",
        ...ToolAnnotationPresets.commit,
      },
      description:
        "Commit a self-mailer send. **Billable** in live mode. Requires a `confirmation_token` from " +
        "lob_self_mailers_preview that matches the current payload (live mode only). " +
        "Sizes: 6x18_bifold (default), 11x9_bifold.",
      inputSchema: selfMailerCommitShape,
      handler: pc.commit,
    });
  • The callCommit closure passed by registerSelfMailerTools — makes the actual Lob API POST to /self_mailers with the stripped payload and idempotency key. This is what eventually sends the self-mailer.
        expiresAt: now + ttlMs,
      };
      ctx.tokenStore.put(record);
      return {
        confirmation_token: token,
        expires_at: new Date(record.expiresAt).toISOString(),
        preview: previewResponse,
      };
    },
    
    async commit(input, serverCtx) {
      const inputAny = input as Record<string, unknown>;
      const confirmationToken = inputAny.confirmation_token as string | undefined;
  • Input schema for the tool. selfMailerCreateShape defines the base fields (inside, outside, size, to, from, etc.) and selfMailerCommitShape extends it with an optional confirmation_token.
    const selfMailerCreateShape = {
      ...mailPieceCommonShape,
      inside: contentSourceSchema.describe("Inside-of-self-mailer content source."),
      outside: contentSourceSchema.describe("Outside-of-self-mailer content source."),
      size: SELF_MAILER_SIZE.optional().describe(
        "Self-mailer size. Defaults to 6x18_bifold.",
      ),
      idempotency_key: idempotencyKeyAutoSchema,
      extra: extraParamsSchema,
    } as const;
    
    const selfMailerCommitShape = {
      ...selfMailerCreateShape,
      confirmation_token: z
        .string()
        .optional()
        .describe(
          "Token from lob_self_mailers_preview. Required in live mode (LOB_LIVE_MODE=true).",
        ),
    } as const;
  • The top-level registration entry point. registerAllTools calls registerSelfMailerTools(server, lob, tokenStore, pieceCounter) which registers lob_self_mailers_create along with the other self-mailer tools.
    export function registerAllTools(
      server: McpServer,
      lob: LobClient,
      tokenStore: TokenStore,
      pieceCounter: PieceCounter,
    ): void {
      registerAddressBookTools(server, lob);
      registerVerificationTools(server, lob);
      registerPostcardTools(server, lob, tokenStore, pieceCounter);
      registerLetterTools(server, lob, tokenStore, pieceCounter);
      registerSelfMailerTools(server, lob, tokenStore, pieceCounter);
  • The registerTool utility that registers the tool with the MCP server, wrapping the handler with error handling and JSON serialization.
    export function registerTool<TShape extends ZodRawShape>(
      server: McpServer,
      def: ToolDefinition<TShape>,
    ): void {
      const a = def.annotations ?? {};
      server.registerTool(
        def.name,
        {
          title: a.title ?? def.name,
          description: def.description,
          inputSchema: def.inputSchema,
          annotations: {
            ...a,
            // Lob is always external; default the hint accordingly.
            openWorldHint: a.openWorldHint ?? true,
          },
        },
        // The SDK's ToolCallback type is parameterised over the exact ZodRawShape and
        // resists the generic erasure here. The runtime contract (validated args in,
        // CallToolResult out) is correct, so we bridge the type boundary with `as never`.
        (async (args: unknown, serverCtx: unknown): Promise<CallToolResult> => {
          try {
            const result = await def.handler(args as never, serverCtx);
            return { content: [{ type: "text", text: stringifyResult(result) }] };
          } catch (err) {
            return {
              isError: true,
              content: [{ type: "text", text: formatErrorForTool(err) }],
            };
          }
        }) as never,
      );
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover mutability and idempotency. Description adds billing behavior (live mode only) and token requirement. Does not contradict annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loads critical info (commit, billable, token). No wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex tool with many params and no output schema, description covers core purpose, billing, and token requirement. Schema descriptions fill remaining gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema covers 100% of parameters with descriptions. Description adds minimal extra, e.g., size defaults. Baseline 3 appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States specific action: 'Commit a self-mailer send.' Distinct from preview, cancel, get, list. Includes billing context and size options.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Clearly indicates prerequisite: requires confirmation_token from preview in live mode. Implicitly tells when to use (after preview) and when not (no token). No explicit alternatives but sibling preview exists.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/optimize-overseas/lob-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server