Provisions a complete Amazon Connect cloud contact center β instance, storage configs, hours of operation, queues, security profiles, routing profiles, contact flows, quick connects, the 5-level user hierarchy, agents, claimed phone numbers, and Lambda/Lex/vocabulary integrations β secure by default. Built for the AWS provider v6.x.
- βοΈ One instance, fully wired. Creates
aws_connect_instanceplus all 16 supporting resource types β a working contact center from one module call, not 17 hand-authored resource blocks. - ποΈ Recording/transcript storage is first-class.
storage_configscovers all 13 validresource_typevalues (not the commonly-assumed 6) with a deeply-typed, polymorphic schema across S3 / Kinesis Stream / Kinesis Firehose / Kinesis Video Stream destinations. - π CMK-ready encryption for member-PII streams.
CALL_RECORDINGSandCHAT_TRANSCRIPTSstorage configs carry a first-classencryption_configβ wire a customer-managed KMS key fromterraform-aws-kmsrather than relying on default S3 encryption. - π Audit visibility on by default.
contact_flow_logs_enableddefaults totrueβ overriding the provider's ownfalsedefault β because visibility into how a member's call was routed is a compliance control, not a nicety.contact_lens_enableddefaults totrueas well. - π No hardcoded passwords, ever. User passwords are supplied per-user through a dedicated
user_passwordsmap markedsensitive = trueβ never embedded in the mainusersobject, never defaulted. - π A resolved Terraform-graph cycle. Amazon Connect allows queues and quick connects to reference each other, but Terraform's static resource graph cannot support both directions in one apply. This module takes
queues[*].quick_connect_idsas raw IDs (not module keys) so both real-world wiring directions stay available without aCycle:error. - π·οΈ Tags on every resource that supports them.
instance_storage_config,user_hierarchy_structure,phone_number_contact_flow_association,lambda_function_association, andbot_associationare the documented exceptions β notagsargument exists on those in the current provider schema.
π‘ Why it matters: Amazon Connect is where member phone calls happen β and where their recordings and transcripts, which may contain PII, get stored. Getting the storage-config encryption, audit-logging, and hierarchy story right in one composite module keeps a regulated FI's contact center auditable from day one.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- β Star this repository to help others discover this Terraform module.
- π€ Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- β Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
terraform-aws-connect sits above the encryption/storage/streaming/compute layer. It consumes an S3 bucket, a KMS CMK, Kinesis stream/Firehose ARNs, and a Lambda function ARN β all by reference β and optionally an existing Directory Service directory. It does not touch networking or compute directly; Connect is a fully managed SaaS control plane with no VPC/ENI footprint of its own.
flowchart LR
s3["terraform-aws-s3-bucket<br/>bucket_name (recordings/transcripts)"]
kms["terraform-aws-kms<br/>CMK key_id (ARN)"]
kinesis["terraform-aws-kinesis-stream<br/>stream_arn"]
firehose["terraform-aws-kinesis-firehose<br/>firehose_arn"]
lambda["terraform-aws-lambda<br/>function_arn"]
ds["Directory Service<br/>directory_id (optional)"]
connect["terraform-aws-connect"]
s3 -->|"storage_configs.s3_config.bucket_name"| connect
kms -->|"storage_configs.*.encryption_config.key_id"| connect
kinesis -->|"storage_configs.kinesis_stream_config.stream_arn"| connect
firehose -->|"storage_configs.kinesis_firehose_config.firehose_arn"| connect
lambda -->|"lambda_function_associations.function_arn"| connect
ds -.->|"directory_id"| connect
style connect fill:#FF9900,color:#fff,stroke:#cc7a00,stroke-width:2px
βΉοΈ
terraform-aws-kinesis-stream,terraform-aws-kinesis-firehose, andterraform-aws-lambdaare Phase 2/7 modules in this repository's roadmap β until they exist, wire raw ARNs from wherever those resources are managed.
flowchart TD
subgraph mod["terraform-aws-connect"]
inst["aws_connect_instance.this<br/>(keystone)"]
store["aws_connect_instance_storage_config.this<br/>for_each storage_configs"]
hoo["aws_connect_hours_of_operation.this<br/>for_each hours_of_operations"]
queue["aws_connect_queue.this<br/>for_each queues"]
secp["aws_connect_security_profile.this<br/>for_each security_profiles"]
rp["aws_connect_routing_profile.this<br/>for_each routing_profiles"]
cf["aws_connect_contact_flow.this<br/>for_each contact_flows"]
cfm["aws_connect_contact_flow_module.this<br/>for_each contact_flow_modules"]
qc["aws_connect_quick_connect.this<br/>for_each quick_connects"]
uhs["aws_connect_user_hierarchy_structure.this<br/>guarded for_each"]
uhg["aws_connect_user_hierarchy_group.this<br/>for_each user_hierarchy_groups"]
user["aws_connect_user.this<br/>for_each users"]
phone["aws_connect_phone_number.this<br/>for_each phone_numbers"]
pnca["aws_connect_phone_number_contact_flow_association.this"]
lfa["aws_connect_lambda_function_association.this"]
bot["aws_connect_bot_association.this"]
vocab["aws_connect_vocabulary.this"]
end
inst --> store
inst --> hoo
hoo --> queue
inst --> secp
queue --> rp
inst --> cf
inst --> cfm
queue --> qc
cf --> qc
inst --> uhs
uhs --> uhg
rp --> user
secp --> user
uhg --> user
qc --> user
inst --> phone
phone --> pnca
cf --> pnca
inst --> lfa
inst --> bot
inst --> vocab
style inst fill:#FF9900,color:#fff,stroke:#cc7a00,stroke-width:2px
style uhs stroke-dasharray: 5 5
| Resource | Count | Created when |
|---|---|---|
aws_connect_instance.this |
1 | always (keystone) |
aws_connect_instance_storage_config.this |
0..13 | one per storage_configs entry |
aws_connect_hours_of_operation.this |
0..N | one per hours_of_operations entry |
aws_connect_queue.this |
0..N | one per queues entry |
aws_connect_security_profile.this |
0..N | one per security_profiles entry |
aws_connect_routing_profile.this |
0..N | one per routing_profiles entry |
aws_connect_contact_flow.this |
0..N | one per contact_flows entry |
aws_connect_contact_flow_module.this |
0..N | one per contact_flow_modules entry |
aws_connect_quick_connect.this |
0..N | one per quick_connects entry |
aws_connect_user_hierarchy_structure.this |
0 or 1 | var.user_hierarchy_structure != null |
aws_connect_user_hierarchy_group.this |
0..N | one per user_hierarchy_groups entry |
aws_connect_user.this |
0..N | one per users entry |
aws_connect_phone_number.this |
0..N | one per phone_numbers entry |
aws_connect_phone_number_contact_flow_association.this |
0..N | one per phone_number_contact_flow_associations entry |
aws_connect_lambda_function_association.this |
0..N | one per lambda_function_associations entry |
aws_connect_bot_association.this |
0..N | one per bot_associations entry |
aws_connect_vocabulary.this |
0..N | one per vocabularies entry |
| Requirement | Version |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/aws |
>= 6.0, < 7.0 |
The module declares only a required_providers block (providers.tf) and inherits the configured provider. There is no provider {} block and no credential variable β credentials resolve through the standard AWS chain at the root/pipeline level (env vars β SSO/shared credentials β assume_role β instance profile / IRSA β OIDC web identity).
Least-privilege actions the Terraform execution identity needs to manage this module.
| Action | Required for | Notes |
|---|---|---|
connect:CreateInstance, connect:DeleteInstance, connect:DescribeInstance, connect:ListInstances, connect:UpdateInstanceAttribute |
Instance lifecycle | 100 combined create/delete per 30 days account-wide |
connect:AssociateInstanceStorageConfig, connect:DisassociateInstanceStorageConfig, connect:DescribeInstanceStorageConfig, connect:UpdateInstanceStorageConfig, connect:ListInstanceStorageConfigs |
Storage configs | One call per storage_configs entry |
connect:CreateHoursOfOperation, connect:DeleteHoursOfOperation, connect:DescribeHoursOfOperation, connect:UpdateHoursOfOperation, connect:ListHoursOfOperations |
Hours of operation | β |
connect:CreateQueue, connect:DescribeQueue, connect:UpdateQueue*, connect:AssociateQueueQuickConnects, connect:DisassociateQueueQuickConnects, connect:ListQueues |
Queues | Queues are disabled, not deleted, by the API |
connect:CreateSecurityProfile, connect:DeleteSecurityProfile, connect:DescribeSecurityProfile, connect:UpdateSecurityProfile, connect:ListSecurityProfiles |
Security profiles | β |
connect:CreateRoutingProfile, connect:DescribeRoutingProfile, connect:UpdateRoutingProfile*, connect:ListRoutingProfiles, connect:ListRoutingProfileQueues |
Routing profiles | Routing profiles are disabled, not deleted |
connect:CreateContactFlow, connect:DeleteContactFlow, connect:DescribeContactFlow, connect:UpdateContactFlowContent, connect:UpdateContactFlowMetadata, connect:ListContactFlows |
Contact flows | β |
connect:CreateContactFlowModule, connect:DeleteContactFlowModule, connect:DescribeContactFlowModule, connect:UpdateContactFlowModuleContent, connect:UpdateContactFlowModuleMetadata, connect:ListContactFlowModules |
Contact flow modules | β |
connect:CreateQuickConnect, connect:DeleteQuickConnect, connect:DescribeQuickConnect, connect:UpdateQuickConnectConfig, connect:UpdateQuickConnectName, connect:ListQuickConnects |
Quick connects | β |
connect:UpdateUserHierarchyStructure, connect:DescribeUserHierarchyStructure |
User hierarchy structure | No delete API β see Architecture Notes |
connect:CreateUserHierarchyGroup, connect:DeleteUserHierarchyGroup, connect:DescribeUserHierarchyGroup, connect:UpdateUserHierarchyGroupName, connect:ListUserHierarchyGroups |
Hierarchy groups | β |
connect:CreateUser, connect:DeleteUser, connect:DescribeUser, connect:UpdateUserIdentityInfo, connect:UpdateUserPhoneConfig, connect:UpdateUserRoutingProfile, connect:UpdateUserSecurityProfiles, connect:UpdateUserHierarchy, connect:ListUsers |
Users/agents | β |
connect:ClaimPhoneNumber, connect:ReleasePhoneNumber, connect:DescribePhoneNumber, connect:UpdatePhoneNumber, connect:ListPhoneNumbersV2, connect:SearchAvailablePhoneNumbers |
Phone numbers | Shares a 30-day rolling rate-limit family with instance create/delete |
connect:AssociateContactFlow, connect:DescribePhoneNumber |
Phone number contact flow associations | β |
connect:AssociateLambdaFunction, connect:DisassociateLambdaFunction, connect:ListLambdaFunctions, lambda:AddPermission |
Lambda associations | Connect adds an internal invoke permission on association |
connect:AssociateBot, connect:DisassociateBot, connect:ListBots, lex:GetBot |
Lex (V1) bot associations | Lex V1 only β provider limitation |
connect:CreateVocabulary, connect:DeleteVocabulary, connect:DescribeVocabulary, connect:ListVocabularies |
Custom vocabularies | β |
connect:TagResource, connect:UntagResource, connect:ListTagsForResource |
Tagging | All taggable sub-resources |
ds:AuthorizeApplication, ds:DescribeDirectories |
Existing-directory identity management | Only when identity_management_type = "EXISTING_DIRECTORY" |
β οΈ Noiam:PassRoleis needed β Connect does not assume a caller-supplied execution role for any resource in this module's scope.
identity_management_typeanddirectory_idare effectively immutable. There is no in-place update path for either; changing them destroys and recreates the instance and everything under it. Get this decision right before the first apply.- 100 combined instance creations/deletions per 30 days, account-wide. Plan create/destroy cycles deliberately, especially in shared/sandbox accounts.
- Phone number claim/release shares a related 30-day rolling rate-limit family. Treat
phone_numberschanges with the same care as instance lifecycle. - Call recordings and chat transcripts are a direct compliance/audit concern. Always pair
CALL_RECORDINGS/CHAT_TRANSCRIPTSstorage configs with a customer-managed KMS key and a retention-appropriate S3 lifecycle policy β this module wires the encryption config but does not own the bucket's lifecycle rules. - The User Hierarchy Structure must exist before any hierarchy group β enforced in this module via an explicit
depends_on. - No service-linked role required β Connect provisions its own internal service role automatically (exposed as
service_role). - Service quotas for queues, routing profiles, users, and quick connects per instance β check current Amazon Connect service quotas before large rollouts; most are raisable via support case.
- Amazon Lex (V1) only for
aws_connect_bot_associationβ Lex V2 is not supported by anyaws_connect_*resource in the current provider.
terraform-aws-connect/
βββ providers.tf # required_providers (aws >= 6.0, < 7.0); no provider block
βββ variables.tf # instance identity β feature toggles β storage_configs β hours_of_operations
β # β queues β security_profiles β routing_profiles β users/user_passwords
β # β hierarchy structure/groups β contact_flows/modules β quick_connects
β # β phone_numbers/associations β lambda/bot/vocabulary β tags β timeouts
βββ main.tf # aws_connect_instance.this + 16 for_each child resources
βββ outputs.tf # id + arn + service_role/status + 13 child reference maps + tags_all
βββ README.md # this file
βββ SCOPE.md # in/out-of-scope, IAM permissions, prerequisites, gotchas
Smallest working call β a Connect-managed instance with no queues/users yet:
module "connect" {
source = "git::https://github.com/microsoftexpert/terraform-aws-connect?ref=v1.0.0"
instance_alias = "casey-member-contact-center"
identity_management_type = "CONNECT_MANAGED"
tags = {
Environment = "prod"
CostCenter = "1234"
}
}| Input | Type | Source module |
|---|---|---|
storage_configs[*].storage_config.s3_config.bucket_name |
string |
terraform-aws-s3-bucket |
storage_configs[*].storage_config.*.encryption_config.key_id |
string (KMS key ARN) |
terraform-aws-kms |
storage_configs[*].storage_config.kinesis_stream_config.stream_arn |
string |
terraform-aws-kinesis-stream |
storage_configs[*].storage_config.kinesis_firehose_config.firehose_arn |
string |
terraform-aws-kinesis-firehose |
directory_id |
string (d-xxxxxxxxxx) |
Directory Service (existing directory) |
lambda_function_associations[*].function_arn |
string |
terraform-aws-lambda |
| Output | Description | Consumed by |
|---|---|---|
id |
Connect instance id | Any hand-authored aws_connect_* resource outside this module |
arn |
Instance ARN β cross-resource reference type | IAM policy Resource statements scoping connect:*; phone number target_arn |
name |
instance_alias, or null |
operator tooling |
service_role |
Connect's own service-linked role ARN | audit |
status |
Instance lifecycle state | health checks |
created_time |
Instance creation timestamp | audit |
storage_configs |
Map: id, association_id, resource_type |
compliance/audit tooling |
hours_of_operations |
Map: id, hours_of_operation_id, arn, name, tags_all |
queue wiring outside this module |
queues |
Map: id, queue_id, arn, name, tags_all |
routing/quick-connect wiring |
security_profiles |
Map: id, security_profile_id, arn, organization_resource_id, name, tags_all |
user-provisioning tooling |
routing_profiles |
Map: id, routing_profile_id, arn, name, tags_all |
user-provisioning tooling |
contact_flows |
Map: id, contact_flow_id, arn, name, type, tags_all |
phone-number-association wiring, quick-connect config |
contact_flow_modules |
Map: id, contact_flow_module_id, arn, name, tags_all |
flow-authoring tooling |
quick_connects |
Map: id, quick_connect_id, arn, name, tags_all |
queue quick_connect_ids wiring (post-apply) |
users |
Map: id, user_id, arn, tags_all |
quick-connect user_config, reporting |
user_hierarchy_structure |
Materialized 5-level structure, or null | hierarchy-group tooling |
user_hierarchy_groups |
Map: id, hierarchy_group_id, arn, level_id, name, tags_all |
user provisioning |
phone_numbers |
Map: id, arn, phone_number, status, tags_all |
telephony documentation, phone-number-association wiring |
vocabularies |
Map: id, vocabulary_id, arn, state, failure_reason, last_modified_time, tags_all |
Contact Lens accuracy tooling |
tags_all |
All tags incl. provider default_tags |
governance/audit |
1 Β· Minimal Connect-managed instance
module "connect" {
source = "git::https://github.com/microsoftexpert/terraform-aws-connect?ref=v1.0.0"
instance_alias = "casey-sandbox-contact-center"
identity_management_type = "CONNECT_MANAGED"
}2 Β· Existing Active Directory identity management
module "connect" {
source = "git::https://github.com/microsoftexpert/terraform-aws-connect?ref=v1.0.0"
identity_management_type = "EXISTING_DIRECTORY"
directory_id = "d-1234567890" # from your Directory Service directory
}3 Β· Call recordings + chat transcripts with a customer-managed KMS key (compliance)
module "connect_kms" {
source = "git::https://github.com/microsoftexpert/terraform-aws-kms?ref=v1.0.0"
alias = "casey/connect-recordings"
}
module "connect_recordings_bucket" {
source = "git::https://github.com/microsoftexpert/terraform-aws-s3-bucket?ref=v1.0.0"
bucket = "casey-connect-call-recordings"
}
module "connect" {
source = "git::https://github.com/microsoftexpert/terraform-aws-connect?ref=v1.0.0"
instance_alias = "casey-member-contact-center"
identity_management_type = "CONNECT_MANAGED"
storage_configs = {
call_recordings = {
resource_type = "CALL_RECORDINGS"
storage_config = {
storage_type = "S3"
s3_config = {
bucket_name = module.connect_recordings_bucket.id
bucket_prefix = "call-recordings"
encryption_config = {
key_id = module.connect_kms.arn # CMK β auditable/revocable via CloudTrail
}
}
}
}
chat_transcripts = {
resource_type = "CHAT_TRANSCRIPTS"
storage_config = {
storage_type = "S3"
s3_config = {
bucket_name = module.connect_recordings_bucket.id
bucket_prefix = "chat-transcripts"
encryption_config = {
key_id = module.connect_kms.arn
}
}
}
}
}
}4 Β· Secure-by-default opt-out β disable contact flow logging
module "connect_dev" {
source = "git::https://github.com/microsoftexpert/terraform-aws-connect?ref=v1.0.0"
instance_alias = "casey-dev-sandbox"
identity_management_type = "CONNECT_MANAGED"
# OFF by default is contact_flow_logs_enabled = true β only disable for a
# documented, non-production exception.
contact_flow_logs_enabled = false
}5 Β· Hours of operation + queue + routing profile (a working call path)
module "connect" {
source = "git::https://github.com/microsoftexpert/terraform-aws-connect?ref=v1.0.0"
instance_alias = "casey-member-contact-center"
identity_management_type = "CONNECT_MANAGED"
hours_of_operations = {
business_hours = {
name = "Business Hours"
time_zone = "America/Chicago"
config = [
{ day = "MONDAY", start_time = { hours = 8, minutes = 0 }, end_time = { hours = 17, minutes = 0 } },
{ day = "TUESDAY", start_time = { hours = 8, minutes = 0 }, end_time = { hours = 17, minutes = 0 } },
{ day = "WEDNESDAY", start_time = { hours = 8, minutes = 0 }, end_time = { hours = 17, minutes = 0 } },
{ day = "THURSDAY", start_time = { hours = 8, minutes = 0 }, end_time = { hours = 17, minutes = 0 } },
{ day = "FRIDAY", start_time = { hours = 8, minutes = 0 }, end_time = { hours = 17, minutes = 0 } },
]
}
}
queues = {
support = {
name = "Member Support"
hours_of_operation_key = "business_hours"
}
}
routing_profiles = {
standard = {
name = "Standard"
description = "Standard voice + chat routing"
default_outbound_queue_key = "support"
media_concurrencies = [
{ channel = "VOICE", concurrency = 1 },
{ channel = "CHAT", concurrency = 3 },
]
queue_configs = [
{ queue_key = "support", channel = "VOICE", priority = 1, delay = 0 },
]
}
}
}6 Β· Security profiles + users (agents)
module "connect" {
source = "git::https://github.com/microsoftexpert/terraform-aws-connect?ref=v1.0.0"
instance_alias = "casey-member-contact-center"
identity_management_type = "CONNECT_MANAGED"
#... hours_of_operations / queues / routing_profiles as in example 5...
security_profiles = {
agent = {
name = "Agent"
permissions = ["BasicAgentAccess", "OutboundCallAccess"]
}
}
users = {
jdoe = {
name = "jdoe"
routing_profile_key = "standard"
security_profile_keys = ["agent"]
identity_info = {
first_name = "Jane"
last_name = "Doe"
email = "jdoe@casey-corp.com"
}
phone_config = {
phone_type = "SOFT_PHONE"
}
}
}
# Never hardcoded β supplied from a secrets manager or generated-password module
user_passwords = {
jdoe = var.jdoe_initial_password
}
}7 Β· 5-level user hierarchy structure + groups
module "connect" {
source = "git::https://github.com/microsoftexpert/terraform-aws-connect?ref=v1.0.0"
instance_alias = "casey-member-contact-center"
identity_management_type = "CONNECT_MANAGED"
user_hierarchy_structure = {
level_one = { name = "Region" }
level_two = { name = "Site" }
level_three = { name = "Team" }
}
user_hierarchy_groups = {
central_region = { name = "Central Region" }
omaha_site = { name = "Omaha", parent_group_key = "central_region" }
support_team = { name = "Support", parent_group_key = "omaha_site" }
}
}8 Β· Lambda contact-flow integration
module "connect" {
source = "git::https://github.com/microsoftexpert/terraform-aws-connect?ref=v1.0.0"
instance_alias = "casey-member-contact-center"
identity_management_type = "CONNECT_MANAGED"
lambda_function_associations = {
id_lookup = {
function_arn = module.member_id_lookup_lambda.arn # from terraform-aws-lambda, no version/alias qualifier
}
}
contact_flows = {
main_ivr = {
name = "Main IVR"
type = "CONTACT_FLOW"
content = jsonencode({
Version = "2019-10-30"
StartAction = "12345678-1234-1234-1234-123456789012"
Actions = []
})
}
}
}9 Β· Claim a phone number and bind it to a contact flow
module "connect" {
source = "git::https://github.com/microsoftexpert/terraform-aws-connect?ref=v1.0.0"
instance_alias = "casey-member-contact-center"
identity_management_type = "CONNECT_MANAGED"
contact_flows = {
main_ivr = {
name = "Main IVR"
content = jsonencode({ Version = "2019-10-30", StartAction = "...", Actions = [] })
}
}
phone_numbers = {
main_line = {
country_code = "US"
type = "DID"
}
}
phone_number_contact_flow_associations = {
main_line = { phone_number_key = "main_line", contact_flow_key = "main_ivr" }
}
}10 Β· Quick connects (transfer targets) β phone, queue, and user
module "connect" {
source = "git::https://github.com/microsoftexpert/terraform-aws-connect?ref=v1.0.0"
instance_alias = "casey-member-contact-center"
identity_management_type = "CONNECT_MANAGED"
#... queues / contact_flows / users as in prior examples...
quick_connects = {
supervisor_line = {
name = "Supervisor"
quick_connect_config = {
quick_connect_type = "PHONE_NUMBER"
phone_config = { phone_number = "+18005551234" }
}
}
transfer_to_support = {
name = "Transfer to Support"
quick_connect_config = {
quick_connect_type = "QUEUE"
queue_config = { queue_key = "support", contact_flow_key = "main_ivr" }
}
}
}
# Wire the quick connect onto a queue by its RAW ID (post-apply), not by
# module key β see Architecture Notes for why the key-based direction
# would create a Terraform graph cycle.
# queues.support.quick_connect_ids = [module.connect.quick_connects["supervisor_line"].quick_connect_id]
}11 Β· Custom vocabulary for Contact Lens transcription accuracy
module "connect" {
source = "git::https://github.com/microsoftexpert/terraform-aws-connect?ref=v1.0.0"
instance_alias = "casey-member-contact-center"
identity_management_type = "CONNECT_MANAGED"
vocabularies = {
casey_terms = {
language_code = "en-US"
content = "Phrase\tIPA\tSoundsLike\tDisplayAs\ncasey\t\t\tcasey\n"
}
}
}12 Β· Tags (merge with provider default_tags)
# Caller's provider block owns default_tags; the module never sets it.
provider "aws" {
default_tags { tags = { Owner = "platform", ManagedBy = "terraform" } }
}
module "connect" {
source = "git::https://github.com/microsoftexpert/terraform-aws-connect?ref=v1.0.0"
instance_alias = "casey-member-contact-center"
identity_management_type = "CONNECT_MANAGED"
tags = {
Environment = "prod" # resource tag β wins over default_tags on key conflict
DataClass = "npi"
}
}
# module.connect.tags_all == { Owner, ManagedBy, Environment, DataClass }13 Β· Import an existing instance
import {
to = module.connect.aws_connect_instance.this
id = "f1288a1f-6193-445a-b47e-af739b2"
}14 Β· SAML identity management
module "connect_saml" {
source = "git::https://github.com/microsoftexpert/terraform-aws-connect?ref=v1.0.0"
instance_alias = "casey-saml-contact-center"
identity_management_type = "SAML"
}15 Β· End-to-end composition β CMK recordings, hierarchy, agents, phone number, Lambda
module "connect_kms" {
source = "git::https://github.com/microsoftexpert/terraform-aws-kms?ref=v1.0.0"
alias = "casey/connect-recordings"
}
module "connect_recordings_bucket" {
source = "git::https://github.com/microsoftexpert/terraform-aws-s3-bucket?ref=v1.0.0"
bucket = "casey-connect-call-recordings"
}
module "connect" {
source = "git::https://github.com/microsoftexpert/terraform-aws-connect?ref=v1.0.0"
instance_alias = "casey-member-contact-center"
identity_management_type = "CONNECT_MANAGED"
storage_configs = {
call_recordings = {
resource_type = "CALL_RECORDINGS"
storage_config = {
storage_type = "S3"
s3_config = {
bucket_name = module.connect_recordings_bucket.id
bucket_prefix = "call-recordings"
encryption_config = { key_id = module.connect_kms.arn }
}
}
}
}
hours_of_operations = {
business_hours = {
name = "Business Hours"
time_zone = "America/Chicago"
config = [{ day = "MONDAY", start_time = { hours = 8, minutes = 0 }, end_time = { hours = 17, minutes = 0 } }]
}
}
queues = {
support = { name = "Member Support", hours_of_operation_key = "business_hours" }
}
security_profiles = {
agent = { name = "Agent", permissions = ["BasicAgentAccess", "OutboundCallAccess"] }
}
routing_profiles = {
standard = {
name = "Standard"
description = "Standard voice + chat routing"
default_outbound_queue_key = "support"
media_concurrencies = [{ channel = "VOICE", concurrency = 1 }]
queue_configs = [{ queue_key = "support", channel = "VOICE", priority = 1, delay = 0 }]
}
}
user_hierarchy_structure = {
level_one = { name = "Region" }
level_two = { name = "Site" }
}
user_hierarchy_groups = {
central = { name = "Central Region" }
}
users = {
jdoe = {
name = "jdoe"
routing_profile_key = "standard"
security_profile_keys = ["agent"]
hierarchy_group_key = "central"
identity_info = { first_name = "Jane", last_name = "Doe", email = "jdoe@casey-corp.com" }
phone_config = { phone_type = "SOFT_PHONE" }
}
}
user_passwords = { jdoe = var.jdoe_initial_password }
contact_flows = {
main_ivr = { name = "Main IVR", content = jsonencode({ Version = "2019-10-30", StartAction = "...", Actions = [] }) }
}
lambda_function_associations = {
id_lookup = { function_arn = module.member_id_lookup_lambda.arn }
}
phone_numbers = {
main_line = { country_code = "US", type = "DID" }
}
phone_number_contact_flow_associations = {
main_line = { phone_number_key = "main_line", contact_flow_key = "main_ivr" }
}
tags = { Environment = "prod", DataClass = "npi" }
}| Name | Type | Default | Description |
|---|---|---|---|
instance_alias |
string |
null |
Friendly instance name. Required unless directory_id is set. Effectively FORCE-NEW. |
identity_management_type |
string |
β required | SAML | CONNECT_MANAGED | EXISTING_DIRECTORY. Effectively FORCE-NEW. |
directory_id |
string |
null |
Existing Directory Service directory id. Required iff identity_management_type = "EXISTING_DIRECTORY". Effectively FORCE-NEW. |
inbound_calls_enabled / outbound_calls_enabled |
bool |
true |
Instance-level call direction toggles. |
contact_flow_logs_enabled |
bool |
true (secure default β overrides provider's false) |
Contact flow execution logs to CloudWatch. |
contact_lens_enabled |
bool |
true |
Contact Lens conversational analytics. |
auto_resolve_best_voices_enabled |
bool |
true |
Auto-resolve best TTS voices. |
early_media_enabled |
bool |
true |
Early media for outbound calls. |
multi_party_conference_enabled |
bool |
false |
Multi-party calls/conferencing. |
storage_configs |
map(object({...})) |
{} |
13 valid resource_type values; one of S3/Kinesis Stream/Firehose/Video Stream per entry. |
hours_of_operations |
map(object({...})) |
{} |
Business-hours schedules. |
queues |
map(object({...})) |
{} |
Contact queues; quick_connect_ids is raw IDs, not module keys (cycle avoidance). |
security_profiles |
map(object({...})) |
{} |
Permission sets. |
routing_profiles |
map(object({...})) |
{} |
Channel concurrency + queue routing. description is required. |
contact_flows |
map(object({...})) |
{} |
IVR/call flows; content or filename+content_hash. |
contact_flow_modules |
map(object({...})) |
{} |
Reusable flow modules (no type argument). |
quick_connects |
map(object({...})) |
{} |
One-click transfer targets: phone / queue / user. |
users |
map(object({...})) |
{} |
Agent accounts. security_profile_keys 1-10 entries. |
user_passwords |
map(string) |
{} |
Initial password per user key. sensitive = true. |
user_hierarchy_structure |
object({...}) |
null |
Up to 5 levels, top-down. |
user_hierarchy_groups |
map(object({...})) |
{} |
Hierarchy nodes; parent_group_key self-references. |
phone_numbers |
map(object({...})) |
{} |
Claimed DID/toll-free numbers. All fields FORCE-NEW. |
phone_number_contact_flow_associations |
map(object({...})) |
{} |
Binds a claimed number to an inbound flow. |
lambda_function_associations |
map(object({...})) |
{} |
Lambda functions invokable from contact flows. |
bot_associations |
map(object({...})) |
{} |
Lex (V1) bot associations only. |
vocabularies |
map(object({...})) |
{} |
Custom Contact Lens transcription vocabularies (map key = vocabulary name). |
tags |
map(string) |
{} |
Tags for every taggable resource in this module. |
timeouts |
object({ create, delete }) |
{} |
Instance operation timeouts (no update timeout in the schema). |
See variables.tf for full heredoc schemas and validation rules.
| Name | Description |
|---|---|
id |
Connect instance id. |
arn |
Instance ARN (cross-resource reference type). |
name |
instance_alias, or null. |
service_role |
Connect's own service-linked role ARN. |
status |
Instance lifecycle state. |
created_time |
RFC3339 instance creation timestamp. |
storage_configs |
Map: id, association_id, resource_type. |
hours_of_operations |
Map keyed as var.hours_of_operations. |
queues |
Map keyed as var.queues. |
security_profiles |
Map keyed as var.security_profiles. |
routing_profiles |
Map keyed as var.routing_profiles. |
contact_flows |
Map keyed as var.contact_flows. |
contact_flow_modules |
Map keyed as var.contact_flow_modules. |
quick_connects |
Map keyed as var.quick_connects. |
users |
Map keyed as var.users. Never includes the password. |
user_hierarchy_structure |
Materialized structure, or null. |
user_hierarchy_groups |
Map keyed as var.user_hierarchy_groups. |
phone_numbers |
Map keyed as var.phone_numbers. |
vocabularies |
Map keyed as var.vocabularies. |
tags_all |
All tags incl. provider default_tags (instance only computed set; see Architecture Notes for per-resource tags_all). |
βΉοΈ
instance_storage_config,user_hierarchy_structure(the resource itself),phone_number_contact_flow_association,lambda_function_association, andbot_associationhave notags_allβ not taggable in the current provider schema.
- ID/ARN formats: the instance
idis a bare UUID;arnisarn:aws:connect:<region>:<account>:instance/<id>. Most child resources'idis a compositeinstance_id:resource_id(colon-separated) β the module surfaces the unqualified*_idattribute (e.g.queue_id,routing_profile_id) for cross-references, since that is what the provider's own nested-block arguments expect (e.g.queue_configs.queue_id). - Force-new / immutable fields:
identity_management_typeanddirectory_idhave no update API β changing either destroys and recreates the entire instance.country_code,type,prefix, anddescriptionare FORCE-NEW onaws_connect_phone_number.typeis FORCE-NEW onaws_connect_contact_flow(defaults toCONTACT_FLOW);aws_connect_contact_flow_modulehas notypeargument at all. - The queue <-> quick-connect Terraform cycle. Amazon Connect allows a queue to list quick connects (
quick_connect_ids) and a quick connect to reference a queue (quick_connect_config.queue_config) simultaneously, but Terraform's resource-level dependency graph cannot support both directions being key-resolved in the same module β it fails with a bareCycle:error regardless of whether the specific IDs involved actually loop. This module takesqueues[*].quick_connect_idsas raw ID strings (notvar.quick_connectskeys) to keep both real-world directions usable, at the cost of the queue-side wiring needing a second apply or an externally-managed quick connect ID. tagsβtags_allβdefault_tags: every in-scope resource exceptinstance_storage_config,user_hierarchy_structure,phone_number_contact_flow_association,lambda_function_association, andbot_associationsupportstags/tags_all.var.tagsflows uniformly to every taggable resource and merges with providerdefault_tags(resource tags win on key conflict).media_concurrenciesandqueue_configsonaws_connect_routing_profileare repeatable blocks, not list arguments β rendered viadynamicblocks.descriptionis REQUIRED on this resource (unlike most other Connect resources).- Eventual consistency / no delete API:
aws_connect_user_hierarchy_structurehas no delete operation β removing it from configuration only stops Terraform from managing it, it does not revert the stored hierarchy.aws_connect_queueandaws_connect_routing_profileare disabled rather than truly deleted by the underlying API; recreating under the samenamecan transiently fail with a duplicate-name error. - Destroy ordering: every child resource references the instance's
id/arn, so Terraform destroys all queues/users/phone numbers/etc. before the instance. Destroying the instance can still fail if Connect's own async cleanup (e.g. an in-progress phone-number claim) hasn't settled β retry. - us-east-1 globals: N/A β Amazon Connect has no CloudFront/WAFv2/ACM-style global-resource coupling.
Secure-by-default posture and every opt-out, explicitly:
| Posture | Default | Opt-out |
|---|---|---|
| Contact flow execution logging | contact_flow_logs_enabled = true (overrides the provider's own false) |
false β only with a documented exception |
| Contact Lens analytics | contact_lens_enabled = true |
false |
| Call-recording / chat-transcript encryption | storage_configs[*].storage_config.s3_config.encryption_config is a first-class field; always supply a CMK from terraform-aws-kms for PII-bearing streams |
omitting encryption_config falls back to S3-default encryption β discouraged for PII |
| Multi-party conferencing | multi_party_conference_enabled = false |
true for a documented conferencing use case |
| User passwords | No hardcoded default; supplied per-key via a dedicated sensitive = true map |
omit the key entirely to leave a password unmanaged |
| Phone number claiming | Always claimed to this module's own instance via target_arn |
claim to a different instance/traffic distribution group outside this module |
Other principles:
- One composite, one keystone. The instance owns every resource meaningless without it β 16 supporting resource types, all
for_each, nevercount. - 13 storage resource types modeled explicitly, not the commonly-assumed 6 β the single most compliance-relevant setting in the module.
- Primary outputs
id+arn, plusservice_role,status,created_time, and 13 child reference maps. - Secrets isolated, not nested.
passwordlives in its ownsensitive = truemap, mirroringterraform-aws-workmail. - A resolved cycle, not a removed feature. Both queue-quick-connect wiring directions stay available; the module picks raw IDs on one side rather than dropping either capability.
# Validate without backend or credentials
terraform init -backend=false
terraform validate
terraform fmt -check
plan/applyrequire valid AWS credentials (profile / SSO / OIDC) resolved through the standard provider chain, plus the IAM actions listed above, and a Region where Amazon Connect is available.
β οΈ Always pin the module source with?ref=v1.0.0β never a branch.
terraform init -backend=false && terraform validateβ schema + reference integrity (17 resource types).terraform fmt -checkβ canonical formatting.terraform planagainst a sandbox account to confirm the instance, queues, routing profiles, users, and phone numbers materialize as expected β watch the 100-instance-per-30-days account limit.- Assert
module.<name>.arn,storage_configs, andtags_allin your root-module test harness. For user paths, assertuser_passwordsvalues never appear in plan output.
module.connect.aws_connect_instance.this: Creation complete after 2m14s [id=f1288a1f-6193-445a-b47e-af739b2]
module.connect.aws_connect_hours_of_operation.this["business_hours"]: Creation complete
module.connect.aws_connect_queue.this["support"]: Creation complete
module.connect.aws_connect_routing_profile.this["standard"]: Creation complete
Outputs:
arn = "arn:aws:connect:us-east-1:123456789012:instance/f1288a1f-6193-445a-b47e-af739b2"
id = "f1288a1f-6193-445a-b47e-af739b2"
service_role = "arn:aws:iam::123456789012:role/aws-service-role/connect.amazonaws.com/..."
status = "ACTIVE"
tags_all = { "DataClass" = "npi", "Environment" = "prod" }
| Symptom | Likely cause | Fix |
|---|---|---|
Error: Cycle: aws_connect_quick_connect.this, aws_connect_queue.this,... |
A queue and a quick connect (directly or via routing profile / user) reference each other | Use raw IDs for queues[*].quick_connect_ids (this module's design); never rewire it to look up var.quick_connects by key |
terraform apply hangs for several minutes on the instance |
Normal β instance creation is asynchronous | Wait; raise timeouts.create if it exceeds 5m |
LimitExceededException on instance create/delete |
Account-wide 100-per-30-days limit reached | Wait out the rolling window; avoid churn-heavy CI plan/destroy cycles against a shared account |
DuplicateResourceException recreating a queue/routing profile under the same name |
The API disables rather than deletes; the name is still reserved | Wait for AWS's own cleanup, or choose a new name |
Storage config conflicts for the same resource_type |
Only one storage_config entry is allowed per resource_type per instance |
Ensure storage_configs has at most one map entry per resource_type value |
password never appears in terraform show/plan |
Expected β user_passwords is sensitive = true |
Not an error |
| Tag drift on every plan | A tag also set by provider default_tags with a different value |
Let resource tags win, or remove the overlap from default_tags |
InvalidParameterException on aws_connect_user_hierarchy_group |
Structure not yet created, or parent_group_key points at a nonexistent key |
Confirm var.user_hierarchy_structure is set; check the parent key spelling |
| Credentials/region errors | Standard AWS provider chain not resolving, or Region doesn't support Connect | Confirm AWS_PROFILE/SSO/OIDC and that the target Region is one of Connect's supported Regions |
| Phone number claim fails with rate-limit error | Shares the 30-day rolling window with instance create/delete | Space out phone_numbers changes; avoid bulk claim/release in one apply |
- Amazon Connect Administrator Guide
- Amazon Connect Flow language
- Amazon Connect service quotas
- Terraform:
aws_connect_instanceΒ·aws_connect_instance_storage_configΒ·aws_connect_queueΒ·aws_connect_routing_profileΒ·aws_connect_user - Sibling modules:
terraform-aws-kms,terraform-aws-s3-bucket,terraform-aws-lambda,terraform-aws-kinesis-stream,terraform-aws-kinesis-firehose - Module internals:
SCOPE.md
π§‘ "Infrastructure as Code should be standardized, consistent, and secure."