diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 208cec09b42..061e0c0cdf5 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -4681,6 +4681,17 @@ export function IAMIcon(props: SVGProps) { ) } +export function IdentityCenterIcon(props: SVGProps) { + return ( + + + + ) +} + export function STSIcon(props: SVGProps) { return ( @@ -4699,6 +4710,24 @@ export function STSIcon(props: SVGProps) { ) } +export function SESIcon(props: SVGProps) { + return ( + + + + + + + + + + + ) +} + export function SecretsManagerIcon(props: SVGProps) { return ( diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 66570ec3af3..13061ba7819 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -91,6 +91,7 @@ import { HuggingFaceIcon, HunterIOIcon, IAMIcon, + IdentityCenterIcon, ImageIcon, IncidentioIcon, InfisicalIcon, @@ -152,6 +153,7 @@ import { RootlyIcon, S3Icon, SalesforceIcon, + SESIcon, SearchIcon, SecretsManagerIcon, SendgridIcon, @@ -294,6 +296,7 @@ export const blockTypeToIconMap: Record = { huggingface: HuggingFaceIcon, hunter: HunterIOIcon, iam: IAMIcon, + identity_center: IdentityCenterIcon, image_generator: ImageIcon, imap: MailServerIcon, incidentio: IncidentioIcon, @@ -370,6 +373,7 @@ export const blockTypeToIconMap: Record = { sentry: SentryIcon, serper: SerperIcon, servicenow: ServiceNowIcon, + ses: SESIcon, sftp: SftpIcon, sharepoint: MicrosoftSharepointIcon, shopify: ShopifyIcon, diff --git a/apps/docs/content/docs/en/tools/cloudwatch.mdx b/apps/docs/content/docs/en/tools/cloudwatch.mdx index 1fc1b19ea75..af0fc0a2b0e 100644 --- a/apps/docs/content/docs/en/tools/cloudwatch.mdx +++ b/apps/docs/content/docs/en/tools/cloudwatch.mdx @@ -57,9 +57,12 @@ Run a CloudWatch Log Insights query against one or more log groups | Parameter | Type | Description | | --------- | ---- | ----------- | -| `results` | array | Query result rows | -| `statistics` | object | Query statistics \(bytesScanned, recordsMatched, recordsScanned\) | -| `status` | string | Query completion status | +| `results` | array | Query result rows \(each row is a key/value map of field name to value\) | +| `statistics` | object | Query statistics | +| ↳ `bytesScanned` | number | Total bytes of log data scanned | +| ↳ `recordsMatched` | number | Number of log records that matched the query | +| ↳ `recordsScanned` | number | Total log records scanned | +| `status` | string | Query completion status \(Complete, Failed, Cancelled, or Timeout\) | ### `cloudwatch_describe_log_groups` @@ -80,6 +83,11 @@ List available CloudWatch log groups | Parameter | Type | Description | | --------- | ---- | ----------- | | `logGroups` | array | List of CloudWatch log groups with metadata | +| ↳ `logGroupName` | string | Log group name | +| ↳ `arn` | string | Log group ARN | +| ↳ `storedBytes` | number | Total stored bytes | +| ↳ `retentionInDays` | number | Retention period in days \(if set\) | +| ↳ `creationTime` | number | Creation time in epoch milliseconds | ### `cloudwatch_get_log_events` @@ -103,6 +111,9 @@ Retrieve log events from a specific CloudWatch log stream | Parameter | Type | Description | | --------- | ---- | ----------- | | `events` | array | Log events with timestamp, message, and ingestion time | +| ↳ `timestamp` | number | Event timestamp in epoch milliseconds | +| ↳ `message` | string | Log event message | +| ↳ `ingestionTime` | number | Ingestion time in epoch milliseconds | ### `cloudwatch_describe_log_streams` @@ -123,7 +134,12 @@ List log streams within a CloudWatch log group | Parameter | Type | Description | | --------- | ---- | ----------- | -| `logStreams` | array | List of log streams with metadata | +| `logStreams` | array | List of log streams with metadata, sorted by last event time \(most recent first\) unless a prefix filter is applied | +| ↳ `logStreamName` | string | Log stream name | +| ↳ `lastEventTimestamp` | number | Timestamp of the last log event in epoch milliseconds | +| ↳ `firstEventTimestamp` | number | Timestamp of the first log event in epoch milliseconds | +| ↳ `creationTime` | number | Stream creation time in epoch milliseconds | +| ↳ `storedBytes` | number | Total stored bytes | ### `cloudwatch_list_metrics` @@ -146,6 +162,9 @@ List available CloudWatch metrics | Parameter | Type | Description | | --------- | ---- | ----------- | | `metrics` | array | List of metrics with namespace, name, and dimensions | +| ↳ `namespace` | string | Metric namespace \(e.g., AWS/EC2\) | +| ↳ `metricName` | string | Metric name \(e.g., CPUUtilization\) | +| ↳ `dimensions` | array | Array of name/value dimension pairs | ### `cloudwatch_get_metric_statistics` @@ -170,8 +189,15 @@ Get statistics for a CloudWatch metric over a time range | Parameter | Type | Description | | --------- | ---- | ----------- | -| `label` | string | Metric label | -| `datapoints` | array | Datapoints with timestamp and statistics values | +| `label` | string | Metric label returned by CloudWatch | +| `datapoints` | array | Datapoints sorted by timestamp with statistics values | +| ↳ `timestamp` | number | Datapoint timestamp in epoch milliseconds | +| ↳ `average` | number | Average statistic value | +| ↳ `sum` | number | Sum statistic value | +| ↳ `minimum` | number | Minimum statistic value | +| ↳ `maximum` | number | Maximum statistic value | +| ↳ `sampleCount` | number | Sample count statistic value | +| ↳ `unit` | string | Unit of the metric | ### `cloudwatch_put_metric_data` @@ -222,5 +248,13 @@ List and filter CloudWatch alarms | Parameter | Type | Description | | --------- | ---- | ----------- | | `alarms` | array | List of CloudWatch alarms with state and configuration | +| ↳ `alarmName` | string | Alarm name | +| ↳ `alarmArn` | string | Alarm ARN | +| ↳ `stateValue` | string | Current state \(OK, ALARM, INSUFFICIENT_DATA\) | +| ↳ `stateReason` | string | Human-readable reason for the state | +| ↳ `metricName` | string | Metric name \(MetricAlarm only\) | +| ↳ `namespace` | string | Metric namespace \(MetricAlarm only\) | +| ↳ `threshold` | number | Threshold value \(MetricAlarm only\) | +| ↳ `stateUpdatedTimestamp` | number | Epoch ms when state last changed | diff --git a/apps/docs/content/docs/en/tools/dynamodb.mdx b/apps/docs/content/docs/en/tools/dynamodb.mdx index 36bc79f6f04..3d37f479dba 100644 --- a/apps/docs/content/docs/en/tools/dynamodb.mdx +++ b/apps/docs/content/docs/en/tools/dynamodb.mdx @@ -1,6 +1,6 @@ --- title: Amazon DynamoDB -description: Connect to Amazon DynamoDB +description: Get, put, query, scan, update, and delete items in Amazon DynamoDB tables --- import { BlockInfoCard } from "@/components/ui/block-info-card" @@ -55,7 +55,7 @@ Get an item from a DynamoDB table by primary key | `accessKeyId` | string | Yes | AWS access key ID | | `secretAccessKey` | string | Yes | AWS secret access key | | `tableName` | string | Yes | DynamoDB table name \(e.g., "Users", "Orders"\) | -| `key` | object | Yes | Primary key of the item to retrieve \(e.g., \{"pk": "USER#123"\} or \{"pk": "ORDER#456", "sk": "ITEM#789"\}\) | +| `key` | json | Yes | Primary key of the item to retrieve \(e.g., \{"pk": "USER#123"\} or \{"pk": "ORDER#456", "sk": "ITEM#789"\}\) | | `consistentRead` | boolean | No | Use strongly consistent read | #### Output @@ -63,7 +63,7 @@ Get an item from a DynamoDB table by primary key | Parameter | Type | Description | | --------- | ---- | ----------- | | `message` | string | Operation status message | -| `item` | object | Retrieved item | +| `item` | json | Retrieved item | ### `dynamodb_put` @@ -77,14 +77,17 @@ Put an item into a DynamoDB table | `accessKeyId` | string | Yes | AWS access key ID | | `secretAccessKey` | string | Yes | AWS secret access key | | `tableName` | string | Yes | DynamoDB table name \(e.g., "Users", "Orders"\) | -| `item` | object | Yes | Item to put into the table \(e.g., \{"pk": "USER#123", "name": "John", "email": "john@example.com"\}\) | +| `item` | json | Yes | Item to put into the table \(e.g., \{"pk": "USER#123", "name": "John", "email": "john@example.com"\}\) | +| `conditionExpression` | string | No | Condition that must be met for the put to succeed \(e.g., "attribute_not_exists\(pk\)" to prevent overwrites\) | +| `expressionAttributeNames` | json | No | Attribute name mappings for reserved words used in conditionExpression \(e.g., \{"#name": "name"\}\) | +| `expressionAttributeValues` | json | No | Expression attribute values used in conditionExpression \(e.g., \{":expected": "value"\}\) | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | | `message` | string | Operation status message | -| `item` | object | Created item | +| `item` | json | Created item | ### `dynamodb_query` @@ -100,10 +103,12 @@ Query items from a DynamoDB table using key conditions | `tableName` | string | Yes | DynamoDB table name \(e.g., "Users", "Orders"\) | | `keyConditionExpression` | string | Yes | Key condition expression \(e.g., "pk = :pk" or "pk = :pk AND sk BEGINS_WITH :prefix"\) | | `filterExpression` | string | No | Filter expression for results \(e.g., "age > :minAge AND #status = :status"\) | -| `expressionAttributeNames` | object | No | Attribute name mappings for reserved words \(e.g., \{"#status": "status"\}\) | -| `expressionAttributeValues` | object | No | Expression attribute values \(e.g., \{":pk": "USER#123", ":minAge": 18\}\) | +| `expressionAttributeNames` | json | No | Attribute name mappings for reserved words \(e.g., \{"#status": "status"\}\) | +| `expressionAttributeValues` | json | No | Expression attribute values \(e.g., \{":pk": "USER#123", ":minAge": 18\}\) | | `indexName` | string | No | Secondary index name to query \(e.g., "GSI1", "email-index"\) | | `limit` | number | No | Maximum number of items to return \(e.g., 10, 50, 100\) | +| `exclusiveStartKey` | json | No | Pagination token from a previous query's lastEvaluatedKey to continue fetching results | +| `scanIndexForward` | boolean | No | Sort order for the sort key: true for ascending \(default\), false for descending | #### Output @@ -112,6 +117,7 @@ Query items from a DynamoDB table using key conditions | `message` | string | Operation status message | | `items` | array | Array of items returned | | `count` | number | Number of items returned | +| `lastEvaluatedKey` | json | Pagination token to pass as exclusiveStartKey to fetch the next page of results | ### `dynamodb_scan` @@ -127,9 +133,10 @@ Scan all items in a DynamoDB table | `tableName` | string | Yes | DynamoDB table name \(e.g., "Users", "Orders"\) | | `filterExpression` | string | No | Filter expression for results \(e.g., "age > :minAge AND #status = :status"\) | | `projectionExpression` | string | No | Attributes to retrieve \(e.g., "pk, sk, #name, email"\) | -| `expressionAttributeNames` | object | No | Attribute name mappings for reserved words \(e.g., \{"#name": "name", "#status": "status"\}\) | -| `expressionAttributeValues` | object | No | Expression attribute values \(e.g., \{":minAge": 18, ":status": "active"\}\) | +| `expressionAttributeNames` | json | No | Attribute name mappings for reserved words \(e.g., \{"#name": "name", "#status": "status"\}\) | +| `expressionAttributeValues` | json | No | Expression attribute values \(e.g., \{":minAge": 18, ":status": "active"\}\) | | `limit` | number | No | Maximum number of items to return \(e.g., 10, 50, 100\) | +| `exclusiveStartKey` | json | No | Pagination token from a previous scan's lastEvaluatedKey to continue fetching results | #### Output @@ -138,6 +145,7 @@ Scan all items in a DynamoDB table | `message` | string | Operation status message | | `items` | array | Array of items returned | | `count` | number | Number of items returned | +| `lastEvaluatedKey` | json | Pagination token to pass as exclusiveStartKey to fetch the next page of results | ### `dynamodb_update` @@ -151,10 +159,10 @@ Update an item in a DynamoDB table | `accessKeyId` | string | Yes | AWS access key ID | | `secretAccessKey` | string | Yes | AWS secret access key | | `tableName` | string | Yes | DynamoDB table name \(e.g., "Users", "Orders"\) | -| `key` | object | Yes | Primary key of the item to update \(e.g., \{"pk": "USER#123"\} or \{"pk": "ORDER#456", "sk": "ITEM#789"\}\) | +| `key` | json | Yes | Primary key of the item to update \(e.g., \{"pk": "USER#123"\} or \{"pk": "ORDER#456", "sk": "ITEM#789"\}\) | | `updateExpression` | string | Yes | Update expression \(e.g., "SET #name = :name, age = :age" or "SET #count = #count + :inc"\) | -| `expressionAttributeNames` | object | No | Attribute name mappings for reserved words \(e.g., \{"#name": "name", "#count": "count"\}\) | -| `expressionAttributeValues` | object | No | Expression attribute values \(e.g., \{":name": "John", ":age": 30, ":inc": 1\}\) | +| `expressionAttributeNames` | json | No | Attribute name mappings for reserved words \(e.g., \{"#name": "name", "#count": "count"\}\) | +| `expressionAttributeValues` | json | No | Expression attribute values \(e.g., \{":name": "John", ":age": 30, ":inc": 1\}\) | | `conditionExpression` | string | No | Condition that must be met for the update to succeed \(e.g., "attribute_exists\(pk\)" or "version = :expectedVersion"\) | #### Output @@ -162,7 +170,7 @@ Update an item in a DynamoDB table | Parameter | Type | Description | | --------- | ---- | ----------- | | `message` | string | Operation status message | -| `item` | object | Updated item | +| `item` | json | Updated item with all attributes | ### `dynamodb_delete` @@ -176,8 +184,10 @@ Delete an item from a DynamoDB table | `accessKeyId` | string | Yes | AWS access key ID | | `secretAccessKey` | string | Yes | AWS secret access key | | `tableName` | string | Yes | DynamoDB table name \(e.g., "Users", "Orders"\) | -| `key` | object | Yes | Primary key of the item to delete \(e.g., \{"pk": "USER#123"\} or \{"pk": "ORDER#456", "sk": "ITEM#789"\}\) | +| `key` | json | Yes | Primary key of the item to delete \(e.g., \{"pk": "USER#123"\} or \{"pk": "ORDER#456", "sk": "ITEM#789"\}\) | | `conditionExpression` | string | No | Condition that must be met for the delete to succeed \(e.g., "attribute_exists\(pk\)"\) | +| `expressionAttributeNames` | json | No | Attribute name mappings for reserved words used in conditionExpression \(e.g., \{"#status": "status"\}\) | +| `expressionAttributeValues` | json | No | Expression attribute values used in conditionExpression \(e.g., \{":status": "active"\}\) | #### Output @@ -204,6 +214,6 @@ Introspect DynamoDB to list tables or get detailed schema information for a spec | --------- | ---- | ----------- | | `message` | string | Operation status message | | `tables` | array | List of table names in the region | -| `tableDetails` | object | Detailed schema information for a specific table | +| `tableDetails` | json | Detailed schema information for a specific table | diff --git a/apps/docs/content/docs/en/tools/iam.mdx b/apps/docs/content/docs/en/tools/iam.mdx index 5fd9263eadd..36b3aee42f5 100644 --- a/apps/docs/content/docs/en/tools/iam.mdx +++ b/apps/docs/content/docs/en/tools/iam.mdx @@ -68,7 +68,7 @@ Get detailed information about an IAM user | `region` | string | Yes | AWS region \(e.g., us-east-1\) | | `accessKeyId` | string | Yes | AWS access key ID | | `secretAccessKey` | string | Yes | AWS secret access key | -| `userName` | string | Yes | The name of the IAM user to retrieve | +| `userName` | string | No | The name of the IAM user to retrieve \(defaults to the caller if omitted\) | #### Output @@ -440,4 +440,80 @@ Remove an IAM user from a group | --------- | ---- | ----------- | | `message` | string | Operation status message | +### `iam_list_attached_role_policies` + +List all managed policies attached to an IAM role + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `roleName` | string | Yes | Name of the IAM role | +| `pathPrefix` | string | No | Path prefix to filter policies \(e.g., /application/\) | +| `maxItems` | number | No | Maximum number of policies to return \(1-1000\) | +| `marker` | string | No | Pagination marker from a previous request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `attachedPolicies` | json | List of attached policies with policyName and policyArn | +| `isTruncated` | boolean | Whether there are more results available | +| `marker` | string | Pagination marker for the next page of results | +| `count` | number | Number of attached policies returned | + +### `iam_list_attached_user_policies` + +List all managed policies attached to an IAM user + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `userName` | string | Yes | Name of the IAM user | +| `pathPrefix` | string | No | Path prefix to filter policies \(e.g., /application/\) | +| `maxItems` | number | No | Maximum number of policies to return \(1-1000\) | +| `marker` | string | No | Pagination marker from a previous request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `attachedPolicies` | json | List of attached policies with policyName and policyArn | +| `isTruncated` | boolean | Whether there are more results available | +| `marker` | string | Pagination marker for the next page of results | +| `count` | number | Number of attached policies returned | + +### `iam_simulate_principal_policy` + +Simulate whether a user, role, or group is allowed to perform specific AWS actions — useful for pre-flight access checks + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `policySourceArn` | string | Yes | ARN of the user, group, or role to simulate \(e.g., arn:aws:iam::123456789012:user/alice\) | +| `actionNames` | string | Yes | Comma-separated list of AWS actions to simulate \(e.g., s3:GetObject,ec2:DescribeInstances\) | +| `resourceArns` | string | No | Comma-separated list of resource ARNs to simulate against \(defaults to * if not provided\) | +| `maxResults` | number | No | Maximum number of simulation results to return \(1-1000\) | +| `marker` | string | No | Pagination marker from a previous request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `evaluationResults` | json | Simulation results per action: evalActionName, evalResourceName, evalDecision \(allowed/explicitDeny/implicitDeny\), matchedStatements \(sourcePolicyId, sourcePolicyType\), missingContextValues | +| `isTruncated` | boolean | Whether there are more results available | +| `marker` | string | Pagination marker for the next page of results | +| `count` | number | Number of evaluation results returned | + diff --git a/apps/docs/content/docs/en/tools/identity_center.mdx b/apps/docs/content/docs/en/tools/identity_center.mdx new file mode 100644 index 00000000000..4c000e8fe05 --- /dev/null +++ b/apps/docs/content/docs/en/tools/identity_center.mdx @@ -0,0 +1,340 @@ +--- +title: AWS Identity Center +description: Manage temporary elevated access in AWS IAM Identity Center +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[AWS IAM Identity Center](https://aws.amazon.com/iam/identity-center/) (formerly AWS Single Sign-On) is the recommended service for managing workforce access to multiple AWS accounts and applications. It provides a central place to assign users and groups temporary, permission-scoped access to AWS accounts using permission sets — without creating long-lived IAM credentials. + +With AWS IAM Identity Center, you can: + +- **Provision account assignments**: Grant a user or group access to a specific AWS account with a specific permission set — the core primitive of temporary elevated access +- **Revoke access on demand**: Delete account assignments to immediately remove elevated permissions when they are no longer needed +- **Look up users by email**: Resolve a federated identity (email address) to an Identity Store user ID for programmatic access provisioning +- **List permission sets**: Enumerate the available permission sets (e.g., ReadOnly, PowerUser, AdministratorAccess) defined in your Identity Center instance +- **Monitor assignment status**: Poll the provisioning status of create/delete operations, which are asynchronous in AWS +- **List accounts in your organization**: Enumerate all AWS accounts in your AWS Organizations structure to populate access request dropdowns +- **Manage groups**: List groups and resolve group IDs by display name for group-based access grants + +In Sim, the AWS Identity Center integration is designed to power **TEAM (Temporary Elevated Access Management)** workflows — automated pipelines where users request elevated access, approvers approve or deny it, access is provisioned with a time limit, and auto-revocation removes it when the window expires. This replaces manual console-based access management with auditable, agent-driven workflows that integrate with Slack, email, ticketing systems, and CloudTrail for full traceability. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Provision and revoke temporary access to AWS accounts via IAM Identity Center (SSO). Assign permission sets to users or groups, look up users by email, and list accounts and permission sets for access request workflows. + + + +## Tools + +### `identity_center_list_instances` + +List all AWS IAM Identity Center instances in your account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `maxResults` | number | No | Maximum number of instances to return \(1-100\) | +| `nextToken` | string | No | Pagination token from a previous request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `instances` | json | List of Identity Center instances with instanceArn, identityStoreId, name, status, statusReason | +| `nextToken` | string | Pagination token for the next page of results | +| `count` | number | Number of instances returned | + +### `identity_center_list_accounts` + +List all AWS accounts in your organization + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `maxResults` | number | No | Maximum number of accounts to return | +| `nextToken` | string | No | Pagination token from a previous request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `accounts` | json | List of AWS accounts with id, arn, name, email, status | +| `nextToken` | string | Pagination token for the next page of results | +| `count` | number | Number of accounts returned | + +### `identity_center_describe_account` + +Retrieve details about a specific AWS account by its ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `accountId` | string | Yes | AWS account ID to describe | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | AWS account ID | +| `arn` | string | AWS account ARN | +| `name` | string | Account name | +| `email` | string | Root email address of the account | +| `status` | string | Account status \(ACTIVE, SUSPENDED, etc.\) | +| `joinedTimestamp` | string | Date the account joined the organization | + +### `identity_center_list_permission_sets` + +List all permission sets defined in an IAM Identity Center instance + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `instanceArn` | string | Yes | ARN of the Identity Center instance | +| `maxResults` | number | No | Maximum number of permission sets to return | +| `nextToken` | string | No | Pagination token from a previous request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `permissionSets` | json | List of permission sets with permissionSetArn, name, description, sessionDuration | +| `nextToken` | string | Pagination token for the next page of results | +| `count` | number | Number of permission sets returned | + +### `identity_center_get_user` + +Look up a user in the Identity Store by email address + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `identityStoreId` | string | Yes | Identity Store ID \(from the Identity Center instance\) | +| `email` | string | Yes | Email address of the user to look up | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `userId` | string | Identity Store user ID \(use as principalId\) | +| `userName` | string | Username in the Identity Store | +| `displayName` | string | Display name of the user | +| `email` | string | Email address of the user | + +### `identity_center_get_group` + +Look up a group in the Identity Store by display name + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `identityStoreId` | string | Yes | Identity Store ID \(from the Identity Center instance\) | +| `displayName` | string | Yes | Display name of the group to look up | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `groupId` | string | Identity Store group ID \(use as principalId\) | +| `displayName` | string | Display name of the group | +| `description` | string | Group description | + +### `identity_center_list_groups` + +List all groups in the Identity Store + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `identityStoreId` | string | Yes | Identity Store ID \(from the Identity Center instance\) | +| `maxResults` | number | No | Maximum number of groups to return | +| `nextToken` | string | No | Pagination token from a previous request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `groups` | json | List of groups with groupId, displayName, description | +| `nextToken` | string | Pagination token for the next page of results | +| `count` | number | Number of groups returned | + +### `identity_center_create_account_assignment` + +Grant a user or group access to an AWS account via a permission set (temporary elevated access) + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `instanceArn` | string | Yes | ARN of the Identity Center instance | +| `accountId` | string | Yes | AWS account ID to grant access to | +| `permissionSetArn` | string | Yes | ARN of the permission set to assign | +| `principalType` | string | Yes | Type of principal: USER or GROUP | +| `principalId` | string | Yes | Identity Store ID of the user or group | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Status message | +| `status` | string | Provisioning status: IN_PROGRESS, FAILED, or SUCCEEDED | +| `requestId` | string | Request ID to use with Check Assignment Status | +| `accountId` | string | Target AWS account ID | +| `permissionSetArn` | string | Permission set ARN | +| `principalType` | string | Principal type \(USER or GROUP\) | +| `principalId` | string | Principal ID | +| `failureReason` | string | Reason for failure if status is FAILED | +| `createdDate` | string | Date the request was created | + +### `identity_center_delete_account_assignment` + +Revoke a user or group access to an AWS account by removing a permission set assignment + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `instanceArn` | string | Yes | ARN of the Identity Center instance | +| `accountId` | string | Yes | AWS account ID to revoke access from | +| `permissionSetArn` | string | Yes | ARN of the permission set to remove | +| `principalType` | string | Yes | Type of principal: USER or GROUP | +| `principalId` | string | Yes | Identity Store ID of the user or group | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Status message | +| `status` | string | Deprovisioning status: IN_PROGRESS, FAILED, or SUCCEEDED | +| `requestId` | string | Request ID to use with Check Assignment Status | +| `accountId` | string | Target AWS account ID | +| `permissionSetArn` | string | Permission set ARN | +| `principalType` | string | Principal type \(USER or GROUP\) | +| `principalId` | string | Principal ID | +| `failureReason` | string | Reason for failure if status is FAILED | +| `createdDate` | string | Date the request was created | + +### `identity_center_check_assignment_status` + +Check the provisioning status of an account assignment creation request + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `instanceArn` | string | Yes | ARN of the Identity Center instance | +| `requestId` | string | Yes | Request ID returned from Create or Delete Account Assignment | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Human-readable status message | +| `status` | string | Current status: IN_PROGRESS, FAILED, or SUCCEEDED | +| `requestId` | string | The request ID that was checked | +| `accountId` | string | Target AWS account ID | +| `permissionSetArn` | string | Permission set ARN | +| `principalType` | string | Principal type \(USER or GROUP\) | +| `principalId` | string | Principal ID | +| `failureReason` | string | Reason for failure if status is FAILED | +| `createdDate` | string | Date the request was created | + +### `identity_center_check_assignment_deletion_status` + +Check the deprovisioning status of an account assignment deletion request + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `instanceArn` | string | Yes | ARN of the Identity Center instance | +| `requestId` | string | Yes | Request ID returned from Delete Account Assignment | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Human-readable status message | +| `status` | string | Current deletion status: IN_PROGRESS, FAILED, or SUCCEEDED | +| `requestId` | string | The deletion request ID that was checked | +| `accountId` | string | Target AWS account ID | +| `permissionSetArn` | string | Permission set ARN | +| `principalType` | string | Principal type \(USER or GROUP\) | +| `principalId` | string | Principal ID | +| `failureReason` | string | Reason for failure if status is FAILED | +| `createdDate` | string | Date the request was created | + +### `identity_center_list_account_assignments` + +List all account assignments for a specific user or group across all accounts + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `instanceArn` | string | Yes | ARN of the Identity Center instance | +| `principalId` | string | Yes | Identity Store ID of the user or group | +| `principalType` | string | Yes | Type of principal: USER or GROUP | +| `maxResults` | number | No | Maximum number of assignments to return | +| `nextToken` | string | No | Pagination token from a previous request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `assignments` | json | List of account assignments with accountId, permissionSetArn, principalType, principalId | +| `nextToken` | string | Pagination token for the next page of results | +| `count` | number | Number of assignments returned | + + diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index 2658fa2c390..91d84fa1e9e 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -86,6 +86,7 @@ "huggingface", "hunter", "iam", + "identity_center", "image_generator", "imap", "incidentio", @@ -154,6 +155,7 @@ "sentry", "serper", "servicenow", + "ses", "sftp", "sharepoint", "shopify", diff --git a/apps/docs/content/docs/en/tools/ses.mdx b/apps/docs/content/docs/en/tools/ses.mdx new file mode 100644 index 00000000000..33248914c2a --- /dev/null +++ b/apps/docs/content/docs/en/tools/ses.mdx @@ -0,0 +1,241 @@ +--- +title: AWS SES +description: Send emails and manage templates with AWS Simple Email Service +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Amazon Simple Email Service (SES)](https://aws.amazon.com/ses/) is a cloud-based email sending service designed for high-volume, transactional, and marketing email delivery. It provides a cost-effective, scalable way to send email without managing your own mail server infrastructure. + +With AWS SES, you can: + +- **Send simple emails**: Deliver one-off emails with plain text or HTML body content to individual recipients +- **Send templated emails**: Use pre-defined templates with variable substitution (e.g., `{{name}}`, `{{link}}`) for personalized emails at scale +- **Send bulk emails**: Deliver templated emails to large lists of recipients in a single API call, with per-destination data overrides +- **Manage email templates**: Create, retrieve, list, and delete reusable email templates for transactional and marketing campaigns +- **Monitor account health**: Retrieve your account's sending quota, send rate, and whether sending is currently enabled + +In Sim, the AWS SES integration is designed for workflows that need reliable, programmatic email delivery — from access request notifications and approval alerts to bulk outreach and automated reporting. It pairs naturally with the IAM Identity Center integration for TEAM (Temporary Elevated Access Management) workflows, where email notifications are sent when access is provisioned, approved, or revoked. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Integrate AWS SES v2 into the workflow. Send simple, templated, and bulk emails. Manage email templates and retrieve account sending quota and verified identity information. + + + +## Tools + +### `ses_send_email` + +Send an email via AWS SES using simple or HTML content + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `fromAddress` | string | Yes | Verified sender email address | +| `toAddresses` | string | Yes | Comma-separated list of recipient email addresses | +| `subject` | string | Yes | Email subject line | +| `bodyText` | string | No | Plain text email body | +| `bodyHtml` | string | No | HTML email body | +| `ccAddresses` | string | No | Comma-separated list of CC email addresses | +| `bccAddresses` | string | No | Comma-separated list of BCC email addresses | +| `replyToAddresses` | string | No | Comma-separated list of reply-to email addresses | +| `configurationSetName` | string | No | SES configuration set name for tracking | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `messageId` | string | SES message ID for the sent email | + +### `ses_send_templated_email` + +Send an email using an SES email template with dynamic template data + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `fromAddress` | string | Yes | Verified sender email address | +| `toAddresses` | string | Yes | Comma-separated list of recipient email addresses | +| `templateName` | string | Yes | Name of the SES email template to use | +| `templateData` | string | Yes | JSON string of key-value pairs for template variable substitution | +| `ccAddresses` | string | No | Comma-separated list of CC email addresses | +| `bccAddresses` | string | No | Comma-separated list of BCC email addresses | +| `configurationSetName` | string | No | SES configuration set name for tracking | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `messageId` | string | SES message ID for the sent email | + +### `ses_send_bulk_email` + +Send emails to multiple recipients using an SES template with per-recipient data + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `fromAddress` | string | Yes | Verified sender email address | +| `templateName` | string | Yes | Name of the SES email template to use | +| `destinations` | string | Yes | JSON array of destination objects with toAddresses \(string\[\]\) and optional templateData \(JSON string\); falls back to defaultTemplateData when omitted | +| `defaultTemplateData` | string | No | Default JSON template data used when a destination does not specify its own | +| `configurationSetName` | string | No | SES configuration set name for tracking | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `results` | array | Per-destination send results with status and messageId | +| `successCount` | number | Number of successfully sent emails | +| `failureCount` | number | Number of failed email sends | + +### `ses_list_identities` + +List all verified email identities (email addresses and domains) in your SES account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `pageSize` | number | No | Maximum number of identities to return \(1-1000\) | +| `nextToken` | string | No | Pagination token from a previous list response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `identities` | array | List of email identities with name, type, sending status, and verification status | +| `nextToken` | string | Pagination token for the next page of results | +| `count` | number | Number of identities returned | + +### `ses_get_account` + +Get SES account sending quota and status information + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `sendingEnabled` | boolean | Whether email sending is enabled for the account | +| `max24HourSend` | number | Maximum emails allowed per 24-hour period | +| `maxSendRate` | number | Maximum emails allowed per second | +| `sentLast24Hours` | number | Number of emails sent in the last 24 hours | + +### `ses_create_template` + +Create a new SES email template for use with templated email sending + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `templateName` | string | Yes | Unique name for the email template | +| `subjectPart` | string | Yes | Subject line template \(supports \{\{variable\}\} substitution\) | +| `textPart` | string | No | Plain text version of the template body | +| `htmlPart` | string | No | HTML version of the template body | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Confirmation message for the created template | + +### `ses_get_template` + +Retrieve the content and details of an SES email template + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `templateName` | string | Yes | Name of the template to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `templateName` | string | Name of the template | +| `subjectPart` | string | Subject line of the template | +| `textPart` | string | Plain text body of the template | +| `htmlPart` | string | HTML body of the template | + +### `ses_list_templates` + +List all SES email templates in your account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `pageSize` | number | No | Maximum number of templates to return | +| `nextToken` | string | No | Pagination token from a previous list response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `templates` | array | List of email templates with name and creation timestamp | +| `nextToken` | string | Pagination token for the next page of results | +| `count` | number | Number of templates returned | + +### `ses_delete_template` + +Delete an existing SES email template + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | AWS region \(e.g., us-east-1\) | +| `accessKeyId` | string | Yes | AWS access key ID | +| `secretAccessKey` | string | Yes | AWS secret access key | +| `templateName` | string | Yes | Name of the template to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Confirmation message for the deleted template | + + diff --git a/apps/docs/content/docs/en/tools/sts.mdx b/apps/docs/content/docs/en/tools/sts.mdx index 29fd0445e16..6c46a0ba784 100644 --- a/apps/docs/content/docs/en/tools/sts.mdx +++ b/apps/docs/content/docs/en/tools/sts.mdx @@ -46,6 +46,7 @@ Assume an IAM role and receive temporary security credentials | `roleArn` | string | Yes | ARN of the IAM role to assume | | `roleSessionName` | string | Yes | Identifier for the assumed role session | | `durationSeconds` | number | No | Duration of the session in seconds \(900-43200, default 3600\) | +| `policy` | string | No | JSON IAM policy to further restrict session permissions \(max 2048 chars\) | | `externalId` | string | No | External ID for cross-account access | | `serialNumber` | string | No | MFA device serial number or ARN | | `tokenCode` | string | No | MFA token code \(6 digits\) | @@ -61,6 +62,7 @@ Assume an IAM role and receive temporary security credentials | `assumedRoleArn` | string | ARN of the assumed role | | `assumedRoleId` | string | Assumed role ID with session name | | `packedPolicySize` | number | Percentage of allowed policy size used | +| `sourceIdentity` | string | Source identity set on the role session, if any | ### `sts_get_caller_identity` diff --git a/apps/docs/content/docs/en/triggers/fireflies.mdx b/apps/docs/content/docs/en/triggers/fireflies.mdx index 563f6608509..9a265f451b0 100644 --- a/apps/docs/content/docs/en/triggers/fireflies.mdx +++ b/apps/docs/content/docs/en/triggers/fireflies.mdx @@ -29,6 +29,7 @@ Trigger workflow when a Fireflies meeting transcription is complete | Parameter | Type | Description | | --------- | ---- | ----------- | | `meetingId` | string | The ID of the transcribed meeting | -| `eventType` | string | The type of event \(Transcription completed\) | +| `eventType` | string | The type of event \(e.g. Transcription completed, meeting.transcribed\) | | `clientReferenceId` | string | Custom reference ID if set during upload | +| `timestamp` | number | Unix timestamp in milliseconds when the event was fired \(V2 webhooks\) | diff --git a/apps/docs/content/docs/en/triggers/jsm.mdx b/apps/docs/content/docs/en/triggers/jsm.mdx index 6aabf82cade..4233fa04e7b 100644 --- a/apps/docs/content/docs/en/triggers/jsm.mdx +++ b/apps/docs/content/docs/en/triggers/jsm.mdx @@ -304,7 +304,7 @@ Trigger workflow on any Jira Service Management webhook event | ↳ `id` | string | Changelog ID | | `comment` | object | comment output from the tool | | ↳ `id` | string | Comment ID | -| ↳ `body` | string | Comment text/body | +| ↳ `body` | json | Comment body in Atlassian Document Format \(ADF\). On Jira Server this may be a plain string. | | ↳ `author` | object | author output from the tool | | ↳ `displayName` | string | Comment author display name | | ↳ `accountId` | string | Comment author account ID | diff --git a/apps/docs/content/docs/en/triggers/slack.mdx b/apps/docs/content/docs/en/triggers/slack.mdx index cdffda257ac..cc5d20b042c 100644 --- a/apps/docs/content/docs/en/triggers/slack.mdx +++ b/apps/docs/content/docs/en/triggers/slack.mdx @@ -25,6 +25,7 @@ Trigger workflow from Slack events like mentions, messages, and reactions | `signingSecret` | string | Yes | The signing secret from your Slack app to validate request authenticity. | | `botToken` | string | No | The bot token from your Slack app. Required for downloading files attached to messages. | | `includeFiles` | boolean | No | Download and include file attachments from messages. Requires a bot token with files:read scope. | +| `setupWizard` | modal | No | Walk through manifest creation, app install, and pasting credentials. | #### Output diff --git a/apps/sim/app/(landing)/components/contact/consts.ts b/apps/sim/app/(landing)/components/contact/consts.ts new file mode 100644 index 00000000000..242d06ecaf0 --- /dev/null +++ b/apps/sim/app/(landing)/components/contact/consts.ts @@ -0,0 +1,82 @@ +import { z } from 'zod' +import { NO_EMAIL_HEADER_CONTROL_CHARS_REGEX } from '@/lib/messaging/email/utils' +import { quickValidateEmail } from '@/lib/messaging/email/validation' + +export const CONTACT_TOPIC_VALUES = [ + 'general', + 'support', + 'integration', + 'feature_request', + 'sales', + 'partnership', + 'billing', + 'other', +] as const + +export const CONTACT_TOPIC_OPTIONS = [ + { value: 'general', label: 'General question' }, + { value: 'support', label: 'Technical support' }, + { value: 'integration', label: 'Integration request' }, + { value: 'feature_request', label: 'Feature request' }, + { value: 'sales', label: 'Sales & pricing' }, + { value: 'partnership', label: 'Partnership' }, + { value: 'billing', label: 'Billing' }, + { value: 'other', label: 'Other' }, +] as const + +export const contactRequestSchema = z.object({ + name: z + .string() + .trim() + .min(1, 'Name is required') + .max(120, 'Name must be 120 characters or less') + .regex(NO_EMAIL_HEADER_CONTROL_CHARS_REGEX, 'Invalid characters'), + email: z + .string() + .trim() + .min(1, 'Email is required') + .max(320) + .transform((value) => value.toLowerCase()) + .refine((value) => quickValidateEmail(value).isValid, 'Enter a valid email'), + company: z + .string() + .trim() + .max(120, 'Company must be 120 characters or less') + .optional() + .transform((value) => (value && value.length > 0 ? value : undefined)), + topic: z.enum(CONTACT_TOPIC_VALUES, { + errorMap: () => ({ message: 'Please select a topic' }), + }), + subject: z + .string() + .trim() + .min(1, 'Subject is required') + .max(200, 'Subject must be 200 characters or less') + .regex(NO_EMAIL_HEADER_CONTROL_CHARS_REGEX, 'Invalid characters'), + message: z + .string() + .trim() + .min(1, 'Message is required') + .max(5000, 'Message must be 5,000 characters or less'), +}) + +export type ContactRequestPayload = z.infer + +export function getContactTopicLabel(value: ContactRequestPayload['topic']): string { + return CONTACT_TOPIC_OPTIONS.find((option) => option.value === value)?.label ?? value +} + +export type HelpEmailType = 'bug' | 'feedback' | 'feature_request' | 'other' + +export function mapContactTopicToHelpType(topic: ContactRequestPayload['topic']): HelpEmailType { + switch (topic) { + case 'feature_request': + return 'feature_request' + case 'support': + return 'bug' + case 'integration': + return 'feedback' + default: + return 'other' + } +} diff --git a/apps/sim/app/(landing)/components/contact/contact-form.tsx b/apps/sim/app/(landing)/components/contact/contact-form.tsx new file mode 100644 index 00000000000..4cde2b3da7a --- /dev/null +++ b/apps/sim/app/(landing)/components/contact/contact-form.tsx @@ -0,0 +1,239 @@ +'use client' + +import { useState } from 'react' +import { useMutation } from '@tanstack/react-query' +import { Combobox, Input, Textarea } from '@/components/emcn' +import { Check } from '@/components/emcn/icons' +import { cn } from '@/lib/core/utils/cn' +import { captureClientEvent } from '@/lib/posthog/client' +import { + CONTACT_TOPIC_OPTIONS, + type ContactRequestPayload, + contactRequestSchema, +} from '@/app/(landing)/components/contact/consts' +import { LandingField } from '@/app/(landing)/components/forms/landing-field' + +type ContactField = keyof ContactRequestPayload +type ContactErrors = Partial> + +interface ContactFormState { + name: string + email: string + company: string + topic: ContactRequestPayload['topic'] | '' + subject: string + message: string +} + +const INITIAL_FORM_STATE: ContactFormState = { + name: '', + email: '', + company: '', + topic: '', + subject: '', + message: '', +} + +const COMBOBOX_TOPICS = [...CONTACT_TOPIC_OPTIONS] + +const LANDING_INPUT = + 'h-[36px] rounded-[5px] border border-[var(--border-1)] bg-[var(--surface-5)] px-3 font-[430] font-season text-[14px] text-[var(--text-primary)] outline-none transition-colors placeholder:text-[var(--text-muted)]' + +async function submitContactRequest(payload: ContactRequestPayload) { + const response = await fetch('/api/contact', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }) + + const result = (await response.json().catch(() => null)) as { + error?: string + message?: string + } | null + + if (!response.ok) { + throw new Error(result?.error || 'Failed to send message') + } + + return result +} + +export function ContactForm() { + const [form, setForm] = useState(INITIAL_FORM_STATE) + const [errors, setErrors] = useState({}) + const [submitSuccess, setSubmitSuccess] = useState(false) + + const contactMutation = useMutation({ + mutationFn: submitContactRequest, + onSuccess: (_data, variables) => { + captureClientEvent('landing_contact_submitted', { topic: variables.topic }) + setForm(INITIAL_FORM_STATE) + setErrors({}) + setSubmitSuccess(true) + }, + }) + + function updateField( + field: TField, + value: ContactFormState[TField] + ) { + setForm((prev) => ({ ...prev, [field]: value })) + setErrors((prev) => { + if (!prev[field as ContactField]) { + return prev + } + const nextErrors = { ...prev } + delete nextErrors[field as ContactField] + return nextErrors + }) + if (contactMutation.isError) { + contactMutation.reset() + } + } + + function handleSubmit(event: React.FormEvent) { + event.preventDefault() + if (contactMutation.isPending) return + + const parsed = contactRequestSchema.safeParse({ + ...form, + company: form.company || undefined, + }) + + if (!parsed.success) { + const fieldErrors = parsed.error.flatten().fieldErrors + setErrors({ + name: fieldErrors.name?.[0], + email: fieldErrors.email?.[0], + company: fieldErrors.company?.[0], + topic: fieldErrors.topic?.[0], + subject: fieldErrors.subject?.[0], + message: fieldErrors.message?.[0], + }) + return + } + + contactMutation.mutate(parsed.data) + } + + const submitError = contactMutation.isError + ? contactMutation.error instanceof Error + ? contactMutation.error.message + : 'Failed to send message. Please try again.' + : null + + if (submitSuccess) { + return ( +
+
+ +
+

+ Message received +

+

+ Thanks for reaching out. We've sent a confirmation to your inbox and will get back to you + shortly. +

+ +
+ ) + } + + return ( +
+
+ + updateField('name', event.target.value)} + placeholder='Your name' + className={LANDING_INPUT} + /> + + + updateField('email', event.target.value)} + placeholder='you@company.com' + className={LANDING_INPUT} + /> + +
+ +
+ + updateField('company', event.target.value)} + placeholder='Company name' + className={LANDING_INPUT} + /> + + + updateField('topic', value as ContactRequestPayload['topic'])} + placeholder='Select a topic' + editable={false} + filterOptions={false} + className='h-[36px] rounded-[5px] px-3 font-[430] font-season text-[14px]' + /> + +
+ + + updateField('subject', event.target.value)} + placeholder='How can we help?' + className={LANDING_INPUT} + /> + + + +