Create AWS Systems Manager Contacts Plans

The aws:ssmcontacts/plan:Plan resource, part of the Pulumi AWS provider, defines escalation plans that control when and how AWS Systems Manager Incident Manager engages contacts during incidents. This guide focuses on two capabilities: single-stage engagement timing and multi-stage escalation with parallel targets.

Plans reference existing SSM Contacts and contact channels by ARN. The examples are intentionally small. Combine them with your own contact definitions and notification channels.

Define a plan for a contact resource

Most incident response workflows start by creating a contact and defining when to engage them.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const contact = new aws.ssmcontacts.Contact("contact", {
    alias: "alias",
    type: "PERSONAL",
});
const plan = new aws.ssmcontacts.Plan("plan", {
    contactId: contact.arn,
    stages: [{
        durationInMinutes: 1,
    }],
});
import pulumi
import pulumi_aws as aws

contact = aws.ssmcontacts.Contact("contact",
    alias="alias",
    type="PERSONAL")
plan = aws.ssmcontacts.Plan("plan",
    contact_id=contact.arn,
    stages=[{
        "duration_in_minutes": 1,
    }])
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ssmcontacts"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		contact, err := ssmcontacts.NewContact(ctx, "contact", &ssmcontacts.ContactArgs{
			Alias: pulumi.String("alias"),
			Type:  pulumi.String("PERSONAL"),
		})
		if err != nil {
			return err
		}
		_, err = ssmcontacts.NewPlan(ctx, "plan", &ssmcontacts.PlanArgs{
			ContactId: contact.Arn,
			Stages: ssmcontacts.PlanStageArray{
				&ssmcontacts.PlanStageArgs{
					DurationInMinutes: pulumi.Int(1),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var contact = new Aws.SsmContacts.Contact("contact", new()
    {
        Alias = "alias",
        Type = "PERSONAL",
    });

    var plan = new Aws.SsmContacts.Plan("plan", new()
    {
        ContactId = contact.Arn,
        Stages = new[]
        {
            new Aws.SsmContacts.Inputs.PlanStageArgs
            {
                DurationInMinutes = 1,
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ssmcontacts.Contact;
import com.pulumi.aws.ssmcontacts.ContactArgs;
import com.pulumi.aws.ssmcontacts.Plan;
import com.pulumi.aws.ssmcontacts.PlanArgs;
import com.pulumi.aws.ssmcontacts.inputs.PlanStageArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var contact = new Contact("contact", ContactArgs.builder()
            .alias("alias")
            .type("PERSONAL")
            .build());

        var plan = new Plan("plan", PlanArgs.builder()
            .contactId(contact.arn())
            .stages(PlanStageArgs.builder()
                .durationInMinutes(1)
                .build())
            .build());

    }
}
resources:
  contact:
    type: aws:ssmcontacts:Contact
    properties:
      alias: alias
      type: PERSONAL
  plan:
    type: aws:ssmcontacts:Plan
    properties:
      contactId: ${contact.arn}
      stages:
        - durationInMinutes: 1

The contactId property links the plan to a specific contact. The stages array defines escalation timing; each stage’s durationInMinutes controls how long to wait before moving to the next stage. Here, the plan engages the contact immediately (1 minute after incident creation).

Build multi-stage escalation with parallel targets

Complex incident response requires escalating through multiple contacts and channels, with some marked as essential and others as optional backups.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const escalationPlan = new aws.ssmcontacts.Contact("escalation_plan", {
    alias: "escalation-plan-alias",
    type: "ESCALATION",
});
const contactOne = new aws.ssmcontacts.Contact("contact_one", {
    alias: "alias",
    type: "PERSONAL",
});
const contactTwo = new aws.ssmcontacts.Contact("contact_two", {
    alias: "alias",
    type: "PERSONAL",
});
const test = new aws.ssmcontacts.Plan("test", {
    contactId: escalationPlan.arn,
    stages: [{
        durationInMinutes: 0,
        targets: [
            {
                contactTargetInfo: {
                    isEssential: false,
                    contactId: contactOne.arn,
                },
            },
            {
                contactTargetInfo: {
                    isEssential: true,
                    contactId: contactTwo.arn,
                },
            },
            {
                channelTargetInfo: {
                    retryIntervalInMinutes: 2,
                    contactChannelId: channel.arn,
                },
            },
        ],
    }],
});
import pulumi
import pulumi_aws as aws

escalation_plan = aws.ssmcontacts.Contact("escalation_plan",
    alias="escalation-plan-alias",
    type="ESCALATION")
contact_one = aws.ssmcontacts.Contact("contact_one",
    alias="alias",
    type="PERSONAL")
contact_two = aws.ssmcontacts.Contact("contact_two",
    alias="alias",
    type="PERSONAL")
test = aws.ssmcontacts.Plan("test",
    contact_id=escalation_plan.arn,
    stages=[{
        "duration_in_minutes": 0,
        "targets": [
            {
                "contact_target_info": {
                    "is_essential": False,
                    "contact_id": contact_one.arn,
                },
            },
            {
                "contact_target_info": {
                    "is_essential": True,
                    "contact_id": contact_two.arn,
                },
            },
            {
                "channel_target_info": {
                    "retry_interval_in_minutes": 2,
                    "contact_channel_id": channel["arn"],
                },
            },
        ],
    }])
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ssmcontacts"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		escalationPlan, err := ssmcontacts.NewContact(ctx, "escalation_plan", &ssmcontacts.ContactArgs{
			Alias: pulumi.String("escalation-plan-alias"),
			Type:  pulumi.String("ESCALATION"),
		})
		if err != nil {
			return err
		}
		contactOne, err := ssmcontacts.NewContact(ctx, "contact_one", &ssmcontacts.ContactArgs{
			Alias: pulumi.String("alias"),
			Type:  pulumi.String("PERSONAL"),
		})
		if err != nil {
			return err
		}
		contactTwo, err := ssmcontacts.NewContact(ctx, "contact_two", &ssmcontacts.ContactArgs{
			Alias: pulumi.String("alias"),
			Type:  pulumi.String("PERSONAL"),
		})
		if err != nil {
			return err
		}
		_, err = ssmcontacts.NewPlan(ctx, "test", &ssmcontacts.PlanArgs{
			ContactId: escalationPlan.Arn,
			Stages: ssmcontacts.PlanStageArray{
				&ssmcontacts.PlanStageArgs{
					DurationInMinutes: pulumi.Int(0),
					Targets: ssmcontacts.PlanStageTargetArray{
						&ssmcontacts.PlanStageTargetArgs{
							ContactTargetInfo: &ssmcontacts.PlanStageTargetContactTargetInfoArgs{
								IsEssential: pulumi.Bool(false),
								ContactId:   contactOne.Arn,
							},
						},
						&ssmcontacts.PlanStageTargetArgs{
							ContactTargetInfo: &ssmcontacts.PlanStageTargetContactTargetInfoArgs{
								IsEssential: pulumi.Bool(true),
								ContactId:   contactTwo.Arn,
							},
						},
						&ssmcontacts.PlanStageTargetArgs{
							ChannelTargetInfo: &ssmcontacts.PlanStageTargetChannelTargetInfoArgs{
								RetryIntervalInMinutes: pulumi.Int(2),
								ContactChannelId:       pulumi.Any(channel.Arn),
							},
						},
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var escalationPlan = new Aws.SsmContacts.Contact("escalation_plan", new()
    {
        Alias = "escalation-plan-alias",
        Type = "ESCALATION",
    });

    var contactOne = new Aws.SsmContacts.Contact("contact_one", new()
    {
        Alias = "alias",
        Type = "PERSONAL",
    });

    var contactTwo = new Aws.SsmContacts.Contact("contact_two", new()
    {
        Alias = "alias",
        Type = "PERSONAL",
    });

    var test = new Aws.SsmContacts.Plan("test", new()
    {
        ContactId = escalationPlan.Arn,
        Stages = new[]
        {
            new Aws.SsmContacts.Inputs.PlanStageArgs
            {
                DurationInMinutes = 0,
                Targets = new[]
                {
                    new Aws.SsmContacts.Inputs.PlanStageTargetArgs
                    {
                        ContactTargetInfo = new Aws.SsmContacts.Inputs.PlanStageTargetContactTargetInfoArgs
                        {
                            IsEssential = false,
                            ContactId = contactOne.Arn,
                        },
                    },
                    new Aws.SsmContacts.Inputs.PlanStageTargetArgs
                    {
                        ContactTargetInfo = new Aws.SsmContacts.Inputs.PlanStageTargetContactTargetInfoArgs
                        {
                            IsEssential = true,
                            ContactId = contactTwo.Arn,
                        },
                    },
                    new Aws.SsmContacts.Inputs.PlanStageTargetArgs
                    {
                        ChannelTargetInfo = new Aws.SsmContacts.Inputs.PlanStageTargetChannelTargetInfoArgs
                        {
                            RetryIntervalInMinutes = 2,
                            ContactChannelId = channel.Arn,
                        },
                    },
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ssmcontacts.Contact;
import com.pulumi.aws.ssmcontacts.ContactArgs;
import com.pulumi.aws.ssmcontacts.Plan;
import com.pulumi.aws.ssmcontacts.PlanArgs;
import com.pulumi.aws.ssmcontacts.inputs.PlanStageArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var escalationPlan = new Contact("escalationPlan", ContactArgs.builder()
            .alias("escalation-plan-alias")
            .type("ESCALATION")
            .build());

        var contactOne = new Contact("contactOne", ContactArgs.builder()
            .alias("alias")
            .type("PERSONAL")
            .build());

        var contactTwo = new Contact("contactTwo", ContactArgs.builder()
            .alias("alias")
            .type("PERSONAL")
            .build());

        var test = new Plan("test", PlanArgs.builder()
            .contactId(escalationPlan.arn())
            .stages(PlanStageArgs.builder()
                .durationInMinutes(0)
                .targets(                
                    PlanStageTargetArgs.builder()
                        .contactTargetInfo(PlanStageTargetContactTargetInfoArgs.builder()
                            .isEssential(false)
                            .contactId(contactOne.arn())
                            .build())
                        .build(),
                    PlanStageTargetArgs.builder()
                        .contactTargetInfo(PlanStageTargetContactTargetInfoArgs.builder()
                            .isEssential(true)
                            .contactId(contactTwo.arn())
                            .build())
                        .build(),
                    PlanStageTargetArgs.builder()
                        .channelTargetInfo(PlanStageTargetChannelTargetInfoArgs.builder()
                            .retryIntervalInMinutes(2)
                            .contactChannelId(channel.arn())
                            .build())
                        .build())
                .build())
            .build());

    }
}
resources:
  escalationPlan:
    type: aws:ssmcontacts:Contact
    name: escalation_plan
    properties:
      alias: escalation-plan-alias
      type: ESCALATION
  contactOne:
    type: aws:ssmcontacts:Contact
    name: contact_one
    properties:
      alias: alias
      type: PERSONAL
  contactTwo:
    type: aws:ssmcontacts:Contact
    name: contact_two
    properties:
      alias: alias
      type: PERSONAL
  test:
    type: aws:ssmcontacts:Plan
    properties:
      contactId: ${escalationPlan.arn}
      stages:
        - durationInMinutes: 0
          targets:
            - contactTargetInfo:
                isEssential: false
                contactId: ${contactOne.arn}
            - contactTargetInfo:
                isEssential: true
                contactId: ${contactTwo.arn}
            - channelTargetInfo:
                retryIntervalInMinutes: 2
                contactChannelId: ${channel.arn}

Each stage can contain multiple targets that are engaged in parallel. The contactTargetInfo block specifies a contact to engage; isEssential marks whether engagement must succeed before proceeding. The channelTargetInfo block specifies a notification channel with retryIntervalInMinutes controlling how often to retry failed deliveries. Setting durationInMinutes to 0 means the stage engages immediately.

Beyond these examples

These snippets focus on specific plan-level features: single-stage and multi-stage escalation, parallel target engagement, and essential vs optional contacts. They’re intentionally minimal rather than full incident response workflows.

The examples reference pre-existing infrastructure such as SSM Contacts (referenced by ARN) and contact channels for notifications. They focus on configuring the plan rather than provisioning the entire incident management system.

To keep things focused, common plan patterns are omitted, including:

  • Multiple escalation stages with different durations
  • Channel-only engagement (without contact targets)
  • Complex retry strategies across stages
  • Integration with incident management workflows

These omissions are intentional: the goal is to illustrate how each plan feature is wired, not provide drop-in incident response modules. See the SSM Contacts Plan resource reference for all available configuration options.

Let's create AWS Systems Manager Contacts Plans

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Contact Configuration
Can I change the contactId after creating the plan?
No, contactId is immutable. Changing it will force resource replacement, so plan carefully when setting this value.
What's the difference between PERSONAL and ESCALATION contact types?
PERSONAL contacts represent individual responders, while ESCALATION contacts define multi-stage escalation plans that can route to multiple contacts.
How do I reference a contact created in the same Pulumi program?
Use the contact’s arn output property as the contactId, like contactId: contact.arn.
Escalation Stages & Timing
What does durationInMinutes: 0 mean?
Setting durationInMinutes to 0 triggers immediate escalation to that stage without waiting.
What's the difference between isEssential true and false?
The isEssential flag on contactTargetInfo determines whether the contact must acknowledge before proceeding to the next stage.
Targets & Channels
Can I use both contact and channel targets in the same stage?
Yes, a stage can include multiple targets with both contactTargetInfo and channelTargetInfo in the same targets array.

Using a different cloud?

Explore integration guides for other cloud providers: