From 0d30a9cabb73251ef84a204e284cf0f6c2758a72 Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Fri, 6 Feb 2026 22:14:37 +1000 Subject: [PATCH] feat(tools): add @tpmjs/tools-postmark with 82 Postmark API tools Full Postmark email API coverage: send emails, manage templates, bounces, domains, webhooks, message streams, stats, suppressions, inbound rules, sender signatures, and data removals. Dual auth with server token (60 tools) and account token (22 tools). --- packages/tools/official/blocks.yml | 1723 ++++++++++++ packages/tools/official/postmark/block.ts | 214 ++ packages/tools/official/postmark/index.ts | 6 + packages/tools/official/postmark/package.json | 383 +++ packages/tools/official/postmark/src/index.ts | 2451 +++++++++++++++++ .../tools/official/postmark/tsconfig.json | 11 + .../tools/official/postmark/tsup.config.ts | 10 + pnpm-lock.yaml | 16 + 8 files changed, 4814 insertions(+) create mode 100644 packages/tools/official/postmark/block.ts create mode 100644 packages/tools/official/postmark/index.ts create mode 100644 packages/tools/official/postmark/package.json create mode 100644 packages/tools/official/postmark/src/index.ts create mode 100644 packages/tools/official/postmark/tsconfig.json create mode 100644 packages/tools/official/postmark/tsup.config.ts diff --git a/packages/tools/official/blocks.yml b/packages/tools/official/blocks.yml index 22fd8d8..380cbf5 100644 --- a/packages/tools/official/blocks.yml +++ b/packages/tools/official/blocks.yml @@ -498,6 +498,53 @@ domain: fields: [id, name, keyHint, createdAt, lastUsedAt] description: "An HLLM API key" + # ------------------------------------------------------------------------- + # Postmark email entities + # ------------------------------------------------------------------------- + postmark_email_result: + fields: [To, SubmittedAt, MessageID, ErrorCode, Message] + description: "Result of sending an email via Postmark" + + postmark_bounce: + fields: [ID, Type, TypeCode, Name, Tag, MessageID, ServerID, Description, Details, Email, From, BouncedAt, DumpAvailable, Inactive, CanActivate, Subject, Content] + description: "A Postmark bounce record" + + postmark_template: + fields: [TemplateId, Name, Subject, HtmlBody, TextBody, Alias, TemplateType, LayoutTemplate, Active] + description: "A Postmark email template" + + postmark_server: + fields: [ID, Name, Color, SmtpApiActivated, RawEmailEnabled, TrackOpens, TrackLinks, InboundDomain] + description: "A Postmark server configuration" + + postmark_message_stream: + fields: [ID, ServerID, Name, Description, MessageStreamType, CreatedAt, UpdatedAt, ArchivedAt] + description: "A Postmark message stream" + + postmark_outbound_message: + fields: [Tag, MessageID, To, Cc, Bcc, Recipients, ReceivedAt, From, Subject, Attachments, Status, MessageEvents] + description: "An outbound message in Postmark" + + postmark_domain: + fields: [ID, Name, SPFVerified, DKIMVerified, ReturnPathDomainVerified, ReturnPathDomain] + description: "A Postmark sending domain" + + postmark_sender_signature: + fields: [ID, Domain, EmailAddress, Name, ReplyToEmailAddress, Confirmed] + description: "A Postmark sender signature" + + postmark_webhook: + fields: [ID, Url, MessageStream, HttpAuth, HttpHeaders, Triggers] + description: "A Postmark webhook configuration" + + postmark_suppression: + fields: [EmailAddress, SuppressionReason, Origin, CreatedAt] + description: "A suppressed email address in Postmark" + + postmark_stats: + fields: [Sent, Bounced, SMTPApiErrors, BounceRate, SpamComplaints, SpamComplaintsRate, Opens, UniqueOpens, Clicks, UniqueClicks, TotalClicks] + description: "Postmark outbound statistics" + # ------------------------------------------------------------------------- # Agent & workflow entities # ------------------------------------------------------------------------- @@ -8218,6 +8265,1682 @@ blocks: description: "List of packages that failed to install" measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + # =========================================================================== + # POSTMARK — Email API tools (82 tools) + # =========================================================================== + + # --- Email Sending --- + + ops.sendEmail: + type: utility + description: "Send a single transactional email via Postmark." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /email endpoint" + inputs: + - name: From + type: string + description: "Sender email address" + - name: To + type: string + description: "Recipient email address(es)" + - name: Subject + type: string + optional: true + description: "Email subject line" + - name: HtmlBody + type: string + optional: true + description: "HTML body" + - name: TextBody + type: string + optional: true + description: "Plain text body" + outputs: + - name: To + type: string + description: "Recipient address" + - name: MessageID + type: string + description: "Postmark message ID" + - name: SubmittedAt + type: string + description: "Submission timestamp" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.sendBatchEmails: + type: utility + description: "Send a batch of up to 500 emails in a single API call." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /email/batch endpoint" + inputs: + - name: Messages + type: array + description: "Array of email messages (max 500)" + outputs: + - name: results + type: postmark_email_result[] + description: "Array of send results" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.sendEmailWithTemplate: + type: utility + description: "Send an email using a Postmark template." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /email/withTemplate endpoint" + inputs: + - name: TemplateId + type: number + optional: true + description: "Numeric template ID" + - name: TemplateAlias + type: string + optional: true + description: "Template alias string" + - name: TemplateModel + type: object + description: "Template variable values" + - name: From + type: string + description: "Sender email address" + - name: To + type: string + description: "Recipient email address" + outputs: + - name: MessageID + type: string + description: "Postmark message ID" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.sendBatchWithTemplates: + type: utility + description: "Send a batch of templated emails in a single API call (max 500)." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /email/batchWithTemplates endpoint" + inputs: + - name: Messages + type: array + description: "Array of templated email messages" + outputs: + - name: results + type: postmark_email_result[] + description: "Array of send results" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + # --- Bulk Email --- + + ops.sendBulkEmail: + type: utility + description: "Submit a bulk email job for large-scale sending." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /email/bulk endpoint" + inputs: + - name: From + type: string + description: "Sender email address" + - name: To + type: string + description: "Recipient email address(es)" + outputs: + - name: JobId + type: string + description: "Bulk job ID" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getBulkEmailStatus: + type: utility + description: "Get the status of a bulk email job." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /email/bulk/{id} endpoint" + inputs: + - name: id + type: string + description: "Bulk email job ID" + outputs: + - name: status + type: object + description: "Bulk job status" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + # --- Bounces --- + + ops.getDeliveryStats: + type: utility + description: "Get delivery statistics including bounce counts by type." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /deliverystats endpoint" + inputs: [] + outputs: + - name: InactiveMails + type: number + description: "Count of inactive emails" + - name: Bounces + type: array + description: "Bounce counts by type" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.searchBounces: + type: utility + description: "Search bounces with optional filters like type, date range, and email." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /bounces endpoint" + inputs: + - name: count + type: number + description: "Number of bounces to return" + - name: offset + type: number + description: "Number of bounces to skip" + - name: type + type: string + optional: true + description: "Bounce type filter" + outputs: + - name: TotalCount + type: number + description: "Total matching bounces" + - name: Bounces + type: postmark_bounce[] + description: "Array of bounce records" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getBounce: + type: utility + description: "Get details of a specific bounce by ID." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /bounces/{id} endpoint" + inputs: + - name: id + type: number + description: "Bounce ID" + outputs: + - name: bounce + type: postmark_bounce + description: "Bounce details" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getBounceDump: + type: utility + description: "Get the raw SMTP dump for a specific bounce." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /bounces/{id}/dump endpoint" + inputs: + - name: id + type: number + description: "Bounce ID" + outputs: + - name: Body + type: string + description: "Raw SMTP dump content" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.activateBounce: + type: utility + description: "Activate a bounced email address to allow sending again." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API PUT /bounces/{id}/activate endpoint" + inputs: + - name: id + type: number + description: "Bounce ID to activate" + outputs: + - name: Message + type: string + description: "Activation result message" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + # --- Templates --- + + ops.listTemplates: + type: utility + description: "List email templates with optional filtering by type." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /templates endpoint" + inputs: + - name: Count + type: number + description: "Number of templates to return" + - name: Offset + type: number + description: "Number of templates to skip" + outputs: + - name: TotalCount + type: number + description: "Total templates" + - name: Templates + type: postmark_template[] + description: "Array of templates" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getTemplate: + type: utility + description: "Get details of a specific template by ID or alias." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /templates/{id} endpoint" + inputs: + - name: templateIdOrAlias + type: string + description: "Template ID or alias" + outputs: + - name: template + type: postmark_template + description: "Template details" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.createTemplate: + type: utility + description: "Create a new email template." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /templates endpoint" + inputs: + - name: Name + type: string + description: "Template name" + - name: Subject + type: string + optional: true + description: "Default subject line" + - name: HtmlBody + type: string + optional: true + description: "HTML body" + - name: TextBody + type: string + optional: true + description: "Text body" + outputs: + - name: TemplateId + type: number + description: "New template ID" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.updateTemplate: + type: utility + description: "Update an existing email template." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API PUT /templates/{id} endpoint" + inputs: + - name: templateIdOrAlias + type: string + description: "Template ID or alias" + - name: Name + type: string + optional: true + description: "Template name" + outputs: + - name: TemplateId + type: number + description: "Updated template ID" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.deleteTemplate: + type: utility + description: "Delete an email template." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API DELETE /templates/{id} endpoint" + inputs: + - name: templateIdOrAlias + type: string + description: "Template ID or alias to delete" + outputs: + - name: ErrorCode + type: number + description: "Error code (0 = success)" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.validateTemplate: + type: utility + description: "Validate template content and test render with a model." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /templates/validate endpoint" + inputs: + - name: Subject + type: string + optional: true + description: "Subject line to validate" + - name: HtmlBody + type: string + optional: true + description: "HTML body to validate" + outputs: + - name: AllContentIsValid + type: boolean + description: "Whether all content is valid" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.pushTemplates: + type: utility + description: "Push templates from one server to another. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API PUT /templates/push endpoint with account token" + inputs: + - name: SourceServerID + type: number + description: "Source server ID" + - name: DestinationServerID + type: number + description: "Destination server ID" + - name: PerformChanges + type: boolean + description: "Apply changes or dry-run" + outputs: + - name: TotalCount + type: number + description: "Total templates compared" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + # --- Server Config --- + + ops.getServer: + type: utility + description: "Get the current server configuration." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /server endpoint" + inputs: [] + outputs: + - name: server + type: postmark_server + description: "Server configuration" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.updateServer: + type: utility + description: "Update the current server configuration." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API PUT /server endpoint" + inputs: + - name: Name + type: string + optional: true + description: "Server name" + - name: TrackOpens + type: boolean + optional: true + description: "Enable open tracking" + outputs: + - name: server + type: postmark_server + description: "Updated server configuration" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + # --- Servers Management --- + + ops.listServers: + type: utility + description: "List all servers in the account. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /servers endpoint with account token" + inputs: + - name: count + type: number + description: "Number of servers to return" + - name: offset + type: number + description: "Number of servers to skip" + outputs: + - name: TotalCount + type: number + description: "Total servers" + - name: Servers + type: postmark_server[] + description: "Array of servers" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getServerById: + type: utility + description: "Get a specific server by ID. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /servers/{id} endpoint with account token" + inputs: + - name: id + type: number + description: "Server ID" + outputs: + - name: server + type: postmark_server + description: "Server details" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.createServer: + type: utility + description: "Create a new server in the account. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /servers endpoint with account token" + inputs: + - name: Name + type: string + description: "Server name" + outputs: + - name: server + type: postmark_server + description: "Created server" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.updateServerById: + type: utility + description: "Update a specific server by ID. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API PUT /servers/{id} endpoint with account token" + inputs: + - name: id + type: number + description: "Server ID" + - name: Name + type: string + optional: true + description: "Server name" + outputs: + - name: server + type: postmark_server + description: "Updated server" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.deleteServer: + type: utility + description: "Delete a server from the account. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API DELETE /servers/{id} endpoint with account token" + inputs: + - name: id + type: number + description: "Server ID to delete" + outputs: + - name: result + type: object + description: "Deletion result" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + # --- Message Streams --- + + ops.listMessageStreams: + type: utility + description: "List all message streams for the server." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /message-streams endpoint" + inputs: + - name: MessageStreamType + type: string + optional: true + description: "Filter by stream type" + outputs: + - name: MessageStreams + type: postmark_message_stream[] + description: "Array of message streams" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getMessageStream: + type: utility + description: "Get details of a specific message stream." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /message-streams/{id} endpoint" + inputs: + - name: id + type: string + description: "Message stream ID" + outputs: + - name: stream + type: postmark_message_stream + description: "Message stream details" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.createMessageStream: + type: utility + description: "Create a new message stream." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /message-streams endpoint" + inputs: + - name: ID + type: string + description: "Unique stream ID" + - name: Name + type: string + description: "Display name" + - name: MessageStreamType + type: string + description: "Stream type (Transactional or Broadcasts)" + outputs: + - name: stream + type: postmark_message_stream + description: "Created message stream" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.updateMessageStream: + type: utility + description: "Update a message stream's name or description." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API PATCH /message-streams/{id} endpoint" + inputs: + - name: id + type: string + description: "Message stream ID" + - name: Name + type: string + optional: true + description: "New display name" + outputs: + - name: stream + type: postmark_message_stream + description: "Updated message stream" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.archiveMessageStream: + type: utility + description: "Archive a message stream." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /message-streams/{id}/archive endpoint" + inputs: + - name: id + type: string + description: "Message stream ID to archive" + outputs: + - name: result + type: object + description: "Archive result" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.unarchiveMessageStream: + type: utility + description: "Unarchive a previously archived message stream." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /message-streams/{id}/unarchive endpoint" + inputs: + - name: id + type: string + description: "Message stream ID to unarchive" + outputs: + - name: result + type: object + description: "Unarchive result" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + # --- Messages — Outbound --- + + ops.searchOutboundMessages: + type: utility + description: "Search outbound messages with optional filters." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /messages/outbound endpoint" + inputs: + - name: count + type: number + description: "Number of messages to return" + - name: offset + type: number + description: "Number of messages to skip" + outputs: + - name: TotalCount + type: number + description: "Total matching messages" + - name: Messages + type: postmark_outbound_message[] + description: "Array of messages" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getOutboundMessageDetails: + type: utility + description: "Get full details of a specific outbound message." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /messages/outbound/{id}/details endpoint" + inputs: + - name: id + type: string + description: "Message ID" + outputs: + - name: message + type: postmark_outbound_message + description: "Message details" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getOutboundMessageDump: + type: utility + description: "Get the raw SMTP dump of an outbound message." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /messages/outbound/{id}/dump endpoint" + inputs: + - name: id + type: string + description: "Message ID" + outputs: + - name: Body + type: string + description: "Raw SMTP dump" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getOutboundMessageOpens: + type: utility + description: "Get open events for a specific outbound message." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /messages/outbound/opens/{id} endpoint" + inputs: + - name: id + type: string + description: "Message ID" + outputs: + - name: Opens + type: array + description: "Array of open events" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getOutboundMessageClicks: + type: utility + description: "Get click events for a specific outbound message." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /messages/outbound/clicks/{id} endpoint" + inputs: + - name: id + type: string + description: "Message ID" + outputs: + - name: Clicks + type: array + description: "Array of click events" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + # --- Messages — Inbound --- + + ops.searchInboundMessages: + type: utility + description: "Search inbound messages with optional filters." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /messages/inbound endpoint" + inputs: + - name: count + type: number + description: "Number of messages to return" + - name: offset + type: number + description: "Number of messages to skip" + outputs: + - name: TotalCount + type: number + description: "Total matching messages" + - name: InboundMessages + type: array + description: "Array of inbound messages" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getInboundMessageDetails: + type: utility + description: "Get full details of a specific inbound message." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /messages/inbound/{id}/details endpoint" + inputs: + - name: id + type: string + description: "Inbound message ID" + outputs: + - name: message + type: object + description: "Inbound message details" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.bypassInboundRules: + type: utility + description: "Bypass inbound rules for a specific message, reprocessing it." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API PUT /messages/inbound/{id}/bypass endpoint" + inputs: + - name: id + type: string + description: "Inbound message ID" + outputs: + - name: result + type: object + description: "Bypass result" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.retryInboundMessage: + type: utility + description: "Retry processing of an inbound message." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API PUT /messages/inbound/{id}/retry endpoint" + inputs: + - name: id + type: string + description: "Inbound message ID" + outputs: + - name: result + type: object + description: "Retry result" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + # --- Messages — Search Opens/Clicks --- + + ops.searchMessageOpens: + type: utility + description: "Search message open events across all outbound messages." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /messages/outbound/opens endpoint" + inputs: + - name: count + type: number + description: "Number of results" + - name: offset + type: number + description: "Number to skip" + outputs: + - name: Opens + type: array + description: "Array of open events" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.searchMessageClicks: + type: utility + description: "Search message click events across all outbound messages." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /messages/outbound/clicks endpoint" + inputs: + - name: count + type: number + description: "Number of results" + - name: offset + type: number + description: "Number to skip" + outputs: + - name: Clicks + type: array + description: "Array of click events" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + # --- Stats --- + + ops.getStatsOverview: + type: utility + description: "Get an overview of outbound email statistics." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /stats/outbound endpoint" + inputs: + - name: tag + type: string + optional: true + description: "Filter by tag" + - name: fromdate + type: string + optional: true + description: "Start date" + - name: todate + type: string + optional: true + description: "End date" + outputs: + - name: stats + type: postmark_stats + description: "Statistics overview" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getStatsSends: + type: utility + description: "Get send count statistics over time." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /stats/outbound/sends endpoint" + inputs: + - name: tag + type: string + optional: true + - name: fromdate + type: string + optional: true + - name: todate + type: string + optional: true + outputs: + - name: Days + type: array + description: "Daily send counts" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getStatsBounces: + type: utility + description: "Get bounce statistics over time." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /stats/outbound/bounces endpoint" + inputs: + - name: tag + type: string + optional: true + - name: fromdate + type: string + optional: true + - name: todate + type: string + optional: true + outputs: + - name: Days + type: array + description: "Daily bounce counts" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getStatsSpamComplaints: + type: utility + description: "Get spam complaint statistics over time." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /stats/outbound/spam endpoint" + inputs: + - name: tag + type: string + optional: true + - name: fromdate + type: string + optional: true + - name: todate + type: string + optional: true + outputs: + - name: Days + type: array + description: "Daily spam complaint counts" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getStatsTracked: + type: utility + description: "Get tracked email statistics over time." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /stats/outbound/tracked endpoint" + inputs: + - name: tag + type: string + optional: true + - name: fromdate + type: string + optional: true + - name: todate + type: string + optional: true + outputs: + - name: Days + type: array + description: "Daily tracked counts" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getStatsOpens: + type: utility + description: "Get email open statistics over time." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /stats/outbound/opens endpoint" + inputs: + - name: tag + type: string + optional: true + - name: fromdate + type: string + optional: true + - name: todate + type: string + optional: true + outputs: + - name: Days + type: array + description: "Daily open counts" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getStatsOpensByPlatform: + type: utility + description: "Get email open statistics grouped by platform." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /stats/outbound/opens/platforms endpoint" + inputs: + - name: tag + type: string + optional: true + - name: fromdate + type: string + optional: true + - name: todate + type: string + optional: true + outputs: + - name: Days + type: array + description: "Platform-grouped open counts" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getStatsOpensByClient: + type: utility + description: "Get email open statistics grouped by email client." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /stats/outbound/opens/emailclients endpoint" + inputs: + - name: tag + type: string + optional: true + - name: fromdate + type: string + optional: true + - name: todate + type: string + optional: true + outputs: + - name: Days + type: array + description: "Client-grouped open counts" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getStatsClicks: + type: utility + description: "Get link click statistics over time." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /stats/outbound/clicks endpoint" + inputs: + - name: tag + type: string + optional: true + - name: fromdate + type: string + optional: true + - name: todate + type: string + optional: true + outputs: + - name: Days + type: array + description: "Daily click counts" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getStatsClicksByBrowser: + type: utility + description: "Get link click statistics grouped by browser family." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /stats/outbound/clicks/browserfamilies endpoint" + inputs: + - name: tag + type: string + optional: true + - name: fromdate + type: string + optional: true + - name: todate + type: string + optional: true + outputs: + - name: Days + type: array + description: "Browser-grouped click counts" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getStatsClicksByPlatform: + type: utility + description: "Get link click statistics grouped by platform." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /stats/outbound/clicks/platforms endpoint" + inputs: + - name: tag + type: string + optional: true + - name: fromdate + type: string + optional: true + - name: todate + type: string + optional: true + outputs: + - name: Days + type: array + description: "Platform-grouped click counts" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getStatsClicksByLocation: + type: utility + description: "Get link click statistics grouped by geographic location." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /stats/outbound/clicks/location endpoint" + inputs: + - name: tag + type: string + optional: true + - name: fromdate + type: string + optional: true + - name: todate + type: string + optional: true + outputs: + - name: Days + type: array + description: "Location-grouped click counts" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + # --- Domains --- + + ops.listDomains: + type: utility + description: "List all domains in the account. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /domains endpoint with account token" + inputs: + - name: count + type: number + description: "Number of domains to return" + - name: offset + type: number + description: "Number of domains to skip" + outputs: + - name: TotalCount + type: number + description: "Total domains" + - name: Domains + type: postmark_domain[] + description: "Array of domains" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getDomain: + type: utility + description: "Get details of a specific domain. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /domains/{id} endpoint with account token" + inputs: + - name: id + type: number + description: "Domain ID" + outputs: + - name: domain + type: postmark_domain + description: "Domain details" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.createDomain: + type: utility + description: "Create a new sending domain. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /domains endpoint with account token" + inputs: + - name: Name + type: string + description: "Domain name" + outputs: + - name: domain + type: postmark_domain + description: "Created domain" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.updateDomain: + type: utility + description: "Update a domain's return-path. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API PUT /domains/{id} endpoint with account token" + inputs: + - name: id + type: number + description: "Domain ID" + - name: ReturnPathDomain + type: string + optional: true + description: "New return-path domain" + outputs: + - name: domain + type: postmark_domain + description: "Updated domain" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.deleteDomain: + type: utility + description: "Delete a domain from the account. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API DELETE /domains/{id} endpoint with account token" + inputs: + - name: id + type: number + description: "Domain ID to delete" + outputs: + - name: result + type: object + description: "Deletion result" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.verifyDomainDkim: + type: utility + description: "Trigger DKIM verification for a domain. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API PUT /domains/{id}/verifyDkim endpoint" + inputs: + - name: id + type: number + description: "Domain ID" + outputs: + - name: domain + type: postmark_domain + description: "Domain with updated DKIM status" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.verifyDomainReturnPath: + type: utility + description: "Trigger return-path verification for a domain. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API PUT /domains/{id}/verifyReturnPath endpoint" + inputs: + - name: id + type: number + description: "Domain ID" + outputs: + - name: domain + type: postmark_domain + description: "Domain with updated return-path status" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.verifyDomainSpf: + type: utility + description: "Trigger SPF verification for a domain. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /domains/{id}/verifyspf endpoint" + inputs: + - name: id + type: number + description: "Domain ID" + outputs: + - name: domain + type: postmark_domain + description: "Domain with updated SPF status" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.rotateDomainDkim: + type: utility + description: "Rotate DKIM keys for a domain. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /domains/{id}/rotatedkim endpoint" + inputs: + - name: id + type: number + description: "Domain ID" + outputs: + - name: result + type: object + description: "DKIM rotation result" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + # --- Sender Signatures --- + + ops.listSenderSignatures: + type: utility + description: "List all sender signatures in the account. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /senders endpoint with account token" + inputs: + - name: count + type: number + description: "Number of signatures to return" + - name: offset + type: number + description: "Number of signatures to skip" + outputs: + - name: TotalCount + type: number + description: "Total signatures" + - name: SenderSignatures + type: postmark_sender_signature[] + description: "Array of sender signatures" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getSenderSignature: + type: utility + description: "Get details of a specific sender signature. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /senders/{id} endpoint with account token" + inputs: + - name: id + type: number + description: "Sender signature ID" + outputs: + - name: signature + type: postmark_sender_signature + description: "Sender signature details" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.createSenderSignature: + type: utility + description: "Create a new sender signature. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /senders endpoint with account token" + inputs: + - name: FromEmail + type: string + description: "Sender email address" + - name: Name + type: string + description: "Sender display name" + outputs: + - name: signature + type: postmark_sender_signature + description: "Created sender signature" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.updateSenderSignature: + type: utility + description: "Update an existing sender signature. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API PUT /senders/{id} endpoint with account token" + inputs: + - name: id + type: number + description: "Sender signature ID" + - name: Name + type: string + optional: true + description: "Sender display name" + outputs: + - name: signature + type: postmark_sender_signature + description: "Updated sender signature" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.deleteSenderSignature: + type: utility + description: "Delete a sender signature. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API DELETE /senders/{id} endpoint with account token" + inputs: + - name: id + type: number + description: "Sender signature ID to delete" + outputs: + - name: result + type: object + description: "Deletion result" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.resendSenderConfirmation: + type: utility + description: "Resend the confirmation email for a sender signature. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /senders/{id}/resend endpoint with account token" + inputs: + - name: id + type: number + description: "Sender signature ID" + outputs: + - name: result + type: object + description: "Resend result" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + # --- Webhooks --- + + ops.listWebhooks: + type: utility + description: "List all webhooks for the server." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /webhooks endpoint" + inputs: + - name: MessageStream + type: string + optional: true + description: "Filter by message stream" + outputs: + - name: Webhooks + type: postmark_webhook[] + description: "Array of webhooks" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getWebhook: + type: utility + description: "Get details of a specific webhook." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /webhooks/{id} endpoint" + inputs: + - name: id + type: number + description: "Webhook ID" + outputs: + - name: webhook + type: postmark_webhook + description: "Webhook details" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.createWebhook: + type: utility + description: "Create a new webhook." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /webhooks endpoint" + inputs: + - name: Url + type: string + description: "Webhook endpoint URL" + - name: Triggers + type: object + optional: true + description: "Event triggers to enable" + outputs: + - name: webhook + type: postmark_webhook + description: "Created webhook" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.updateWebhook: + type: utility + description: "Update an existing webhook." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API PUT /webhooks/{id} endpoint" + inputs: + - name: id + type: number + description: "Webhook ID" + - name: Url + type: string + optional: true + description: "Webhook endpoint URL" + outputs: + - name: webhook + type: postmark_webhook + description: "Updated webhook" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.deleteWebhook: + type: utility + description: "Delete a webhook." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API DELETE /webhooks/{id} endpoint" + inputs: + - name: id + type: number + description: "Webhook ID to delete" + outputs: + - name: result + type: object + description: "Deletion result" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + # --- Suppressions --- + + ops.listSuppressions: + type: utility + description: "List suppressed email addresses for a message stream." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /message-streams/{streamId}/suppressions/dump endpoint" + inputs: + - name: streamId + type: string + description: "Message stream ID" + outputs: + - name: Suppressions + type: postmark_suppression[] + description: "Array of suppressed addresses" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.createSuppressions: + type: utility + description: "Add email addresses to the suppression list for a message stream." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /message-streams/{streamId}/suppressions endpoint" + inputs: + - name: streamId + type: string + description: "Message stream ID" + - name: Suppressions + type: array + description: "Email addresses to suppress" + outputs: + - name: Suppressions + type: array + description: "Results of suppression operations" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.deleteSuppressions: + type: utility + description: "Remove email addresses from the suppression list." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /message-streams/{streamId}/suppressions/delete endpoint" + inputs: + - name: streamId + type: string + description: "Message stream ID" + - name: Suppressions + type: array + description: "Email addresses to unsuppress" + outputs: + - name: Suppressions + type: array + description: "Results of unsuppression operations" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + # --- Inbound Rules --- + + ops.listInboundRules: + type: utility + description: "List all inbound processing rules." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /triggers/inboundrules endpoint" + inputs: + - name: count + type: number + description: "Number of rules to return" + - name: offset + type: number + description: "Number of rules to skip" + outputs: + - name: TotalCount + type: number + description: "Total rules" + - name: InboundRules + type: array + description: "Array of inbound rules" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.createInboundRule: + type: utility + description: "Create a new inbound processing rule to block emails matching a pattern." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /triggers/inboundrules endpoint" + inputs: + - name: Rule + type: string + description: "Email address or domain pattern to block" + outputs: + - name: ID + type: number + description: "Created rule ID" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.deleteInboundRule: + type: utility + description: "Delete an inbound processing rule." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API DELETE /triggers/inboundrules/{id} endpoint" + inputs: + - name: id + type: number + description: "Inbound rule ID to delete" + outputs: + - name: result + type: object + description: "Deletion result" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + # --- Data Removals --- + + ops.createDataRemoval: + type: utility + description: "Request removal of personal data associated with an email address. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API POST /data-removals endpoint with account token" + inputs: + - name: RequestedBy + type: string + description: "Email of the person requesting removal" + - name: RequestedFor + type: string + description: "Email address whose data should be removed" + outputs: + - name: ID + type: number + description: "Data removal request ID" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + + ops.getDataRemovalStatus: + type: utility + description: "Get the status of a data removal request. Uses account token." + path: "postmark" + domain_rules: + - id: api_integration + description: "Must call Postmark API GET /data-removals/{id} endpoint with account token" + inputs: + - name: id + type: number + description: "Data removal request ID" + outputs: + - name: Status + type: string + description: "Current status of the removal request" + measures: [working_implementation, valid_output_structure, proper_error_handling, ai_sdk_compliance] + # ============================================================================= # VALIDATORS - Which validators to run against each block # ============================================================================= diff --git a/packages/tools/official/postmark/block.ts b/packages/tools/official/postmark/block.ts new file mode 100644 index 0000000..cfa492a --- /dev/null +++ b/packages/tools/official/postmark/block.ts @@ -0,0 +1,214 @@ +/** + * Block metadata for Postmark tools + * This file provides metadata for the blocks validator + */ +import { + activateBounce, + archiveMessageStream, + bypassInboundRules, + // Data Removals + createDataRemoval, + createDomain, + createInboundRule, + createMessageStream, + createSenderSignature, + createServer, + createSuppressions, + createTemplate, + createWebhook, + deleteDomain, + deleteInboundRule, + deleteSenderSignature, + deleteServer, + deleteSuppressions, + deleteTemplate, + deleteWebhook, + getBounce, + getBounceDump, + getBulkEmailStatus, + getDataRemovalStatus, + // Bounces + getDeliveryStats, + getDomain, + getInboundMessageDetails, + getMessageStream, + getOutboundMessageClicks, + getOutboundMessageDetails, + getOutboundMessageDump, + getOutboundMessageOpens, + getSenderSignature, + // Server Config + getServer, + getServerById, + getStatsBounces, + getStatsClicks, + getStatsClicksByBrowser, + getStatsClicksByLocation, + getStatsClicksByPlatform, + getStatsOpens, + getStatsOpensByClient, + getStatsOpensByPlatform, + // Stats + getStatsOverview, + getStatsSends, + getStatsSpamComplaints, + getStatsTracked, + getTemplate, + getWebhook, + // Domains + listDomains, + // Inbound Rules + listInboundRules, + // Message Streams + listMessageStreams, + // Sender Signatures + listSenderSignatures, + // Servers Management + listServers, + // Suppressions + listSuppressions, + // Templates + listTemplates, + // Webhooks + listWebhooks, + pushTemplates, + resendSenderConfirmation, + retryInboundMessage, + rotateDomainDkim, + searchBounces, + // Messages — Inbound + searchInboundMessages, + searchMessageClicks, + // Messages — Search Opens/Clicks + searchMessageOpens, + // Messages — Outbound + searchOutboundMessages, + sendBatchEmails, + sendBatchWithTemplates, + // Bulk Email + sendBulkEmail, + // Email Sending + sendEmail, + sendEmailWithTemplate, + unarchiveMessageStream, + updateDomain, + updateMessageStream, + updateSenderSignature, + updateServer, + updateServerById, + updateTemplate, + updateWebhook, + validateTemplate, + verifyDomainDkim, + verifyDomainReturnPath, + verifyDomainSpf, +} from './src/index.js'; + +export const block = { + name: 'postmark', + description: + 'Postmark email API tools for AI agents. Send emails, manage templates, bounces, domains, webhooks, and more.', + tools: { + // Email Sending + sendEmail, + sendBatchEmails, + sendEmailWithTemplate, + sendBatchWithTemplates, + // Bulk Email + sendBulkEmail, + getBulkEmailStatus, + // Bounces + getDeliveryStats, + searchBounces, + getBounce, + getBounceDump, + activateBounce, + // Templates + listTemplates, + getTemplate, + createTemplate, + updateTemplate, + deleteTemplate, + validateTemplate, + pushTemplates, + // Server Config + getServer, + updateServer, + // Servers Management + listServers, + getServerById, + createServer, + updateServerById, + deleteServer, + // Message Streams + listMessageStreams, + getMessageStream, + createMessageStream, + updateMessageStream, + archiveMessageStream, + unarchiveMessageStream, + // Messages — Outbound + searchOutboundMessages, + getOutboundMessageDetails, + getOutboundMessageDump, + getOutboundMessageOpens, + getOutboundMessageClicks, + // Messages — Inbound + searchInboundMessages, + getInboundMessageDetails, + bypassInboundRules, + retryInboundMessage, + // Messages — Search Opens/Clicks + searchMessageOpens, + searchMessageClicks, + // Stats + getStatsOverview, + getStatsSends, + getStatsBounces, + getStatsSpamComplaints, + getStatsTracked, + getStatsOpens, + getStatsOpensByPlatform, + getStatsOpensByClient, + getStatsClicks, + getStatsClicksByBrowser, + getStatsClicksByPlatform, + getStatsClicksByLocation, + // Domains + listDomains, + getDomain, + createDomain, + updateDomain, + deleteDomain, + verifyDomainDkim, + verifyDomainReturnPath, + verifyDomainSpf, + rotateDomainDkim, + // Sender Signatures + listSenderSignatures, + getSenderSignature, + createSenderSignature, + updateSenderSignature, + deleteSenderSignature, + resendSenderConfirmation, + // Webhooks + listWebhooks, + getWebhook, + createWebhook, + updateWebhook, + deleteWebhook, + // Suppressions + listSuppressions, + createSuppressions, + deleteSuppressions, + // Inbound Rules + listInboundRules, + createInboundRule, + deleteInboundRule, + // Data Removals + createDataRemoval, + getDataRemovalStatus, + }, +}; + +export default block; diff --git a/packages/tools/official/postmark/index.ts b/packages/tools/official/postmark/index.ts new file mode 100644 index 0000000..6cf4b94 --- /dev/null +++ b/packages/tools/official/postmark/index.ts @@ -0,0 +1,6 @@ +/** + * Postmark Email API Tools for TPMJS + * Re-export all tools from src/index.ts + */ +export * from './src/index.js'; +export { default } from './src/index.js'; diff --git a/packages/tools/official/postmark/package.json b/packages/tools/official/postmark/package.json new file mode 100644 index 0000000..d55ea6e --- /dev/null +++ b/packages/tools/official/postmark/package.json @@ -0,0 +1,383 @@ +{ + "name": "@tpmjs/tools-postmark", + "version": "0.1.0", + "description": "Postmark email API tools for AI agents. Send emails, manage templates, bounces, domains, webhooks, and more.", + "type": "module", + "keywords": [ + "tpmjs", + "postmark", + "email", + "transactional", + "smtp", + "agent" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "type-check": "tsc --noEmit", + "clean": "rm -rf dist .turbo" + }, + "devDependencies": { + "@tpmjs/tsconfig": "workspace:*", + "tsup": "^8.5.1", + "typescript": "^5.9.3" + }, + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/tpmjs/tpmjs.git", + "directory": "packages/tools/official/postmark" + }, + "homepage": "https://tpmjs.com", + "license": "MIT", + "tpmjs": { + "category": "ops", + "frameworks": [ + "vercel-ai" + ], + "tools": [ + { + "name": "sendEmail", + "description": "Send a single transactional email via Postmark." + }, + { + "name": "sendBatchEmails", + "description": "Send a batch of up to 500 emails in a single API call." + }, + { + "name": "sendEmailWithTemplate", + "description": "Send an email using a Postmark template." + }, + { + "name": "sendBatchWithTemplates", + "description": "Send a batch of templated emails in a single API call." + }, + { + "name": "sendBulkEmail", + "description": "Submit a bulk email job for large-scale sending." + }, + { + "name": "getBulkEmailStatus", + "description": "Get the status of a bulk email job." + }, + { + "name": "getDeliveryStats", + "description": "Get delivery statistics including bounce counts by type." + }, + { + "name": "searchBounces", + "description": "Search bounces with optional filters." + }, + { + "name": "getBounce", + "description": "Get details of a specific bounce by ID." + }, + { + "name": "getBounceDump", + "description": "Get the raw SMTP dump for a specific bounce." + }, + { + "name": "activateBounce", + "description": "Activate a bounced email address to allow sending again." + }, + { + "name": "listTemplates", + "description": "List email templates with optional filtering." + }, + { + "name": "getTemplate", + "description": "Get details of a specific template." + }, + { + "name": "createTemplate", + "description": "Create a new email template." + }, + { + "name": "updateTemplate", + "description": "Update an existing email template." + }, + { + "name": "deleteTemplate", + "description": "Delete an email template." + }, + { + "name": "validateTemplate", + "description": "Validate template content and model." + }, + { + "name": "pushTemplates", + "description": "Push templates from one server to another." + }, + { + "name": "getServer", + "description": "Get the current server configuration." + }, + { + "name": "updateServer", + "description": "Update the current server configuration." + }, + { + "name": "listServers", + "description": "List all servers in the account." + }, + { + "name": "getServerById", + "description": "Get a specific server by ID." + }, + { + "name": "createServer", + "description": "Create a new server in the account." + }, + { + "name": "updateServerById", + "description": "Update a specific server by ID." + }, + { + "name": "deleteServer", + "description": "Delete a server from the account." + }, + { + "name": "listMessageStreams", + "description": "List all message streams for the server." + }, + { + "name": "getMessageStream", + "description": "Get details of a specific message stream." + }, + { + "name": "createMessageStream", + "description": "Create a new message stream." + }, + { + "name": "updateMessageStream", + "description": "Update a message stream's properties." + }, + { + "name": "archiveMessageStream", + "description": "Archive a message stream." + }, + { + "name": "unarchiveMessageStream", + "description": "Unarchive a previously archived message stream." + }, + { + "name": "searchOutboundMessages", + "description": "Search outbound messages with optional filters." + }, + { + "name": "getOutboundMessageDetails", + "description": "Get full details of a specific outbound message." + }, + { + "name": "getOutboundMessageDump", + "description": "Get the raw SMTP dump of an outbound message." + }, + { + "name": "getOutboundMessageOpens", + "description": "Get open events for a specific outbound message." + }, + { + "name": "getOutboundMessageClicks", + "description": "Get click events for a specific outbound message." + }, + { + "name": "searchInboundMessages", + "description": "Search inbound messages with optional filters." + }, + { + "name": "getInboundMessageDetails", + "description": "Get full details of a specific inbound message." + }, + { + "name": "bypassInboundRules", + "description": "Bypass inbound rules for a specific message." + }, + { + "name": "retryInboundMessage", + "description": "Retry processing of an inbound message." + }, + { + "name": "searchMessageOpens", + "description": "Search message open events across all messages." + }, + { + "name": "searchMessageClicks", + "description": "Search message click events across all messages." + }, + { + "name": "getStatsOverview", + "description": "Get an overview of outbound email statistics." + }, + { + "name": "getStatsSends", + "description": "Get send count statistics over time." + }, + { + "name": "getStatsBounces", + "description": "Get bounce statistics over time." + }, + { + "name": "getStatsSpamComplaints", + "description": "Get spam complaint statistics over time." + }, + { + "name": "getStatsTracked", + "description": "Get tracked email statistics over time." + }, + { + "name": "getStatsOpens", + "description": "Get email open statistics over time." + }, + { + "name": "getStatsOpensByPlatform", + "description": "Get email open statistics grouped by platform." + }, + { + "name": "getStatsOpensByClient", + "description": "Get email open statistics grouped by email client." + }, + { + "name": "getStatsClicks", + "description": "Get link click statistics over time." + }, + { + "name": "getStatsClicksByBrowser", + "description": "Get link click statistics grouped by browser." + }, + { + "name": "getStatsClicksByPlatform", + "description": "Get link click statistics grouped by platform." + }, + { + "name": "getStatsClicksByLocation", + "description": "Get link click statistics grouped by geographic location." + }, + { + "name": "listDomains", + "description": "List all domains in the account." + }, + { + "name": "getDomain", + "description": "Get details of a specific domain." + }, + { + "name": "createDomain", + "description": "Create a new sending domain." + }, + { + "name": "updateDomain", + "description": "Update a domain's return-path." + }, + { + "name": "deleteDomain", + "description": "Delete a domain from the account." + }, + { + "name": "verifyDomainDkim", + "description": "Trigger DKIM verification for a domain." + }, + { + "name": "verifyDomainReturnPath", + "description": "Trigger return-path verification for a domain." + }, + { + "name": "verifyDomainSpf", + "description": "Trigger SPF verification for a domain." + }, + { + "name": "rotateDomainDkim", + "description": "Rotate DKIM keys for a domain." + }, + { + "name": "listSenderSignatures", + "description": "List all sender signatures in the account." + }, + { + "name": "getSenderSignature", + "description": "Get details of a specific sender signature." + }, + { + "name": "createSenderSignature", + "description": "Create a new sender signature." + }, + { + "name": "updateSenderSignature", + "description": "Update an existing sender signature." + }, + { + "name": "deleteSenderSignature", + "description": "Delete a sender signature." + }, + { + "name": "resendSenderConfirmation", + "description": "Resend the confirmation email for a sender signature." + }, + { + "name": "listWebhooks", + "description": "List all webhooks for the server." + }, + { + "name": "getWebhook", + "description": "Get details of a specific webhook." + }, + { + "name": "createWebhook", + "description": "Create a new webhook." + }, + { + "name": "updateWebhook", + "description": "Update an existing webhook." + }, + { + "name": "deleteWebhook", + "description": "Delete a webhook." + }, + { + "name": "listSuppressions", + "description": "List suppressed email addresses for a message stream." + }, + { + "name": "createSuppressions", + "description": "Add email addresses to the suppression list." + }, + { + "name": "deleteSuppressions", + "description": "Remove email addresses from the suppression list." + }, + { + "name": "listInboundRules", + "description": "List all inbound processing rules." + }, + { + "name": "createInboundRule", + "description": "Create a new inbound processing rule." + }, + { + "name": "deleteInboundRule", + "description": "Delete an inbound processing rule." + }, + { + "name": "createDataRemoval", + "description": "Request removal of personal data." + }, + { + "name": "getDataRemovalStatus", + "description": "Get the status of a data removal request." + } + ] + }, + "dependencies": { + "ai": "6.0.49" + } +} diff --git a/packages/tools/official/postmark/src/index.ts b/packages/tools/official/postmark/src/index.ts new file mode 100644 index 0000000..c68ad35 --- /dev/null +++ b/packages/tools/official/postmark/src/index.ts @@ -0,0 +1,2451 @@ +/** + * Postmark Email API Tools for TPMJS + * Full access to the Postmark transactional email API: send emails, manage templates, + * bounces, domains, webhooks, message streams, analytics, and more. + * + * @requires POSTMARK_SERVER_TOKEN environment variable (server-level operations) + * @requires POSTMARK_ACCOUNT_TOKEN environment variable (account-level operations: servers, domains, senders, data removals, template push) + */ + +import { jsonSchema, tool } from 'ai'; + +const BASE_URL = 'https://api.postmarkapp.com'; + +/** + * Get the server-level API token + */ +function getServerToken(): string { + const token = process.env.POSTMARK_SERVER_TOKEN; + if (!token) { + throw new Error( + 'POSTMARK_SERVER_TOKEN environment variable is required. Get your server token from https://account.postmarkapp.com/servers' + ); + } + return token; +} + +/** + * Get the account-level API token + */ +function getAccountToken(): string { + const token = process.env.POSTMARK_ACCOUNT_TOKEN; + if (!token) { + throw new Error( + 'POSTMARK_ACCOUNT_TOKEN environment variable is required. Get your account token from https://account.postmarkapp.com/account/edit' + ); + } + return token; +} + +/** + * Make an authenticated request to the Postmark API using the server token + */ +async function apiRequest( + method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', + path: string, + body?: unknown +): Promise { + const token = getServerToken(); + + const headers: Record = { + Accept: 'application/json', + 'X-Postmark-Server-Token': token, + }; + + if (body !== undefined) { + headers['Content-Type'] = 'application/json'; + } + + const response = await fetch(`${BASE_URL}${path}`, { + method, + headers, + body: body !== undefined ? JSON.stringify(body) : undefined, + }); + + if (!response.ok) { + const errorText = await response.text().catch(() => 'Unknown error'); + handleApiError(response.status, errorText); + } + + return response.json() as Promise; +} + +/** + * Make an authenticated request to the Postmark API using the account token + */ +async function accountApiRequest( + method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', + path: string, + body?: unknown +): Promise { + const token = getAccountToken(); + + const headers: Record = { + Accept: 'application/json', + 'X-Postmark-Account-Token': token, + }; + + if (body !== undefined) { + headers['Content-Type'] = 'application/json'; + } + + const response = await fetch(`${BASE_URL}${path}`, { + method, + headers, + body: body !== undefined ? JSON.stringify(body) : undefined, + }); + + if (!response.ok) { + const errorText = await response.text().catch(() => 'Unknown error'); + handleApiError(response.status, errorText); + } + + return response.json() as Promise; +} + +/** + * Handle API errors with specific messages for common HTTP status codes + */ +function handleApiError(status: number, errorText: string): never { + switch (status) { + case 400: + throw new Error(`Bad request: ${errorText}`); + case 401: + throw new Error( + 'Authentication failed: Invalid Postmark token. Check POSTMARK_SERVER_TOKEN or POSTMARK_ACCOUNT_TOKEN.' + ); + case 403: + throw new Error(`Access forbidden: ${errorText}`); + case 404: + throw new Error(`Resource not found: ${errorText}`); + case 422: + throw new Error(`Validation error: ${errorText}`); + case 429: + throw new Error(`Rate limit exceeded: ${errorText}`); + case 500: + case 502: + case 503: + throw new Error(`Postmark service error (${status}): ${errorText}`); + default: + throw new Error(`Postmark API error: HTTP ${status} - ${errorText}`); + } +} + +// ============================================================================ +// Email Sending +// ============================================================================ + +export interface SendEmailInput { + From: string; + To: string; + Cc?: string; + Bcc?: string; + Subject?: string; + Tag?: string; + HtmlBody?: string; + TextBody?: string; + ReplyTo?: string; + Metadata?: Record; + Headers?: Array<{ Name: string; Value: string }>; + Attachments?: Array<{ Name: string; Content: string; ContentType: string; ContentID?: string }>; + TrackOpens?: boolean; + TrackLinks?: 'None' | 'HtmlAndText' | 'HtmlOnly' | 'TextOnly'; + MessageStream?: string; +} + +export interface SendEmailResult { + To: string; + SubmittedAt: string; + MessageID: string; + ErrorCode: number; + Message: string; +} + +export const sendEmail = tool({ + description: + 'Send a single transactional email via Postmark. Requires From, To, and either HtmlBody, TextBody, or both.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + From: { type: 'string', description: 'Sender email address.' }, + To: { type: 'string', description: 'Recipient email address(es), comma-separated.' }, + Cc: { type: 'string', description: 'CC recipients, comma-separated.' }, + Bcc: { type: 'string', description: 'BCC recipients, comma-separated.' }, + Subject: { type: 'string', description: 'Email subject line.' }, + Tag: { type: 'string', description: 'Tag for categorizing the email.' }, + HtmlBody: { type: 'string', description: 'HTML body of the email.' }, + TextBody: { type: 'string', description: 'Plain text body of the email.' }, + ReplyTo: { type: 'string', description: 'Reply-to email address.' }, + Metadata: { + type: 'object', + description: 'Key-value metadata pairs.', + additionalProperties: { type: 'string' }, + }, + Headers: { + type: 'array', + description: 'Custom email headers.', + items: { + type: 'object', + properties: { + Name: { type: 'string' }, + Value: { type: 'string' }, + }, + required: ['Name', 'Value'], + }, + }, + Attachments: { + type: 'array', + description: 'File attachments (base64-encoded content).', + items: { + type: 'object', + properties: { + Name: { type: 'string', description: 'Filename.' }, + Content: { type: 'string', description: 'Base64-encoded file content.' }, + ContentType: { type: 'string', description: 'MIME type.' }, + ContentID: { type: 'string', description: 'Content ID for inline images.' }, + }, + required: ['Name', 'Content', 'ContentType'], + }, + }, + TrackOpens: { type: 'boolean', description: 'Enable open tracking.' }, + TrackLinks: { + type: 'string', + enum: ['None', 'HtmlAndText', 'HtmlOnly', 'TextOnly'], + description: 'Link tracking mode.', + }, + MessageStream: { type: 'string', description: 'Message stream ID. Default: outbound.' }, + }, + required: ['From', 'To'], + additionalProperties: false, + }), + async execute(input: SendEmailInput): Promise { + return apiRequest('POST', '/email', input); + }, +}); + +export interface SendBatchEmailsInput { + Messages: SendEmailInput[]; +} + +export const sendBatchEmails = tool({ + description: 'Send a batch of up to 500 emails in a single API call.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + Messages: { + type: 'array', + description: 'Array of email messages (max 500).', + items: { + type: 'object', + properties: { + From: { type: 'string' }, + To: { type: 'string' }, + Cc: { type: 'string' }, + Bcc: { type: 'string' }, + Subject: { type: 'string' }, + Tag: { type: 'string' }, + HtmlBody: { type: 'string' }, + TextBody: { type: 'string' }, + ReplyTo: { type: 'string' }, + TrackOpens: { type: 'boolean' }, + TrackLinks: { type: 'string' }, + MessageStream: { type: 'string' }, + }, + required: ['From', 'To'], + }, + }, + }, + required: ['Messages'], + additionalProperties: false, + }), + async execute(input: SendBatchEmailsInput): Promise { + return apiRequest('POST', '/email/batch', input.Messages); + }, +}); + +export interface SendEmailWithTemplateInput { + TemplateId?: number; + TemplateAlias?: string; + TemplateModel: Record; + From: string; + To: string; + Cc?: string; + Bcc?: string; + Tag?: string; + ReplyTo?: string; + Metadata?: Record; + Headers?: Array<{ Name: string; Value: string }>; + Attachments?: Array<{ Name: string; Content: string; ContentType: string; ContentID?: string }>; + TrackOpens?: boolean; + TrackLinks?: 'None' | 'HtmlAndText' | 'HtmlOnly' | 'TextOnly'; + MessageStream?: string; + InlineCss?: boolean; +} + +export const sendEmailWithTemplate = tool({ + description: + 'Send an email using a Postmark template. Provide either TemplateId or TemplateAlias.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + TemplateId: { type: 'number', description: 'Numeric template ID.' }, + TemplateAlias: { type: 'string', description: 'Template alias string.' }, + TemplateModel: { + type: 'object', + description: 'Template variable values.', + additionalProperties: true, + }, + From: { type: 'string', description: 'Sender email address.' }, + To: { type: 'string', description: 'Recipient email address(es).' }, + Cc: { type: 'string', description: 'CC recipients.' }, + Bcc: { type: 'string', description: 'BCC recipients.' }, + Tag: { type: 'string', description: 'Tag for categorizing.' }, + ReplyTo: { type: 'string', description: 'Reply-to address.' }, + Metadata: { + type: 'object', + description: 'Metadata key-value pairs.', + additionalProperties: { type: 'string' }, + }, + Headers: { + type: 'array', + items: { + type: 'object', + properties: { Name: { type: 'string' }, Value: { type: 'string' } }, + required: ['Name', 'Value'], + }, + }, + Attachments: { + type: 'array', + items: { + type: 'object', + properties: { + Name: { type: 'string' }, + Content: { type: 'string' }, + ContentType: { type: 'string' }, + ContentID: { type: 'string' }, + }, + required: ['Name', 'Content', 'ContentType'], + }, + }, + TrackOpens: { type: 'boolean' }, + TrackLinks: { type: 'string', enum: ['None', 'HtmlAndText', 'HtmlOnly', 'TextOnly'] }, + MessageStream: { type: 'string' }, + InlineCss: { type: 'boolean', description: 'Inline CSS in the HTML body.' }, + }, + required: ['TemplateModel', 'From', 'To'], + additionalProperties: false, + }), + async execute(input: SendEmailWithTemplateInput): Promise { + return apiRequest('POST', '/email/withTemplate', input); + }, +}); + +export interface SendBatchWithTemplatesInput { + Messages: SendEmailWithTemplateInput[]; +} + +export const sendBatchWithTemplates = tool({ + description: 'Send a batch of templated emails in a single API call (max 500).', + inputSchema: jsonSchema({ + type: 'object', + properties: { + Messages: { + type: 'array', + description: 'Array of templated email messages.', + items: { + type: 'object', + properties: { + TemplateId: { type: 'number' }, + TemplateAlias: { type: 'string' }, + TemplateModel: { type: 'object', additionalProperties: true }, + From: { type: 'string' }, + To: { type: 'string' }, + Cc: { type: 'string' }, + Bcc: { type: 'string' }, + Tag: { type: 'string' }, + ReplyTo: { type: 'string' }, + TrackOpens: { type: 'boolean' }, + TrackLinks: { type: 'string' }, + MessageStream: { type: 'string' }, + }, + required: ['TemplateModel', 'From', 'To'], + }, + }, + }, + required: ['Messages'], + additionalProperties: false, + }), + async execute(input: SendBatchWithTemplatesInput): Promise { + return apiRequest('POST', '/email/batchWithTemplates', { + Messages: input.Messages, + }); + }, +}); + +// ============================================================================ +// Bulk Email +// ============================================================================ + +export interface SendBulkEmailInput { + From: string; + To: string; + Cc?: string; + Bcc?: string; + Subject?: string; + Tag?: string; + HtmlBody?: string; + TextBody?: string; + ReplyTo?: string; + TrackOpens?: boolean; + TrackLinks?: 'None' | 'HtmlAndText' | 'HtmlOnly' | 'TextOnly'; + MessageStream?: string; +} + +export interface BulkEmailJobResult { + JobId: string; + SubmittedAt: string; + ErrorCode: number; + Message: string; +} + +export const sendBulkEmail = tool({ + description: 'Submit a bulk email job for large-scale sending.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + From: { type: 'string', description: 'Sender email address.' }, + To: { type: 'string', description: 'Recipient email address(es).' }, + Cc: { type: 'string' }, + Bcc: { type: 'string' }, + Subject: { type: 'string' }, + Tag: { type: 'string' }, + HtmlBody: { type: 'string' }, + TextBody: { type: 'string' }, + ReplyTo: { type: 'string' }, + TrackOpens: { type: 'boolean' }, + TrackLinks: { type: 'string', enum: ['None', 'HtmlAndText', 'HtmlOnly', 'TextOnly'] }, + MessageStream: { type: 'string' }, + }, + required: ['From', 'To'], + additionalProperties: false, + }), + async execute(input: SendBulkEmailInput): Promise { + return apiRequest('POST', '/email/bulk', input); + }, +}); + +export const getBulkEmailStatus = tool({ + description: 'Get the status of a bulk email job.', + inputSchema: jsonSchema<{ id: string }>({ + type: 'object', + properties: { + id: { type: 'string', description: 'Bulk email job ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: string }): Promise { + return apiRequest('GET', `/email/bulk/${encodeURIComponent(input.id)}`); + }, +}); + +// ============================================================================ +// Bounces +// ============================================================================ + +export const getDeliveryStats = tool({ + description: 'Get delivery statistics including bounce counts by type.', + inputSchema: jsonSchema>({ + type: 'object', + properties: {}, + additionalProperties: false, + }), + async execute(): Promise { + return apiRequest('GET', '/deliverystats'); + }, +}); + +export interface SearchBouncesInput { + count: number; + offset: number; + type?: string; + inactive?: boolean; + emailFilter?: string; + tag?: string; + messageID?: string; + fromdate?: string; + todate?: string; + messagestream?: string; +} + +export const searchBounces = tool({ + description: 'Search bounces with optional filters like type, date range, and email.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + count: { type: 'number', description: 'Number of bounces to return (max 500).' }, + offset: { type: 'number', description: 'Number of bounces to skip.' }, + type: { + type: 'string', + description: 'Bounce type filter (e.g., HardBounce, SoftBounce, SpamNotification).', + }, + inactive: { type: 'boolean', description: 'Filter by inactive status.' }, + emailFilter: { type: 'string', description: 'Filter by email address.' }, + tag: { type: 'string', description: 'Filter by tag.' }, + messageID: { type: 'string', description: 'Filter by message ID.' }, + fromdate: { type: 'string', description: 'Start date (YYYY-MM-DD).' }, + todate: { type: 'string', description: 'End date (YYYY-MM-DD).' }, + messagestream: { type: 'string', description: 'Message stream ID.' }, + }, + required: ['count', 'offset'], + additionalProperties: false, + }), + async execute(input: SearchBouncesInput): Promise { + const params = new URLSearchParams(); + params.set('count', input.count.toString()); + params.set('offset', input.offset.toString()); + if (input.type) params.set('type', input.type); + if (input.inactive !== undefined) params.set('inactive', input.inactive.toString()); + if (input.emailFilter) params.set('emailFilter', input.emailFilter); + if (input.tag) params.set('tag', input.tag); + if (input.messageID) params.set('messageID', input.messageID); + if (input.fromdate) params.set('fromdate', input.fromdate); + if (input.todate) params.set('todate', input.todate); + if (input.messagestream) params.set('messagestream', input.messagestream); + return apiRequest('GET', `/bounces?${params.toString()}`); + }, +}); + +export const getBounce = tool({ + description: 'Get details of a specific bounce by ID.', + inputSchema: jsonSchema<{ id: number }>({ + type: 'object', + properties: { + id: { type: 'number', description: 'Bounce ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: number }): Promise { + return apiRequest('GET', `/bounces/${encodeURIComponent(input.id)}`); + }, +}); + +export const getBounceDump = tool({ + description: 'Get the raw SMTP dump for a specific bounce.', + inputSchema: jsonSchema<{ id: number }>({ + type: 'object', + properties: { + id: { type: 'number', description: 'Bounce ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: number }): Promise { + return apiRequest('GET', `/bounces/${encodeURIComponent(input.id)}/dump`); + }, +}); + +export const activateBounce = tool({ + description: 'Activate a bounced email address to allow sending again.', + inputSchema: jsonSchema<{ id: number }>({ + type: 'object', + properties: { + id: { type: 'number', description: 'Bounce ID to activate.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: number }): Promise { + return apiRequest('PUT', `/bounces/${encodeURIComponent(input.id)}/activate`); + }, +}); + +// ============================================================================ +// Templates +// ============================================================================ + +export interface ListTemplatesInput { + Count: number; + Offset: number; + TemplateType?: 'Standard' | 'Layout'; + LayoutTemplate?: string; +} + +export const listTemplates = tool({ + description: 'List email templates with optional filtering by type.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + Count: { type: 'number', description: 'Number of templates to return.' }, + Offset: { type: 'number', description: 'Number of templates to skip.' }, + TemplateType: { + type: 'string', + enum: ['Standard', 'Layout'], + description: 'Filter by template type.', + }, + LayoutTemplate: { type: 'string', description: 'Filter by layout template alias.' }, + }, + required: ['Count', 'Offset'], + additionalProperties: false, + }), + async execute(input: ListTemplatesInput): Promise { + const params = new URLSearchParams(); + params.set('Count', input.Count.toString()); + params.set('Offset', input.Offset.toString()); + if (input.TemplateType) params.set('TemplateType', input.TemplateType); + if (input.LayoutTemplate) params.set('LayoutTemplate', input.LayoutTemplate); + return apiRequest('GET', `/templates?${params.toString()}`); + }, +}); + +export const getTemplate = tool({ + description: 'Get details of a specific template by ID or alias.', + inputSchema: jsonSchema<{ templateIdOrAlias: string }>({ + type: 'object', + properties: { + templateIdOrAlias: { type: 'string', description: 'Template ID (numeric) or alias.' }, + }, + required: ['templateIdOrAlias'], + additionalProperties: false, + }), + async execute(input: { templateIdOrAlias: string }): Promise { + return apiRequest('GET', `/templates/${encodeURIComponent(input.templateIdOrAlias)}`); + }, +}); + +export interface CreateTemplateInput { + Name: string; + Subject?: string; + HtmlBody?: string; + TextBody?: string; + Alias?: string; + TemplateType?: 'Standard' | 'Layout'; + LayoutTemplate?: string; +} + +export const createTemplate = tool({ + description: 'Create a new email template.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + Name: { type: 'string', description: 'Template name.' }, + Subject: { type: 'string', description: 'Default subject line (supports Mustachio).' }, + HtmlBody: { type: 'string', description: 'HTML body (supports Mustachio).' }, + TextBody: { type: 'string', description: 'Text body (supports Mustachio).' }, + Alias: { type: 'string', description: 'Optional alias for easy reference.' }, + TemplateType: { type: 'string', enum: ['Standard', 'Layout'], description: 'Template type.' }, + LayoutTemplate: { type: 'string', description: 'Layout template alias to use.' }, + }, + required: ['Name'], + additionalProperties: false, + }), + async execute(input: CreateTemplateInput): Promise { + return apiRequest('POST', '/templates', input); + }, +}); + +export interface UpdateTemplateInput { + templateIdOrAlias: string; + Name?: string; + Subject?: string; + HtmlBody?: string; + TextBody?: string; + Alias?: string; + LayoutTemplate?: string; +} + +export const updateTemplate = tool({ + description: 'Update an existing email template.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + templateIdOrAlias: { type: 'string', description: 'Template ID or alias.' }, + Name: { type: 'string', description: 'Template name.' }, + Subject: { type: 'string', description: 'Subject line.' }, + HtmlBody: { type: 'string', description: 'HTML body.' }, + TextBody: { type: 'string', description: 'Text body.' }, + Alias: { type: 'string', description: 'Template alias.' }, + LayoutTemplate: { type: 'string', description: 'Layout template alias.' }, + }, + required: ['templateIdOrAlias'], + additionalProperties: false, + }), + async execute(input: UpdateTemplateInput): Promise { + const { templateIdOrAlias, ...body } = input; + return apiRequest('PUT', `/templates/${encodeURIComponent(templateIdOrAlias)}`, body); + }, +}); + +export const deleteTemplate = tool({ + description: 'Delete an email template.', + inputSchema: jsonSchema<{ templateIdOrAlias: string }>({ + type: 'object', + properties: { + templateIdOrAlias: { type: 'string', description: 'Template ID or alias to delete.' }, + }, + required: ['templateIdOrAlias'], + additionalProperties: false, + }), + async execute(input: { templateIdOrAlias: string }): Promise { + return apiRequest( + 'DELETE', + `/templates/${encodeURIComponent(input.templateIdOrAlias)}` + ); + }, +}); + +export interface ValidateTemplateInput { + Subject?: string; + HtmlBody?: string; + TextBody?: string; + TestRenderModel?: Record; + InlineCssForHtmlTestRender?: boolean; + TemplateType?: 'Standard' | 'Layout'; + LayoutTemplate?: string; +} + +export const validateTemplate = tool({ + description: 'Validate template content and test render with a model.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + Subject: { type: 'string', description: 'Subject line to validate.' }, + HtmlBody: { type: 'string', description: 'HTML body to validate.' }, + TextBody: { type: 'string', description: 'Text body to validate.' }, + TestRenderModel: { + type: 'object', + description: 'Model data for test rendering.', + additionalProperties: true, + }, + InlineCssForHtmlTestRender: { type: 'boolean', description: 'Inline CSS in test render.' }, + TemplateType: { type: 'string', enum: ['Standard', 'Layout'] }, + LayoutTemplate: { type: 'string' }, + }, + additionalProperties: false, + }), + async execute(input: ValidateTemplateInput): Promise { + return apiRequest('POST', '/templates/validate', input); + }, +}); + +export interface PushTemplatesInput { + SourceServerID: number; + DestinationServerID: number; + PerformChanges: boolean; +} + +export const pushTemplates = tool({ + description: 'Push templates from one server to another. Uses account token.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + SourceServerID: { type: 'number', description: 'Source server ID.' }, + DestinationServerID: { type: 'number', description: 'Destination server ID.' }, + PerformChanges: { + type: 'boolean', + description: 'Set to true to apply changes, false for dry-run preview.', + }, + }, + required: ['SourceServerID', 'DestinationServerID', 'PerformChanges'], + additionalProperties: false, + }), + async execute(input: PushTemplatesInput): Promise { + return accountApiRequest('PUT', '/templates/push', input); + }, +}); + +// ============================================================================ +// Server Configuration (current server) +// ============================================================================ + +export const getServer = tool({ + description: 'Get the current server configuration.', + inputSchema: jsonSchema>({ + type: 'object', + properties: {}, + additionalProperties: false, + }), + async execute(): Promise { + return apiRequest('GET', '/server'); + }, +}); + +export interface UpdateServerInput { + Name?: string; + Color?: string; + SmtpApiActivated?: boolean; + RawEmailEnabled?: boolean; + InboundHookUrl?: string; + BounceHookUrl?: string; + OpenHookUrl?: string; + PostFirstOpenOnly?: boolean; + TrackOpens?: boolean; + TrackLinks?: 'None' | 'HtmlAndText' | 'HtmlOnly' | 'TextOnly'; + InboundDomain?: string; + InboundSpamThreshold?: number; + ClickHookUrl?: string; + DeliveryHookUrl?: string; +} + +export const updateServer = tool({ + description: 'Update the current server configuration.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + Name: { type: 'string', description: 'Server name.' }, + Color: { + type: 'string', + description: + 'Server color (e.g., purple, blue, turquoise, green, red, yellow, grey, orange).', + }, + SmtpApiActivated: { type: 'boolean', description: 'Enable SMTP API.' }, + RawEmailEnabled: { type: 'boolean', description: 'Enable raw email access.' }, + InboundHookUrl: { type: 'string', description: 'Inbound webhook URL.' }, + BounceHookUrl: { type: 'string', description: 'Bounce webhook URL.' }, + OpenHookUrl: { type: 'string', description: 'Open tracking webhook URL.' }, + PostFirstOpenOnly: { type: 'boolean', description: 'Only post first open.' }, + TrackOpens: { type: 'boolean', description: 'Enable open tracking.' }, + TrackLinks: { type: 'string', enum: ['None', 'HtmlAndText', 'HtmlOnly', 'TextOnly'] }, + InboundDomain: { type: 'string', description: 'Inbound processing domain.' }, + InboundSpamThreshold: { type: 'number', description: 'Spam threshold (0-25).' }, + ClickHookUrl: { type: 'string', description: 'Click tracking webhook URL.' }, + DeliveryHookUrl: { type: 'string', description: 'Delivery webhook URL.' }, + }, + additionalProperties: false, + }), + async execute(input: UpdateServerInput): Promise { + return apiRequest('PUT', '/server', input); + }, +}); + +// ============================================================================ +// Servers Management (account token) +// ============================================================================ + +export interface ListServersInput { + count: number; + offset: number; + name?: string; +} + +export const listServers = tool({ + description: 'List all servers in the account. Uses account token.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + count: { type: 'number', description: 'Number of servers to return.' }, + offset: { type: 'number', description: 'Number of servers to skip.' }, + name: { type: 'string', description: 'Filter by server name.' }, + }, + required: ['count', 'offset'], + additionalProperties: false, + }), + async execute(input: ListServersInput): Promise { + const params = new URLSearchParams(); + params.set('count', input.count.toString()); + params.set('offset', input.offset.toString()); + if (input.name) params.set('name', input.name); + return accountApiRequest('GET', `/servers?${params.toString()}`); + }, +}); + +export const getServerById = tool({ + description: 'Get a specific server by ID. Uses account token.', + inputSchema: jsonSchema<{ id: number }>({ + type: 'object', + properties: { + id: { type: 'number', description: 'Server ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: number }): Promise { + return accountApiRequest('GET', `/servers/${encodeURIComponent(input.id)}`); + }, +}); + +export interface CreateServerInput { + Name: string; + Color?: string; + SmtpApiActivated?: boolean; + RawEmailEnabled?: boolean; + InboundHookUrl?: string; + BounceHookUrl?: string; + OpenHookUrl?: string; + PostFirstOpenOnly?: boolean; + TrackOpens?: boolean; + TrackLinks?: 'None' | 'HtmlAndText' | 'HtmlOnly' | 'TextOnly'; + InboundDomain?: string; + InboundSpamThreshold?: number; + ClickHookUrl?: string; + DeliveryHookUrl?: string; +} + +export const createServer = tool({ + description: 'Create a new server in the account. Uses account token.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + Name: { type: 'string', description: 'Server name.' }, + Color: { type: 'string', description: 'Server color.' }, + SmtpApiActivated: { type: 'boolean' }, + RawEmailEnabled: { type: 'boolean' }, + InboundHookUrl: { type: 'string' }, + BounceHookUrl: { type: 'string' }, + OpenHookUrl: { type: 'string' }, + PostFirstOpenOnly: { type: 'boolean' }, + TrackOpens: { type: 'boolean' }, + TrackLinks: { type: 'string', enum: ['None', 'HtmlAndText', 'HtmlOnly', 'TextOnly'] }, + InboundDomain: { type: 'string' }, + InboundSpamThreshold: { type: 'number' }, + ClickHookUrl: { type: 'string' }, + DeliveryHookUrl: { type: 'string' }, + }, + required: ['Name'], + additionalProperties: false, + }), + async execute(input: CreateServerInput): Promise { + return accountApiRequest('POST', '/servers', input); + }, +}); + +export interface UpdateServerByIdInput { + id: number; + Name?: string; + Color?: string; + SmtpApiActivated?: boolean; + RawEmailEnabled?: boolean; + InboundHookUrl?: string; + BounceHookUrl?: string; + OpenHookUrl?: string; + PostFirstOpenOnly?: boolean; + TrackOpens?: boolean; + TrackLinks?: 'None' | 'HtmlAndText' | 'HtmlOnly' | 'TextOnly'; + InboundDomain?: string; + InboundSpamThreshold?: number; + ClickHookUrl?: string; + DeliveryHookUrl?: string; +} + +export const updateServerById = tool({ + description: 'Update a specific server by ID. Uses account token.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + id: { type: 'number', description: 'Server ID.' }, + Name: { type: 'string' }, + Color: { type: 'string' }, + SmtpApiActivated: { type: 'boolean' }, + RawEmailEnabled: { type: 'boolean' }, + InboundHookUrl: { type: 'string' }, + BounceHookUrl: { type: 'string' }, + OpenHookUrl: { type: 'string' }, + PostFirstOpenOnly: { type: 'boolean' }, + TrackOpens: { type: 'boolean' }, + TrackLinks: { type: 'string', enum: ['None', 'HtmlAndText', 'HtmlOnly', 'TextOnly'] }, + InboundDomain: { type: 'string' }, + InboundSpamThreshold: { type: 'number' }, + ClickHookUrl: { type: 'string' }, + DeliveryHookUrl: { type: 'string' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: UpdateServerByIdInput): Promise { + const { id, ...body } = input; + return accountApiRequest('PUT', `/servers/${encodeURIComponent(id)}`, body); + }, +}); + +export const deleteServer = tool({ + description: 'Delete a server from the account. Uses account token.', + inputSchema: jsonSchema<{ id: number }>({ + type: 'object', + properties: { + id: { type: 'number', description: 'Server ID to delete.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: number }): Promise { + return accountApiRequest('DELETE', `/servers/${encodeURIComponent(input.id)}`); + }, +}); + +// ============================================================================ +// Message Streams +// ============================================================================ + +export const listMessageStreams = tool({ + description: 'List all message streams for the server.', + inputSchema: jsonSchema<{ + MessageStreamType?: 'Transactional' | 'Inbound' | 'Broadcasts'; + IncludeArchivedStreams?: boolean; + }>({ + type: 'object', + properties: { + MessageStreamType: { + type: 'string', + enum: ['Transactional', 'Inbound', 'Broadcasts'], + description: 'Filter by stream type.', + }, + IncludeArchivedStreams: { type: 'boolean', description: 'Include archived streams.' }, + }, + additionalProperties: false, + }), + async execute(input: { + MessageStreamType?: string; + IncludeArchivedStreams?: boolean; + }): Promise { + const params = new URLSearchParams(); + if (input.MessageStreamType) params.set('MessageStreamType', input.MessageStreamType); + if (input.IncludeArchivedStreams !== undefined) + params.set('IncludeArchivedStreams', input.IncludeArchivedStreams.toString()); + const query = params.toString() ? `?${params.toString()}` : ''; + return apiRequest('GET', `/message-streams${query}`); + }, +}); + +export const getMessageStream = tool({ + description: 'Get details of a specific message stream.', + inputSchema: jsonSchema<{ id: string }>({ + type: 'object', + properties: { + id: { type: 'string', description: 'Message stream ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: string }): Promise { + return apiRequest('GET', `/message-streams/${encodeURIComponent(input.id)}`); + }, +}); + +export interface CreateMessageStreamInput { + ID: string; + Name: string; + MessageStreamType: 'Transactional' | 'Broadcasts'; + Description?: string; +} + +export const createMessageStream = tool({ + description: 'Create a new message stream.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + ID: { type: 'string', description: 'Unique stream ID (lowercase, alphanumeric, hyphens).' }, + Name: { type: 'string', description: 'Display name.' }, + MessageStreamType: { + type: 'string', + enum: ['Transactional', 'Broadcasts'], + description: 'Stream type.', + }, + Description: { type: 'string', description: 'Optional description.' }, + }, + required: ['ID', 'Name', 'MessageStreamType'], + additionalProperties: false, + }), + async execute(input: CreateMessageStreamInput): Promise { + return apiRequest('POST', '/message-streams', input); + }, +}); + +export interface UpdateMessageStreamInput { + id: string; + Name?: string; + Description?: string; +} + +export const updateMessageStream = tool({ + description: "Update a message stream's name or description.", + inputSchema: jsonSchema({ + type: 'object', + properties: { + id: { type: 'string', description: 'Message stream ID.' }, + Name: { type: 'string', description: 'New display name.' }, + Description: { type: 'string', description: 'New description.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: UpdateMessageStreamInput): Promise { + const { id, ...body } = input; + return apiRequest('PATCH', `/message-streams/${encodeURIComponent(id)}`, body); + }, +}); + +export const archiveMessageStream = tool({ + description: 'Archive a message stream. Archived streams stop accepting messages.', + inputSchema: jsonSchema<{ id: string }>({ + type: 'object', + properties: { + id: { type: 'string', description: 'Message stream ID to archive.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: string }): Promise { + return apiRequest('POST', `/message-streams/${encodeURIComponent(input.id)}/archive`); + }, +}); + +export const unarchiveMessageStream = tool({ + description: 'Unarchive a previously archived message stream.', + inputSchema: jsonSchema<{ id: string }>({ + type: 'object', + properties: { + id: { type: 'string', description: 'Message stream ID to unarchive.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: string }): Promise { + return apiRequest( + 'POST', + `/message-streams/${encodeURIComponent(input.id)}/unarchive` + ); + }, +}); + +// ============================================================================ +// Messages — Outbound +// ============================================================================ + +export interface SearchOutboundMessagesInput { + count: number; + offset: number; + recipient?: string; + fromemail?: string; + tag?: string; + status?: string; + fromdate?: string; + todate?: string; + subject?: string; + metadata_?: string; + messagestream?: string; +} + +export const searchOutboundMessages = tool({ + description: 'Search outbound messages with optional filters.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + count: { type: 'number', description: 'Number of messages to return (max 500).' }, + offset: { type: 'number', description: 'Number of messages to skip.' }, + recipient: { type: 'string', description: 'Filter by recipient email.' }, + fromemail: { type: 'string', description: 'Filter by sender email.' }, + tag: { type: 'string', description: 'Filter by tag.' }, + status: { type: 'string', description: 'Filter by status (queued, sent, processed).' }, + fromdate: { type: 'string', description: 'Start date (YYYY-MM-DD).' }, + todate: { type: 'string', description: 'End date (YYYY-MM-DD).' }, + subject: { type: 'string', description: 'Filter by subject.' }, + metadata_: { type: 'string', description: 'Filter by metadata (key_value format).' }, + messagestream: { type: 'string', description: 'Message stream ID.' }, + }, + required: ['count', 'offset'], + additionalProperties: false, + }), + async execute(input: SearchOutboundMessagesInput): Promise { + const params = new URLSearchParams(); + params.set('count', input.count.toString()); + params.set('offset', input.offset.toString()); + if (input.recipient) params.set('recipient', input.recipient); + if (input.fromemail) params.set('fromemail', input.fromemail); + if (input.tag) params.set('tag', input.tag); + if (input.status) params.set('status', input.status); + if (input.fromdate) params.set('fromdate', input.fromdate); + if (input.todate) params.set('todate', input.todate); + if (input.subject) params.set('subject', input.subject); + if (input.metadata_) params.set('metadata_', input.metadata_); + if (input.messagestream) params.set('messagestream', input.messagestream); + return apiRequest('GET', `/messages/outbound?${params.toString()}`); + }, +}); + +export const getOutboundMessageDetails = tool({ + description: 'Get full details of a specific outbound message.', + inputSchema: jsonSchema<{ id: string }>({ + type: 'object', + properties: { + id: { type: 'string', description: 'Message ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: string }): Promise { + return apiRequest('GET', `/messages/outbound/${encodeURIComponent(input.id)}/details`); + }, +}); + +export const getOutboundMessageDump = tool({ + description: 'Get the raw SMTP dump of an outbound message.', + inputSchema: jsonSchema<{ id: string }>({ + type: 'object', + properties: { + id: { type: 'string', description: 'Message ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: string }): Promise { + return apiRequest('GET', `/messages/outbound/${encodeURIComponent(input.id)}/dump`); + }, +}); + +export const getOutboundMessageOpens = tool({ + description: 'Get open events for a specific outbound message.', + inputSchema: jsonSchema<{ id: string; count?: number; offset?: number }>({ + type: 'object', + properties: { + id: { type: 'string', description: 'Message ID.' }, + count: { type: 'number', description: 'Number of results.' }, + offset: { type: 'number', description: 'Number to skip.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: string; count?: number; offset?: number }): Promise { + const params = new URLSearchParams(); + if (input.count) params.set('count', input.count.toString()); + if (input.offset) params.set('offset', input.offset.toString()); + const query = params.toString() ? `?${params.toString()}` : ''; + return apiRequest( + 'GET', + `/messages/outbound/opens/${encodeURIComponent(input.id)}${query}` + ); + }, +}); + +export const getOutboundMessageClicks = tool({ + description: 'Get click events for a specific outbound message.', + inputSchema: jsonSchema<{ id: string; count?: number; offset?: number }>({ + type: 'object', + properties: { + id: { type: 'string', description: 'Message ID.' }, + count: { type: 'number', description: 'Number of results.' }, + offset: { type: 'number', description: 'Number to skip.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: string; count?: number; offset?: number }): Promise { + const params = new URLSearchParams(); + if (input.count) params.set('count', input.count.toString()); + if (input.offset) params.set('offset', input.offset.toString()); + const query = params.toString() ? `?${params.toString()}` : ''; + return apiRequest( + 'GET', + `/messages/outbound/clicks/${encodeURIComponent(input.id)}${query}` + ); + }, +}); + +// ============================================================================ +// Messages — Inbound +// ============================================================================ + +export interface SearchInboundMessagesInput { + count: number; + offset: number; + recipient?: string; + fromemail?: string; + tag?: string; + status?: string; + fromdate?: string; + todate?: string; + subject?: string; + mailboxhash?: string; +} + +export const searchInboundMessages = tool({ + description: 'Search inbound messages with optional filters.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + count: { type: 'number', description: 'Number of messages to return (max 500).' }, + offset: { type: 'number', description: 'Number of messages to skip.' }, + recipient: { type: 'string', description: 'Filter by recipient email.' }, + fromemail: { type: 'string', description: 'Filter by sender email.' }, + tag: { type: 'string', description: 'Filter by tag.' }, + status: { + type: 'string', + description: 'Filter by status (blocked, processed, queued, failed, scheduled).', + }, + fromdate: { type: 'string', description: 'Start date (YYYY-MM-DD).' }, + todate: { type: 'string', description: 'End date (YYYY-MM-DD).' }, + subject: { type: 'string', description: 'Filter by subject.' }, + mailboxhash: { type: 'string', description: 'Filter by mailbox hash.' }, + }, + required: ['count', 'offset'], + additionalProperties: false, + }), + async execute(input: SearchInboundMessagesInput): Promise { + const params = new URLSearchParams(); + params.set('count', input.count.toString()); + params.set('offset', input.offset.toString()); + if (input.recipient) params.set('recipient', input.recipient); + if (input.fromemail) params.set('fromemail', input.fromemail); + if (input.tag) params.set('tag', input.tag); + if (input.status) params.set('status', input.status); + if (input.fromdate) params.set('fromdate', input.fromdate); + if (input.todate) params.set('todate', input.todate); + if (input.subject) params.set('subject', input.subject); + if (input.mailboxhash) params.set('mailboxhash', input.mailboxhash); + return apiRequest('GET', `/messages/inbound?${params.toString()}`); + }, +}); + +export const getInboundMessageDetails = tool({ + description: 'Get full details of a specific inbound message.', + inputSchema: jsonSchema<{ id: string }>({ + type: 'object', + properties: { + id: { type: 'string', description: 'Inbound message ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: string }): Promise { + return apiRequest('GET', `/messages/inbound/${encodeURIComponent(input.id)}/details`); + }, +}); + +export const bypassInboundRules = tool({ + description: 'Bypass inbound rules for a specific message, reprocessing it.', + inputSchema: jsonSchema<{ id: string }>({ + type: 'object', + properties: { + id: { type: 'string', description: 'Inbound message ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: string }): Promise { + return apiRequest('PUT', `/messages/inbound/${encodeURIComponent(input.id)}/bypass`); + }, +}); + +export const retryInboundMessage = tool({ + description: 'Retry processing of an inbound message.', + inputSchema: jsonSchema<{ id: string }>({ + type: 'object', + properties: { + id: { type: 'string', description: 'Inbound message ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: string }): Promise { + return apiRequest('PUT', `/messages/inbound/${encodeURIComponent(input.id)}/retry`); + }, +}); + +// ============================================================================ +// Messages — Search Opens/Clicks +// ============================================================================ + +export interface SearchMessageOpensInput { + count: number; + offset: number; + recipient?: string; + tag?: string; + client_name?: string; + client_company?: string; + client_family?: string; + os_name?: string; + os_family?: string; + os_company?: string; + platform?: string; + region?: string; + city?: string; + messagestream?: string; +} + +export const searchMessageOpens = tool({ + description: 'Search message open events across all outbound messages.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + count: { type: 'number', description: 'Number of results (max 500).' }, + offset: { type: 'number', description: 'Number to skip.' }, + recipient: { type: 'string', description: 'Filter by recipient.' }, + tag: { type: 'string', description: 'Filter by tag.' }, + client_name: { type: 'string', description: 'Filter by email client name.' }, + client_company: { type: 'string', description: 'Filter by email client company.' }, + client_family: { type: 'string', description: 'Filter by email client family.' }, + os_name: { type: 'string', description: 'Filter by OS name.' }, + os_family: { type: 'string', description: 'Filter by OS family.' }, + os_company: { type: 'string', description: 'Filter by OS company.' }, + platform: { type: 'string', description: 'Filter by platform.' }, + region: { type: 'string', description: 'Filter by region.' }, + city: { type: 'string', description: 'Filter by city.' }, + messagestream: { type: 'string', description: 'Message stream ID.' }, + }, + required: ['count', 'offset'], + additionalProperties: false, + }), + async execute(input: SearchMessageOpensInput): Promise { + const params = new URLSearchParams(); + params.set('count', input.count.toString()); + params.set('offset', input.offset.toString()); + if (input.recipient) params.set('recipient', input.recipient); + if (input.tag) params.set('tag', input.tag); + if (input.client_name) params.set('client_name', input.client_name); + if (input.client_company) params.set('client_company', input.client_company); + if (input.client_family) params.set('client_family', input.client_family); + if (input.os_name) params.set('os_name', input.os_name); + if (input.os_family) params.set('os_family', input.os_family); + if (input.os_company) params.set('os_company', input.os_company); + if (input.platform) params.set('platform', input.platform); + if (input.region) params.set('region', input.region); + if (input.city) params.set('city', input.city); + if (input.messagestream) params.set('messagestream', input.messagestream); + return apiRequest('GET', `/messages/outbound/opens?${params.toString()}`); + }, +}); + +export interface SearchMessageClicksInput { + count: number; + offset: number; + recipient?: string; + tag?: string; + client_name?: string; + client_company?: string; + client_family?: string; + os_name?: string; + os_family?: string; + os_company?: string; + platform?: string; + region?: string; + city?: string; + messagestream?: string; +} + +export const searchMessageClicks = tool({ + description: 'Search message click events across all outbound messages.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + count: { type: 'number', description: 'Number of results (max 500).' }, + offset: { type: 'number', description: 'Number to skip.' }, + recipient: { type: 'string', description: 'Filter by recipient.' }, + tag: { type: 'string', description: 'Filter by tag.' }, + client_name: { type: 'string', description: 'Filter by browser name.' }, + client_company: { type: 'string', description: 'Filter by browser company.' }, + client_family: { type: 'string', description: 'Filter by browser family.' }, + os_name: { type: 'string', description: 'Filter by OS name.' }, + os_family: { type: 'string', description: 'Filter by OS family.' }, + os_company: { type: 'string', description: 'Filter by OS company.' }, + platform: { type: 'string', description: 'Filter by platform.' }, + region: { type: 'string', description: 'Filter by region.' }, + city: { type: 'string', description: 'Filter by city.' }, + messagestream: { type: 'string', description: 'Message stream ID.' }, + }, + required: ['count', 'offset'], + additionalProperties: false, + }), + async execute(input: SearchMessageClicksInput): Promise { + const params = new URLSearchParams(); + params.set('count', input.count.toString()); + params.set('offset', input.offset.toString()); + if (input.recipient) params.set('recipient', input.recipient); + if (input.tag) params.set('tag', input.tag); + if (input.client_name) params.set('client_name', input.client_name); + if (input.client_company) params.set('client_company', input.client_company); + if (input.client_family) params.set('client_family', input.client_family); + if (input.os_name) params.set('os_name', input.os_name); + if (input.os_family) params.set('os_family', input.os_family); + if (input.os_company) params.set('os_company', input.os_company); + if (input.platform) params.set('platform', input.platform); + if (input.region) params.set('region', input.region); + if (input.city) params.set('city', input.city); + if (input.messagestream) params.set('messagestream', input.messagestream); + return apiRequest('GET', `/messages/outbound/clicks?${params.toString()}`); + }, +}); + +// ============================================================================ +// Stats +// ============================================================================ + +export interface StatsQueryInput { + tag?: string; + fromdate?: string; + todate?: string; + messagestream?: string; +} + +/** + * Helper to build stats query params + */ +function buildStatsQuery(input: StatsQueryInput): string { + const params = new URLSearchParams(); + if (input.tag) params.set('tag', input.tag); + if (input.fromdate) params.set('fromdate', input.fromdate); + if (input.todate) params.set('todate', input.todate); + if (input.messagestream) params.set('messagestream', input.messagestream); + const query = params.toString(); + return query ? `?${query}` : ''; +} + +const statsInputSchema = jsonSchema({ + type: 'object', + properties: { + tag: { type: 'string', description: 'Filter by tag.' }, + fromdate: { type: 'string', description: 'Start date (YYYY-MM-DD).' }, + todate: { type: 'string', description: 'End date (YYYY-MM-DD).' }, + messagestream: { type: 'string', description: 'Message stream ID.' }, + }, + additionalProperties: false, +}); + +export const getStatsOverview = tool({ + description: 'Get an overview of outbound email statistics.', + inputSchema: statsInputSchema, + async execute(input: StatsQueryInput): Promise { + return apiRequest('GET', `/stats/outbound${buildStatsQuery(input)}`); + }, +}); + +export const getStatsSends = tool({ + description: 'Get send count statistics over time.', + inputSchema: statsInputSchema, + async execute(input: StatsQueryInput): Promise { + return apiRequest('GET', `/stats/outbound/sends${buildStatsQuery(input)}`); + }, +}); + +export const getStatsBounces = tool({ + description: 'Get bounce statistics over time.', + inputSchema: statsInputSchema, + async execute(input: StatsQueryInput): Promise { + return apiRequest('GET', `/stats/outbound/bounces${buildStatsQuery(input)}`); + }, +}); + +export const getStatsSpamComplaints = tool({ + description: 'Get spam complaint statistics over time.', + inputSchema: statsInputSchema, + async execute(input: StatsQueryInput): Promise { + return apiRequest('GET', `/stats/outbound/spam${buildStatsQuery(input)}`); + }, +}); + +export const getStatsTracked = tool({ + description: 'Get tracked email statistics over time.', + inputSchema: statsInputSchema, + async execute(input: StatsQueryInput): Promise { + return apiRequest('GET', `/stats/outbound/tracked${buildStatsQuery(input)}`); + }, +}); + +export const getStatsOpens = tool({ + description: 'Get email open statistics over time.', + inputSchema: statsInputSchema, + async execute(input: StatsQueryInput): Promise { + return apiRequest('GET', `/stats/outbound/opens${buildStatsQuery(input)}`); + }, +}); + +export const getStatsOpensByPlatform = tool({ + description: 'Get email open statistics grouped by platform.', + inputSchema: statsInputSchema, + async execute(input: StatsQueryInput): Promise { + return apiRequest('GET', `/stats/outbound/opens/platforms${buildStatsQuery(input)}`); + }, +}); + +export const getStatsOpensByClient = tool({ + description: 'Get email open statistics grouped by email client.', + inputSchema: statsInputSchema, + async execute(input: StatsQueryInput): Promise { + return apiRequest( + 'GET', + `/stats/outbound/opens/emailclients${buildStatsQuery(input)}` + ); + }, +}); + +export const getStatsClicks = tool({ + description: 'Get link click statistics over time.', + inputSchema: statsInputSchema, + async execute(input: StatsQueryInput): Promise { + return apiRequest('GET', `/stats/outbound/clicks${buildStatsQuery(input)}`); + }, +}); + +export const getStatsClicksByBrowser = tool({ + description: 'Get link click statistics grouped by browser family.', + inputSchema: statsInputSchema, + async execute(input: StatsQueryInput): Promise { + return apiRequest( + 'GET', + `/stats/outbound/clicks/browserfamilies${buildStatsQuery(input)}` + ); + }, +}); + +export const getStatsClicksByPlatform = tool({ + description: 'Get link click statistics grouped by platform.', + inputSchema: statsInputSchema, + async execute(input: StatsQueryInput): Promise { + return apiRequest('GET', `/stats/outbound/clicks/platforms${buildStatsQuery(input)}`); + }, +}); + +export const getStatsClicksByLocation = tool({ + description: 'Get link click statistics grouped by geographic location.', + inputSchema: statsInputSchema, + async execute(input: StatsQueryInput): Promise { + return apiRequest('GET', `/stats/outbound/clicks/location${buildStatsQuery(input)}`); + }, +}); + +// ============================================================================ +// Domains (account token) +// ============================================================================ + +export interface ListDomainsInput { + count: number; + offset: number; +} + +export const listDomains = tool({ + description: 'List all domains in the account. Uses account token.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + count: { type: 'number', description: 'Number of domains to return.' }, + offset: { type: 'number', description: 'Number of domains to skip.' }, + }, + required: ['count', 'offset'], + additionalProperties: false, + }), + async execute(input: ListDomainsInput): Promise { + const params = new URLSearchParams(); + params.set('count', input.count.toString()); + params.set('offset', input.offset.toString()); + return accountApiRequest('GET', `/domains?${params.toString()}`); + }, +}); + +export const getDomain = tool({ + description: 'Get details of a specific domain. Uses account token.', + inputSchema: jsonSchema<{ id: number }>({ + type: 'object', + properties: { + id: { type: 'number', description: 'Domain ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: number }): Promise { + return accountApiRequest('GET', `/domains/${encodeURIComponent(input.id)}`); + }, +}); + +export interface CreateDomainInput { + Name: string; + ReturnPathDomain?: string; +} + +export const createDomain = tool({ + description: 'Create a new sending domain. Uses account token.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + Name: { type: 'string', description: 'Domain name (e.g., example.com).' }, + ReturnPathDomain: { type: 'string', description: 'Custom return-path domain.' }, + }, + required: ['Name'], + additionalProperties: false, + }), + async execute(input: CreateDomainInput): Promise { + return accountApiRequest('POST', '/domains', input); + }, +}); + +export interface UpdateDomainInput { + id: number; + ReturnPathDomain?: string; +} + +export const updateDomain = tool({ + description: "Update a domain's return-path. Uses account token.", + inputSchema: jsonSchema({ + type: 'object', + properties: { + id: { type: 'number', description: 'Domain ID.' }, + ReturnPathDomain: { type: 'string', description: 'New return-path domain.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: UpdateDomainInput): Promise { + const { id, ...body } = input; + return accountApiRequest('PUT', `/domains/${encodeURIComponent(id)}`, body); + }, +}); + +export const deleteDomain = tool({ + description: 'Delete a domain from the account. Uses account token.', + inputSchema: jsonSchema<{ id: number }>({ + type: 'object', + properties: { + id: { type: 'number', description: 'Domain ID to delete.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: number }): Promise { + return accountApiRequest('DELETE', `/domains/${encodeURIComponent(input.id)}`); + }, +}); + +export const verifyDomainDkim = tool({ + description: 'Trigger DKIM verification for a domain. Uses account token.', + inputSchema: jsonSchema<{ id: number }>({ + type: 'object', + properties: { + id: { type: 'number', description: 'Domain ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: number }): Promise { + return accountApiRequest('PUT', `/domains/${encodeURIComponent(input.id)}/verifyDkim`); + }, +}); + +export const verifyDomainReturnPath = tool({ + description: 'Trigger return-path verification for a domain. Uses account token.', + inputSchema: jsonSchema<{ id: number }>({ + type: 'object', + properties: { + id: { type: 'number', description: 'Domain ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: number }): Promise { + return accountApiRequest( + 'PUT', + `/domains/${encodeURIComponent(input.id)}/verifyReturnPath` + ); + }, +}); + +export const verifyDomainSpf = tool({ + description: 'Trigger SPF verification for a domain. Uses account token.', + inputSchema: jsonSchema<{ id: number }>({ + type: 'object', + properties: { + id: { type: 'number', description: 'Domain ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: number }): Promise { + return accountApiRequest('POST', `/domains/${encodeURIComponent(input.id)}/verifyspf`); + }, +}); + +export const rotateDomainDkim = tool({ + description: 'Rotate DKIM keys for a domain. Uses account token.', + inputSchema: jsonSchema<{ id: number }>({ + type: 'object', + properties: { + id: { type: 'number', description: 'Domain ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: number }): Promise { + return accountApiRequest( + 'POST', + `/domains/${encodeURIComponent(input.id)}/rotatedkim` + ); + }, +}); + +// ============================================================================ +// Sender Signatures (account token) +// ============================================================================ + +export interface ListSenderSignaturesInput { + count: number; + offset: number; +} + +export const listSenderSignatures = tool({ + description: 'List all sender signatures in the account. Uses account token.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + count: { type: 'number', description: 'Number of signatures to return.' }, + offset: { type: 'number', description: 'Number of signatures to skip.' }, + }, + required: ['count', 'offset'], + additionalProperties: false, + }), + async execute(input: ListSenderSignaturesInput): Promise { + const params = new URLSearchParams(); + params.set('count', input.count.toString()); + params.set('offset', input.offset.toString()); + return accountApiRequest('GET', `/senders?${params.toString()}`); + }, +}); + +export const getSenderSignature = tool({ + description: 'Get details of a specific sender signature. Uses account token.', + inputSchema: jsonSchema<{ id: number }>({ + type: 'object', + properties: { + id: { type: 'number', description: 'Sender signature ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: number }): Promise { + return accountApiRequest('GET', `/senders/${encodeURIComponent(input.id)}`); + }, +}); + +export interface CreateSenderSignatureInput { + FromEmail: string; + Name: string; + ReplyToEmail?: string; + ReturnPathDomain?: string; +} + +export const createSenderSignature = tool({ + description: 'Create a new sender signature. Uses account token.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + FromEmail: { type: 'string', description: 'Sender email address.' }, + Name: { type: 'string', description: 'Sender display name.' }, + ReplyToEmail: { type: 'string', description: 'Reply-to email address.' }, + ReturnPathDomain: { type: 'string', description: 'Custom return-path domain.' }, + }, + required: ['FromEmail', 'Name'], + additionalProperties: false, + }), + async execute(input: CreateSenderSignatureInput): Promise { + return accountApiRequest('POST', '/senders', input); + }, +}); + +export interface UpdateSenderSignatureInput { + id: number; + Name?: string; + ReplyToEmail?: string; + ReturnPathDomain?: string; +} + +export const updateSenderSignature = tool({ + description: 'Update an existing sender signature. Uses account token.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + id: { type: 'number', description: 'Sender signature ID.' }, + Name: { type: 'string', description: 'Sender display name.' }, + ReplyToEmail: { type: 'string', description: 'Reply-to email address.' }, + ReturnPathDomain: { type: 'string', description: 'Custom return-path domain.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: UpdateSenderSignatureInput): Promise { + const { id, ...body } = input; + return accountApiRequest('PUT', `/senders/${encodeURIComponent(id)}`, body); + }, +}); + +export const deleteSenderSignature = tool({ + description: 'Delete a sender signature. Uses account token.', + inputSchema: jsonSchema<{ id: number }>({ + type: 'object', + properties: { + id: { type: 'number', description: 'Sender signature ID to delete.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: number }): Promise { + return accountApiRequest('DELETE', `/senders/${encodeURIComponent(input.id)}`); + }, +}); + +export const resendSenderConfirmation = tool({ + description: 'Resend the confirmation email for a sender signature. Uses account token.', + inputSchema: jsonSchema<{ id: number }>({ + type: 'object', + properties: { + id: { type: 'number', description: 'Sender signature ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: number }): Promise { + return accountApiRequest('POST', `/senders/${encodeURIComponent(input.id)}/resend`); + }, +}); + +// ============================================================================ +// Webhooks +// ============================================================================ + +export const listWebhooks = tool({ + description: 'List all webhooks for the server.', + inputSchema: jsonSchema<{ MessageStream?: string }>({ + type: 'object', + properties: { + MessageStream: { type: 'string', description: 'Filter by message stream ID.' }, + }, + additionalProperties: false, + }), + async execute(input: { MessageStream?: string }): Promise { + const params = new URLSearchParams(); + if (input.MessageStream) params.set('MessageStream', input.MessageStream); + const query = params.toString() ? `?${params.toString()}` : ''; + return apiRequest('GET', `/webhooks${query}`); + }, +}); + +export const getWebhook = tool({ + description: 'Get details of a specific webhook.', + inputSchema: jsonSchema<{ id: number }>({ + type: 'object', + properties: { + id: { type: 'number', description: 'Webhook ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: number }): Promise { + return apiRequest('GET', `/webhooks/${encodeURIComponent(input.id)}`); + }, +}); + +export interface CreateWebhookInput { + Url: string; + MessageStream?: string; + HttpAuth?: { Username: string; Password: string }; + HttpHeaders?: Array<{ Name: string; Value: string }>; + Triggers?: { + Open?: { Enabled: boolean; PostFirstOpenOnly?: boolean }; + Click?: { Enabled: boolean }; + Delivery?: { Enabled: boolean }; + Bounce?: { Enabled: boolean; IncludeContent?: boolean }; + SpamComplaint?: { Enabled: boolean; IncludeContent?: boolean }; + SubscriptionChange?: { Enabled: boolean }; + }; +} + +export const createWebhook = tool({ + description: 'Create a new webhook.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + Url: { type: 'string', description: 'Webhook endpoint URL.' }, + MessageStream: { type: 'string', description: 'Message stream to listen on.' }, + HttpAuth: { + type: 'object', + description: 'HTTP basic auth credentials.', + properties: { + Username: { type: 'string' }, + Password: { type: 'string' }, + }, + required: ['Username', 'Password'], + }, + HttpHeaders: { + type: 'array', + description: 'Custom HTTP headers sent with webhook.', + items: { + type: 'object', + properties: { + Name: { type: 'string' }, + Value: { type: 'string' }, + }, + required: ['Name', 'Value'], + }, + }, + Triggers: { + type: 'object', + description: 'Event triggers to enable.', + properties: { + Open: { + type: 'object', + properties: { + Enabled: { type: 'boolean' }, + PostFirstOpenOnly: { type: 'boolean' }, + }, + required: ['Enabled'], + }, + Click: { + type: 'object', + properties: { Enabled: { type: 'boolean' } }, + required: ['Enabled'], + }, + Delivery: { + type: 'object', + properties: { Enabled: { type: 'boolean' } }, + required: ['Enabled'], + }, + Bounce: { + type: 'object', + properties: { + Enabled: { type: 'boolean' }, + IncludeContent: { type: 'boolean' }, + }, + required: ['Enabled'], + }, + SpamComplaint: { + type: 'object', + properties: { + Enabled: { type: 'boolean' }, + IncludeContent: { type: 'boolean' }, + }, + required: ['Enabled'], + }, + SubscriptionChange: { + type: 'object', + properties: { Enabled: { type: 'boolean' } }, + required: ['Enabled'], + }, + }, + }, + }, + required: ['Url'], + additionalProperties: false, + }), + async execute(input: CreateWebhookInput): Promise { + return apiRequest('POST', '/webhooks', input); + }, +}); + +export interface UpdateWebhookInput { + id: number; + Url?: string; + HttpAuth?: { Username: string; Password: string }; + HttpHeaders?: Array<{ Name: string; Value: string }>; + Triggers?: { + Open?: { Enabled: boolean; PostFirstOpenOnly?: boolean }; + Click?: { Enabled: boolean }; + Delivery?: { Enabled: boolean }; + Bounce?: { Enabled: boolean; IncludeContent?: boolean }; + SpamComplaint?: { Enabled: boolean; IncludeContent?: boolean }; + SubscriptionChange?: { Enabled: boolean }; + }; +} + +export const updateWebhook = tool({ + description: 'Update an existing webhook.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + id: { type: 'number', description: 'Webhook ID.' }, + Url: { type: 'string', description: 'Webhook endpoint URL.' }, + HttpAuth: { + type: 'object', + properties: { + Username: { type: 'string' }, + Password: { type: 'string' }, + }, + required: ['Username', 'Password'], + }, + HttpHeaders: { + type: 'array', + items: { + type: 'object', + properties: { Name: { type: 'string' }, Value: { type: 'string' } }, + required: ['Name', 'Value'], + }, + }, + Triggers: { + type: 'object', + properties: { + Open: { + type: 'object', + properties: { Enabled: { type: 'boolean' }, PostFirstOpenOnly: { type: 'boolean' } }, + required: ['Enabled'], + }, + Click: { + type: 'object', + properties: { Enabled: { type: 'boolean' } }, + required: ['Enabled'], + }, + Delivery: { + type: 'object', + properties: { Enabled: { type: 'boolean' } }, + required: ['Enabled'], + }, + Bounce: { + type: 'object', + properties: { Enabled: { type: 'boolean' }, IncludeContent: { type: 'boolean' } }, + required: ['Enabled'], + }, + SpamComplaint: { + type: 'object', + properties: { Enabled: { type: 'boolean' }, IncludeContent: { type: 'boolean' } }, + required: ['Enabled'], + }, + SubscriptionChange: { + type: 'object', + properties: { Enabled: { type: 'boolean' } }, + required: ['Enabled'], + }, + }, + }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: UpdateWebhookInput): Promise { + const { id, ...body } = input; + return apiRequest('PUT', `/webhooks/${encodeURIComponent(id)}`, body); + }, +}); + +export const deleteWebhook = tool({ + description: 'Delete a webhook.', + inputSchema: jsonSchema<{ id: number }>({ + type: 'object', + properties: { + id: { type: 'number', description: 'Webhook ID to delete.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: number }): Promise { + return apiRequest('DELETE', `/webhooks/${encodeURIComponent(input.id)}`); + }, +}); + +// ============================================================================ +// Suppressions +// ============================================================================ + +export interface ListSuppressionsInput { + streamId: string; + SuppressionReason?: 'ManualSuppression' | 'HardBounce' | 'SpamComplaint'; + Origin?: 'Recipient' | 'Customer' | 'Admin'; + EmailAddress?: string; +} + +export const listSuppressions = tool({ + description: 'List suppressed email addresses for a message stream.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + streamId: { type: 'string', description: 'Message stream ID.' }, + SuppressionReason: { + type: 'string', + enum: ['ManualSuppression', 'HardBounce', 'SpamComplaint'], + description: 'Filter by suppression reason.', + }, + Origin: { + type: 'string', + enum: ['Recipient', 'Customer', 'Admin'], + description: 'Filter by origin.', + }, + EmailAddress: { type: 'string', description: 'Filter by email address.' }, + }, + required: ['streamId'], + additionalProperties: false, + }), + async execute(input: ListSuppressionsInput): Promise { + const { streamId, ...queryParams } = input; + const params = new URLSearchParams(); + if (queryParams.SuppressionReason) + params.set('SuppressionReason', queryParams.SuppressionReason); + if (queryParams.Origin) params.set('Origin', queryParams.Origin); + if (queryParams.EmailAddress) params.set('EmailAddress', queryParams.EmailAddress); + const query = params.toString() ? `?${params.toString()}` : ''; + return apiRequest( + 'GET', + `/message-streams/${encodeURIComponent(streamId)}/suppressions/dump${query}` + ); + }, +}); + +export interface CreateSuppressionsInput { + streamId: string; + Suppressions: Array<{ EmailAddress: string }>; +} + +export const createSuppressions = tool({ + description: 'Add email addresses to the suppression list for a message stream.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + streamId: { type: 'string', description: 'Message stream ID.' }, + Suppressions: { + type: 'array', + description: 'Email addresses to suppress.', + items: { + type: 'object', + properties: { + EmailAddress: { type: 'string', description: 'Email address to suppress.' }, + }, + required: ['EmailAddress'], + }, + }, + }, + required: ['streamId', 'Suppressions'], + additionalProperties: false, + }), + async execute(input: CreateSuppressionsInput): Promise { + return apiRequest( + 'POST', + `/message-streams/${encodeURIComponent(input.streamId)}/suppressions`, + { Suppressions: input.Suppressions } + ); + }, +}); + +export interface DeleteSuppressionsInput { + streamId: string; + Suppressions: Array<{ EmailAddress: string }>; +} + +export const deleteSuppressions = tool({ + description: 'Remove email addresses from the suppression list.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + streamId: { type: 'string', description: 'Message stream ID.' }, + Suppressions: { + type: 'array', + description: 'Email addresses to unsuppress.', + items: { + type: 'object', + properties: { + EmailAddress: { type: 'string', description: 'Email address to remove.' }, + }, + required: ['EmailAddress'], + }, + }, + }, + required: ['streamId', 'Suppressions'], + additionalProperties: false, + }), + async execute(input: DeleteSuppressionsInput): Promise { + return apiRequest( + 'POST', + `/message-streams/${encodeURIComponent(input.streamId)}/suppressions/delete`, + { Suppressions: input.Suppressions } + ); + }, +}); + +// ============================================================================ +// Inbound Rules +// ============================================================================ + +export const listInboundRules = tool({ + description: 'List all inbound processing rules.', + inputSchema: jsonSchema<{ count: number; offset: number }>({ + type: 'object', + properties: { + count: { type: 'number', description: 'Number of rules to return.' }, + offset: { type: 'number', description: 'Number of rules to skip.' }, + }, + required: ['count', 'offset'], + additionalProperties: false, + }), + async execute(input: { count: number; offset: number }): Promise { + const params = new URLSearchParams(); + params.set('count', input.count.toString()); + params.set('offset', input.offset.toString()); + return apiRequest('GET', `/triggers/inboundrules?${params.toString()}`); + }, +}); + +export const createInboundRule = tool({ + description: 'Create a new inbound processing rule to block emails matching a pattern.', + inputSchema: jsonSchema<{ Rule: string }>({ + type: 'object', + properties: { + Rule: { + type: 'string', + description: + 'Email address or domain pattern to block (e.g., test@example.com or @example.com).', + }, + }, + required: ['Rule'], + additionalProperties: false, + }), + async execute(input: { Rule: string }): Promise { + return apiRequest('POST', '/triggers/inboundrules', input); + }, +}); + +export const deleteInboundRule = tool({ + description: 'Delete an inbound processing rule.', + inputSchema: jsonSchema<{ id: number }>({ + type: 'object', + properties: { + id: { type: 'number', description: 'Inbound rule ID to delete.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: number }): Promise { + return apiRequest('DELETE', `/triggers/inboundrules/${encodeURIComponent(input.id)}`); + }, +}); + +// ============================================================================ +// Data Removals (account token) +// ============================================================================ + +export interface CreateDataRemovalInput { + RequestedBy: string; + RequestedFor: string; + NotifyWhenCompleted?: boolean; +} + +export const createDataRemoval = tool({ + description: + 'Request removal of personal data associated with an email address. Uses account token.', + inputSchema: jsonSchema({ + type: 'object', + properties: { + RequestedBy: { type: 'string', description: 'Email of the person requesting removal.' }, + RequestedFor: { + type: 'string', + description: 'Email address whose data should be removed.', + }, + NotifyWhenCompleted: { type: 'boolean', description: 'Send notification when complete.' }, + }, + required: ['RequestedBy', 'RequestedFor'], + additionalProperties: false, + }), + async execute(input: CreateDataRemovalInput): Promise { + return accountApiRequest('POST', '/data-removals', input); + }, +}); + +export const getDataRemovalStatus = tool({ + description: 'Get the status of a data removal request. Uses account token.', + inputSchema: jsonSchema<{ id: number }>({ + type: 'object', + properties: { + id: { type: 'number', description: 'Data removal request ID.' }, + }, + required: ['id'], + additionalProperties: false, + }), + async execute(input: { id: number }): Promise { + return accountApiRequest('GET', `/data-removals/${encodeURIComponent(input.id)}`); + }, +}); + +// ============================================================================ +// Default Export +// ============================================================================ + +export default { + // Email Sending + sendEmail, + sendBatchEmails, + sendEmailWithTemplate, + sendBatchWithTemplates, + // Bulk Email + sendBulkEmail, + getBulkEmailStatus, + // Bounces + getDeliveryStats, + searchBounces, + getBounce, + getBounceDump, + activateBounce, + // Templates + listTemplates, + getTemplate, + createTemplate, + updateTemplate, + deleteTemplate, + validateTemplate, + pushTemplates, + // Server Config + getServer, + updateServer, + // Servers Management + listServers, + getServerById, + createServer, + updateServerById, + deleteServer, + // Message Streams + listMessageStreams, + getMessageStream, + createMessageStream, + updateMessageStream, + archiveMessageStream, + unarchiveMessageStream, + // Messages — Outbound + searchOutboundMessages, + getOutboundMessageDetails, + getOutboundMessageDump, + getOutboundMessageOpens, + getOutboundMessageClicks, + // Messages — Inbound + searchInboundMessages, + getInboundMessageDetails, + bypassInboundRules, + retryInboundMessage, + // Messages — Search Opens/Clicks + searchMessageOpens, + searchMessageClicks, + // Stats + getStatsOverview, + getStatsSends, + getStatsBounces, + getStatsSpamComplaints, + getStatsTracked, + getStatsOpens, + getStatsOpensByPlatform, + getStatsOpensByClient, + getStatsClicks, + getStatsClicksByBrowser, + getStatsClicksByPlatform, + getStatsClicksByLocation, + // Domains + listDomains, + getDomain, + createDomain, + updateDomain, + deleteDomain, + verifyDomainDkim, + verifyDomainReturnPath, + verifyDomainSpf, + rotateDomainDkim, + // Sender Signatures + listSenderSignatures, + getSenderSignature, + createSenderSignature, + updateSenderSignature, + deleteSenderSignature, + resendSenderConfirmation, + // Webhooks + listWebhooks, + getWebhook, + createWebhook, + updateWebhook, + deleteWebhook, + // Suppressions + listSuppressions, + createSuppressions, + deleteSuppressions, + // Inbound Rules + listInboundRules, + createInboundRule, + deleteInboundRule, + // Data Removals + createDataRemoval, + getDataRemovalStatus, +}; diff --git a/packages/tools/official/postmark/tsconfig.json b/packages/tools/official/postmark/tsconfig.json new file mode 100644 index 0000000..6521d56 --- /dev/null +++ b/packages/tools/official/postmark/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "@tpmjs/tsconfig/base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "incremental": false, + "composite": false + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/tools/official/postmark/tsup.config.ts b/packages/tools/official/postmark/tsup.config.ts new file mode 100644 index 0000000..a242871 --- /dev/null +++ b/packages/tools/official/postmark/tsup.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['esm'], + dts: true, + clean: true, + treeshake: true, + splitting: false, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 198f9da..d35c3a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2538,6 +2538,22 @@ importers: specifier: ^5.9.3 version: 5.9.3 + packages/tools/official/postmark: + dependencies: + ai: + specifier: 6.0.49 + version: 6.0.49(zod@4.3.5) + devDependencies: + '@tpmjs/tsconfig': + specifier: workspace:* + version: link:../../../config/tsconfig + tsup: + specifier: ^8.5.1 + version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + packages/tools/official/postmortem-action-extractor: dependencies: ai: