Guide · Amazon Pinpoint · Project Setup · Endpoint Model · Channel Overview

Amazon Pinpoint for MCP Servers — Project Setup, Endpoint Model, and Channel Overview

MCP server tools that send notifications — alert users when a monitored endpoint goes down, deliver a one-time passcode, or push a weekly digest — need a channel-aware messaging layer. Amazon Pinpoint handles email, SMS, push notifications, and voice from a single SDK client. Its endpoint model (which tracks users across channels under a single address record) fits naturally into MCP tool patterns: a register_notification_target tool writes an endpoint, and a send_alert tool fires a transactional message to it. This guide covers creating a Pinpoint project, IAM setup, the endpoint model, channel types, and when to choose Pinpoint over SES or SNS.

TL;DR

Amazon Pinpoint is a multi-channel messaging service (email, SMS, push, voice) with an endpoint model that ties a user identity to one or more delivery addresses. For MCP servers: (1) create a Pinpoint project (CreateAppCommand), (2) enable the channels you need (UpdateEmailChannelCommand / UpdateSmsChannelCommand), (3) register destinations as endpoints (UpdateEndpointCommand), and (4) send transactional messages with SendMessagesCommand. For high-volume email-only workloads, SES is cheaper and simpler. For pure pub/sub fan-out, SNS is better. Pinpoint is the right choice when you need multi-channel delivery, per-user endpoint tracking, or event analytics attached to message delivery.

When to use Pinpoint vs SES vs SNS

All three services can send email, and the naming overlaps enough that the choice is not obvious. The decision turns on what else you need beyond delivery.

Requirement Best fit Reason
Transactional email only, high volume SES Lower per-message cost; no per-endpoint overhead; direct SMTP + API; simpler IAM surface
Pub/sub fan-out to many subscribers SNS Native topic subscriptions across email, SMS, SQS, Lambda, HTTP; no endpoint management needed
Multi-channel (email + SMS + push) to known users Pinpoint Single endpoint record per user; channel switching without re-registration; event analytics per channel
OTP / verification codes Pinpoint SendOTPMessageCommand manages code generation, delivery, and validation natively
Event-driven journeys (onboarding sequences, re-engagement) Pinpoint Journey builder with branching, wait states, and A/B splits; triggered by endpoint events
Analytics on message open/click/unsubscribe rates Pinpoint Native engagement metrics per campaign and channel; event stream to Kinesis Firehose

For most MCP server notification tools, Pinpoint is the right choice if you need to: track which users have opted in to which channels, send alerts across email and SMS without duplicating address management, or attach analytics to message delivery. If you are only sending email receipts in response to tool calls and never need SMS or push, SES is simpler and cheaper.

Creating a Pinpoint project and enabling channels

Everything in Pinpoint is scoped to an application (also called a project — the console and API use both terms interchangeably). The application has a UUID ApplicationId that is required on every subsequent API call. Applications are regional; create one in the region closest to your users.

import {
  PinpointClient,
  CreateAppCommand,
  UpdateEmailChannelCommand,
  UpdateSmsChannelCommand,
} from '@aws-sdk/client-pinpoint';

const client = new PinpointClient({ region: process.env.AWS_REGION ?? 'us-east-1' });

// Step 1: create the application (idempotent — returns existing app if name matches)
const { ApplicationResponse } = await client.send(
  new CreateAppCommand({ CreateApplicationRequest: { Name: 'mcp-alerts' } })
);
const appId = ApplicationResponse!.Id!;
// Store appId in env or config — it never changes for the lifetime of the project
console.log('Pinpoint application ID:', appId);

// Step 2: enable the email channel
// Requires a verified SES identity (domain or email address) in the same region
await client.send(new UpdateEmailChannelCommand({
  ApplicationId: appId,
  EmailChannelRequest: {
    Enabled: true,
    // The "from" address — must be a SES-verified identity
    FromAddress: 'alerts@example.com',
    // SES identity ARN — not the email address; must be in same account + region
    Identity: `arn:aws:ses:us-east-1:${process.env.AWS_ACCOUNT_ID}:identity/alerts@example.com`,
    // Optional: IAM role that Pinpoint assumes to call SES on your behalf
    // Omit to use Pinpoint's default service role (simpler for getting started)
  },
}));

// Step 3: enable the SMS channel (requires a request to exit sandbox first)
await client.send(new UpdateSmsChannelCommand({
  ApplicationId: appId,
  SMSChannelRequest: {
    Enabled: true,
    // SenderId is the name shown to recipients on supporting carriers (not US)
    // For US, you must use a long code, toll-free, or short code — not a named sender
    SenderId: 'AliveMCP',
    // ShortCode: '12345',  // uncomment if you have a dedicated short code
  },
}));

Region availability: Pinpoint is not available in every AWS region. At time of writing, the supported regions include us-east-1, us-west-2, eu-west-1, eu-central-1, ap-southeast-1, ap-southeast-2, ap-northeast-1, and ca-central-1. The SDK throws UnrecognizedClientException if you target an unsupported region, not a more descriptive error.

SES sandbox: The email channel requires the underlying SES identity to be verified. If your SES account is still in sandbox mode (all new accounts start there), you can only send to verified email addresses. Submit a production-access request via the SES console before going live.

SMS sandbox: Similarly, SMS starts in a sandbox where you can only send to verified destination phone numbers. After testing, submit a production-access request. US SMS also requires requesting an origination number (long code, toll-free, or short code) separately — Pinpoint SMS will silently fail to deliver if no origination number is configured.

IAM permissions

Pinpoint uses the mobiletargeting IAM namespace — not pinpoint. This trips up developers searching the IAM policy reference. The minimum policy for a notification MCP tool that registers endpoints and sends transactional messages:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PinpointSendTransactional",
      "Effect": "Allow",
      "Action": [
        "mobiletargeting:SendMessages",
        "mobiletargeting:SendOTPMessage",
        "mobiletargeting:VerifyOTPMessage"
      ],
      "Resource": "arn:aws:mobiletargeting:us-east-1:ACCOUNT_ID:apps/APP_ID"
    },
    {
      "Sid": "PinpointManageEndpoints",
      "Effect": "Allow",
      "Action": [
        "mobiletargeting:UpdateEndpoint",
        "mobiletargeting:GetEndpoint",
        "mobiletargeting:DeleteEndpoint",
        "mobiletargeting:PutEvents"
      ],
      "Resource": "arn:aws:mobiletargeting:us-east-1:ACCOUNT_ID:apps/APP_ID/endpoints/*"
    }
  ]
}

For reading project-level settings (channel status, quota limits), add mobiletargeting:GetApp and mobiletargeting:GetEmailChannel / mobiletargeting:GetSmsChannel on the app ARN. The app ARN format is arn:aws:mobiletargeting:REGION:ACCOUNT:apps/APP_ID — note the plural apps and the absence of a trailing slash.

If you are creating applications or configuring channels from the MCP server (a setup/admin tool), add mobiletargeting:CreateApp, mobiletargeting:UpdateEmailChannel, and mobiletargeting:UpdateSmsChannelCommand as needed. Grant these only to the setup path, not the hot path that sends messages.

The endpoint model

An endpoint in Pinpoint represents a single delivery destination for a user: one email address, one phone number, or one device push token. A user can have multiple endpoints — one for email, one for SMS, one for each app installation on their devices. Endpoints are the atomic unit that Pinpoint tracks for delivery, analytics, and opt-out management.

Each endpoint is identified by a string ID that you generate (typically a UUID or a hash of the user + channel type). Pinpoint does not enforce uniqueness of the address field — you can register the same email address under multiple endpoint IDs — so your application is responsible for deduplication if that matters.

import { UpdateEndpointCommand, EndpointRequest } from '@aws-sdk/client-pinpoint';

// Register an email endpoint for a user
async function registerEmailEndpoint(
  appId: string,
  userId: string,
  emailAddress: string
): Promise {
  const endpointId = `email-${userId}`;  // deterministic ID for idempotency

  const endpoint: EndpointRequest = {
    // ChannelType is required — must match the channel you will use to send
    ChannelType: 'EMAIL',
    // Address is the actual delivery destination
    Address: emailAddress,
    // OptOut controls whether Pinpoint will deliver to this endpoint
    // 'NONE' = opted in; 'ALL' = opted out of all channels
    OptOut: 'NONE',
    // User sub-object attaches a user ID and attributes to the endpoint
    User: {
      UserId: userId,
      UserAttributes: {
        // String list values only; no nested objects
        Plan: ['team'],
        SignupDate: [new Date().toISOString()],
      },
    },
    // Endpoint-level attributes for segmentation and filtering
    Attributes: {
      NotificationTypes: ['downtime', 'schema-drift'],
      Environment: ['production'],
    },
  };

  await client.send(new UpdateEndpointCommand({
    ApplicationId: appId,
    EndpointId: endpointId,
    EndpointRequest: endpoint,
  }));
}

// Register an SMS endpoint for the same user
async function registerSmsEndpoint(
  appId: string,
  userId: string,
  phoneNumber: string  // E.164 format: +12125551234
): Promise {
  const endpointId = `sms-${userId}`;

  await client.send(new UpdateEndpointCommand({
    ApplicationId: appId,
    EndpointId: endpointId,
    EndpointRequest: {
      ChannelType: 'SMS',
      Address: phoneNumber,
      OptOut: 'NONE',
      User: { UserId: userId },
    },
  }));
}

Endpoint ID determinism: Using a deterministic endpoint ID (like email-{userId}) means UpdateEndpointCommand is idempotent — calling it with the same ID and address is a no-op if nothing changed, or an update if the address or attributes changed. This is safer for MCP tools than generating a random UUID each call, which would create duplicate endpoint records.

OptOut enforcement: When a user unsubscribes from email (clicks the unsubscribe link in a Pinpoint-managed email), Pinpoint automatically sets the endpoint's OptOut to ALL. Any subsequent SendMessagesCommand targeting that endpoint will show DeliveryStatus: OPT_OUT in the response — it will not throw an error, so your MCP tool needs to check the per-endpoint delivery status in the response, not just the HTTP status code.

Channel types and their constraints

Channel type Address format Key constraint Typical MCP tool use
EMAIL RFC 5321 email address Requires SES-verified sender identity; subject to SES sending quotas (not Pinpoint's own) Downtime alerts, weekly digests, password reset flows
SMS E.164 phone number (+12125551234) US requires registered origination number; 160-char limit per segment; sandbox restricts destinations OTP verification, urgent incident alerts, two-factor authentication
GCM Firebase Cloud Messaging device token Requires GCM/FCM server key configured in Pinpoint; tokens expire and must be refreshed Mobile app push notifications for real-time agent events
APNS APNs device token (hex string) Requires APNs certificate or key/team ID configured; separate sandbox vs production APNs environments iOS push notifications for agent events
VOICE E.164 phone number US-only outbound calls; 10 concurrent calls/account by default; uses SSML for speech synthesis Critical incident voice alerts when SMS/email might be missed
IN_APP n/a — no address needed Requires Pinpoint In-App SDK in mobile/web app; messages served when user opens app Onboarding tips, feature announcements in agent-powered apps

MCP server tools most commonly use EMAIL and SMS. The push channels (GCM, APNS) require a mobile app integration that is typically separate from the MCP server layer. VOICE is useful for high-priority alert tools where silent notifications are unacceptable.

Sending a transactional message with SendMessages

SendMessagesCommand is the right API for one-off transactional messages triggered by MCP tool calls — a user requesting a password reset, an alert fired when an endpoint goes down, an OTP code for verification. It delivers immediately (not on a schedule) and does not require a campaign or segment setup.

import { SendMessagesCommand, MessageRequest } from '@aws-sdk/client-pinpoint';

// Send a transactional email to a specific endpoint
async function sendAlertEmail(
  appId: string,
  endpointId: string,
  subject: string,
  htmlBody: string,
  textBody: string
): Promise {
  const request: MessageRequest = {
    Endpoints: {
      // Keys are endpoint IDs; values are per-endpoint overrides
      [endpointId]: {},
    },
    MessageConfiguration: {
      EmailMessage: {
        SimpleEmail: {
          Subject: { Charset: 'UTF-8', Data: subject },
          HtmlPart: { Charset: 'UTF-8', Data: htmlBody },
          TextPart: { Charset: 'UTF-8', Data: textBody },
        },
        // FromAddress overrides the channel-level default if set
        // FromAddress: 'custom@example.com',
      },
    },
  };

  const { MessageResponse } = await client.send(new SendMessagesCommand({
    ApplicationId: appId,
    MessageRequest: request,
  }));

  // Check per-endpoint delivery status — HTTP 200 does NOT mean delivery succeeded
  const result = MessageResponse?.Result?.[endpointId];
  if (!result) throw new Error('No delivery result for endpoint');

  if (result.DeliveryStatus !== 'SUCCESSFUL') {
    // Common statuses: SUCCESSFUL, OPT_OUT, QUIET_TIME, PERMANENT_FAILURE, TEMPORARY_FAILURE
    throw new Error(`Message delivery failed: ${result.DeliveryStatus} — ${result.StatusMessage}`);
  }

  return result.MessageId!;
}

// Send to multiple endpoints at once (up to 100 per call)
async function sendMultiEndpointAlert(
  appId: string,
  endpointIds: string[],
  subject: string,
  body: string
): Promise> {
  if (endpointIds.length > 100) throw new Error('SendMessages supports max 100 endpoints per call');

  const { MessageResponse } = await client.send(new SendMessagesCommand({
    ApplicationId: appId,
    MessageRequest: {
      Endpoints: Object.fromEntries(endpointIds.map(id => [id, {}])),
      MessageConfiguration: {
        EmailMessage: {
          SimpleEmail: {
            Subject: { Charset: 'UTF-8', Data: subject },
            TextPart: { Charset: 'UTF-8', Data: body },
          },
        },
      },
    },
  }));

  const results: Record = {};
  for (const [id, r] of Object.entries(MessageResponse?.Result ?? {})) {
    results[id] = r.DeliveryStatus ?? 'UNKNOWN';
  }
  return results;
}

The 100-endpoint limit per SendMessagesCommand call is a hard service limit. For bulk sends to more than 100 users, batch your endpoint IDs into chunks and call SendMessagesCommand in a loop. If you are sending to thousands of users on a schedule, switch to a Pinpoint Campaign (targeting a saved segment) rather than looping SendMessagesCommand — campaigns are designed for that volume and handle retry/backpressure automatically.

Common errors and their causes

Error / symptom Cause Fix
NotFoundException: Resource not found on SendMessagesCommand The ApplicationId does not exist in the targeted region, or the endpoint ID in Endpoints map is not registered Verify the app ID and region. Endpoint IDs in SendMessagesCommand must be pre-registered via UpdateEndpointCommand — Pinpoint does not auto-create endpoints on message send
DeliveryStatus: PERMANENT_FAILURE with StatusMessage: "No origination number" SMS channel is enabled but no long code, toll-free number, or short code has been assigned to the application Request an origination number in the Pinpoint console under "SMS and voice > Phone numbers" and assign it to the application; this process takes 24-72 hours for toll-free numbers and several weeks for short codes
BadRequestException: Email channel is not enabled UpdateEmailChannelCommand was not called, or was called with Enabled: false Call GetEmailChannelCommand to check status; re-run UpdateEmailChannelCommand with a verified SES identity
HTTP 200 response but email never arrives DeliveryStatus is OPT_OUT, QUIET_TIME, or TEMPORARY_FAILURE — not checked by application code Always read MessageResponse.Result[endpointId].DeliveryStatus; treat anything other than SUCCESSFUL as a failure
UnrecognizedClientException (not a 403 — an endpoint error) Pinpoint is not available in the region configured on the SDK client Change the client region to a supported Pinpoint region; common issue when using the same region config as other AWS services without checking Pinpoint availability
TooManyRequestsException on endpoint updates Pinpoint enforces endpoint update rate limits (default 300 requests/sec per account); bulk MCP tools updating many endpoints in parallel can hit this Throttle concurrent UpdateEndpointCommand calls; use UpdateEndpointsBatchCommand (up to 100 endpoints per call) for bulk registration