Repository: honeycombio/terraform-provider-honeycombio
Pull request #386 (2023-11-07 17:09:37)

Add support for 'msteams' recipients
Adds supports for `msteams` recipients.

Also fixes a "sneaky bug" where a previous refactor would allow `marker` type recipients on Burn Alerts during the validation stage (the API still would reject them).

---
- To see the specific tasks where the Asana app for GitHub is being used, see below:
  - https://app.asana.com/0/0/1205887299919797


Patch:
diff --git a/client/recipient.go b/client/recipient.go
index c3aa622..0e4e04b 100644
--- a/client/recipient.go
+++ b/client/recipient.go
@@ -61,9 +61,10 @@ type RecipientDetails struct {
 	PDIntegrationName string `json:"pagerduty_integration_name,omitempty"`
 	// slack
 	SlackChannel string `json:"slack_channel,omitempty"`
-	// webhook
-	WebhookName   string `json:"webhook_name,omitempty"`
-	WebhookURL    string `json:"webhook_url,omitempty"`
+	// webhook or msteams
+	WebhookName string `json:"webhook_name,omitempty"`
+	WebhookURL  string `json:"webhook_url,omitempty"`
+	// webhook only
 	WebhookSecret string `json:"webhook_secret,omitempty"`
 }
 
@@ -81,6 +82,7 @@ const (
 	RecipientTypeSlack     RecipientType = "slack"
 	RecipientTypeWebhook   RecipientType = "webhook"
 	RecipientTypeMarker    RecipientType = "marker"
+	RecipientTypeMSTeams   RecipientType = "msteams"
 )
 
 // PagerDutySeverity holds all the possible PD Severity types
@@ -94,24 +96,21 @@ const (
 	PDDefaultSeverity                    = PDSeverityCRITICAL
 )
 
-// TriggerRecipientTypes returns a list of recipient types compatible with Triggers
+// TriggerRecipientTypes returns a list of recipient types compatible with Triggers.
+// Triggers are a special case as 'Marker' recipients are supported in addition to
+// usual types.
 func TriggerRecipientTypes() []RecipientType {
-	return []RecipientType{
-		RecipientTypeEmail,
-		RecipientTypePagerDuty,
-		RecipientTypeSlack,
-		RecipientTypeWebhook,
-		RecipientTypeMarker,
-	}
+	return append(RecipientTypes(), RecipientTypeMarker)
 }
 
-// BurnAlertRecipientTypes returns a list of recipient types compatible with Burn Alerts
-func BurnAlertRecipientTypes() []RecipientType {
+// RecipientTypes returns all supported Recipient types
+func RecipientTypes() []RecipientType {
 	return []RecipientType{
 		RecipientTypeEmail,
 		RecipientTypePagerDuty,
 		RecipientTypeSlack,
 		RecipientTypeWebhook,
+		RecipientTypeMSTeams,
 	}
 }
 
diff --git a/docs/data-sources/recipient.md b/docs/data-sources/recipient.md
index e6c2acf..a739c99 100644
--- a/docs/data-sources/recipient.md
+++ b/docs/data-sources/recipient.md
@@ -54,7 +54,7 @@ resource "honeycombio_trigger" "example" {
 
 The following arguments are supported:
 
-* `type` - (Required) The type of recipient, allowed types are `email`, `pagerduty`, `slack` and `webhook`.
+* `type` - (Required) The type of recipient, allowed types are `email`, `pagerduty`, `msteams`, `slack` and `webhook`.
 * `dataset` - (Optional) Deprecated: recipients are now a Team-level construct. Any provided value will be ignored.
 * `detail_filter` - (Optional) a block to further filter recipients as described below.
 * `target` - (Optional) Deprecated: use `detail_filter` instead. The target of the recipient, this has another meaning depending on the type of recipient (see the table below).
@@ -63,6 +63,7 @@ Type      | Target
 ----------|-------------------------
 email     | an email address
 marker    | name of the marker
+msteams   | name of the integration
 pagerduty | _N/A_
 slack     | name of the channel
 webhook   | name of the webhook
@@ -82,8 +83,8 @@ In addition to all arguments above, the following attributes are exported:
 * `id` - ID of the recipient.
 * `address` - The email recipient's address -- if of type `email`.
 * `channel` - The Slack recipient's channel -- if of type `slack`.
-* `name` - The webhook recipient's name -- if of type `webhook`.
+* `name` - The webhook recipient's name -- if of type `webhook` or `msteams`.
 * `secret` - (Sensitive) The webhook recipient's secret -- if of type `webhook`.
-* `url` - The webhook recipient's URL - if of type `webhook`.
+* `url` - The webhook recipient's URL - if of type `webhook` or `msteams`.
 * `integration_key` - (Sensitive) The PagerDuty recipient's integration key -- if of type `pagerduty`.
 * `integration_name` - The PagerDuty recipient's inregration name -- if of type `pagerduty`.
diff --git a/docs/data-sources/recipients.md b/docs/data-sources/recipients.md
index 0dedde8..46a387b 100644
--- a/docs/data-sources/recipients.md
+++ b/docs/data-sources/recipients.md
@@ -25,7 +25,7 @@ data "honeycombio_recipients" "example-dot-com" {
 
 The following arguments are supported:
 
-* `type` - (Optional) The type of recipient, allowed types are `email`, `pagerduty`, `slack` and `webhook`.
+* `type` - (Optional) The type of recipient, allowed types are `email`, `pagerduty`, `msteams`, `slack` and `webhook`.
 * `detail_filter` - (Optional) a block to further filter recipients as described below. `name` must be set when providing a filter.
 
 To further filter the recipient results, a `detail_filter` block can be provided which accepts the following arguments:
diff --git a/docs/resources/burn_alert.md b/docs/resources/burn_alert.md
index ca787d0..9ecc2fa 100644
--- a/docs/resources/burn_alert.md
+++ b/docs/resources/burn_alert.md
@@ -79,7 +79,7 @@ The following arguments are supported:
 
 Each burn alert configuration may have one or more `recipient` blocks, which each accept the following arguments. A recipient block can either refer to an existing recipient (a recipient that is already present in another burn alert or trigger) or a new recipient. When specifying an existing recipient, only `id` may be set. If you pass in a recipient without its ID and only include the type and target, Honeycomb will make a best effort to match to an existing recipient. To retrieve the ID of an existing recipient, refer to the [`honeycombio_recipient`](../data-sources/recipient.md) data source.
 
-* `type` - (Optional) The type of the recipient, allowed types are `email`, `pagerduty`, `slack` and `webhook`. Should not be used in combination with `id`.
+* `type` - (Optional) The type of the recipient, allowed types are `email`, `pagerduty`, `msteams`, `slack` and `webhook`. Should not be used in combination with `id`.
 * `target` - (Optional) Target of the recipient, this has another meaning depending on the type of recipient (see the table below). Should not be used in combination with `id`.
 * `id` - (Optional) The ID of an already existing recipient. Should not be used in combination with `type` and `target`.
 * `notification_details` - (Optional) a block of additional details to send along with the notification. The only supported option currently is `pagerduty_severity` which has a default value of `critical` but can be set to one of `info`, `warning`, `error`, or `critical` and must be used in combination with a PagerDuty recipient.
diff --git a/docs/resources/msteams_recipient.md b/docs/resources/msteams_recipient.md
new file mode 100644
index 0000000..7655596
--- /dev/null
+++ b/docs/resources/msteams_recipient.md
@@ -0,0 +1,33 @@
+# Resource: honeycombio_msteams_recipient
+
+`honeycombio_msteams_recipient` allows you to define and manage an MSTeams recipient that can be used by Triggers or BurnAlerts notifications.
+
+## Example Usage
+
+```hcl
+resource "honeycombio_msteams_recipient" "prod" {
+  name = "Production Alerts"
+  url  = "https://mycorp.webhook.office.com/webhookb2/abcd12345"
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `name` - (Required) The name of the MS Teams Integration to create.
+* `url` - (Required) The Incoming Webhook URL to send the notification to.
+
+## Attribute Reference
+
+In addition to all arguments above, the following attributes are exported:
+
+* `id` - The ID of the recipient.
+
+## Import
+
+MSTeams Recipients can be imported by their ID, e.g.
+
+```
+$ terraform import honeycombio_msteams_recipient.my_recipient nx2zsefB1cX
+```
diff --git a/docs/resources/trigger.md b/docs/resources/trigger.md
index adc1289..6f96b59 100644
--- a/docs/resources/trigger.md
+++ b/docs/resources/trigger.md
@@ -166,7 +166,7 @@ Each trigger configuration may provide an `evaluation_schedule` block, which acc
 
 Each trigger configuration may have zero or more `recipient` blocks, which each accept the following arguments. A trigger recipient block can either refer to an existing recipient (a recipient that is already present in another trigger) or a new recipient. When specifying an existing recipient, only `id` may be set. If you pass in a recipient without its ID and only include the type and target, Honeycomb will make a best effort to match to an existing recipient. To retrieve the ID of an existing recipient, refer to the [`honeycombio_recipient`](../data-sources/recipient.md) data source.
 
-* `type` - (Optional) The type of the trigger recipient, allowed types are `email`, `marker`, `pagerduty`, `slack` and `webhook`.
+* `type` - (Optional) The type of the trigger recipient, allowed types are `email`, `marker`, `msteams`, `pagerduty`, `slack` and `webhook`.
 Cannot not be used in combination with `id`.
 * `target` - (Optional) Target of the trigger recipient, this has another meaning depending on the type of recipient (see the table below).
 Cannot not be used in combination with `id`.
diff --git a/honeycombio/data_source_recipient.go b/honeycombio/data_source_recipient.go
index ea4c97d..d6c4cfb 100644
--- a/honeycombio/data_source_recipient.go
+++ b/honeycombio/data_source_recipient.go
@@ -7,6 +7,7 @@ import (
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
 	honeycombio "github.com/honeycombio/terraform-provider-honeycombio/client"
+	"github.com/honeycombio/terraform-provider-honeycombio/internal/helper"
 )
 
 func dataSourceHoneycombioRecipient() *schema.Resource {
@@ -33,8 +34,8 @@ If you want to match multiple recipients, use the 'honeycombio_recipients' data
 			"type": {
 				Type:         schema.TypeString,
 				Required:     true,
-				Description:  "The type of recipient, allowed types are `email`, `pagerduty`, `slack` and `webhook`.",
-				ValidateFunc: validation.StringInSlice([]string{"email", "pagerduty", "slack", "webhook"}, false),
+				Description:  "The type of recipient.",
+				ValidateFunc: validation.StringInSlice(helper.AsStringSlice(honeycombio.RecipientTypes()), false),
 			},
 			"target": {
 				Type:          schema.TypeString,
@@ -95,7 +96,7 @@ If you want to match multiple recipients, use the 'honeycombio_recipients' data
 				Computed:    true,
 				Required:    false,
 				Optional:    false,
-				Description: "The webhook recipient's name -- if of type `webhook`",
+				Description: "The webhook recipient's name -- if of type `webhook` or `msteams`",
 			},
 			"secret": {
 				Type:        schema.TypeString,
@@ -110,7 +111,7 @@ If you want to match multiple recipients, use the 'honeycombio_recipients' data
 				Computed:    true,
 				Required:    false,
 				Optional:    false,
-				Description: "The webhook recipient's URL -- if of type `webhook`",
+				Description: "The webhook recipient's URL -- if of type `webhook` or `msteams`",
 			},
 			"integration_key": {
 				Type:        schema.TypeString,
@@ -143,14 +144,10 @@ func dataSourceHoneycombioRecipientRead(ctx context.Context, d *schema.ResourceD
 	rcptFilter := &recipientFilter{Type: matchType}
 	if v, ok := d.GetOk("target"); ok {
 		// deprecated argument to be removed in future
-		target := v.(string)
-		rcptFilter.Value = &target
+		rcptFilter = &recipientFilter{Value: honeycombio.ToPtr(v.(string))}
 	}
 	if v, ok := d.GetOk("detail_filter"); ok {
 		rcptFilter = expandRecipientFilter(v.([]interface{}))
-		if rcptFilter.Type != matchType {
-			return diag.Errorf("provided type doesn't match filter type")
-		}
 	}
 
 	var filteredRcpts []honeycombio.Recipient
@@ -174,6 +171,9 @@ func dataSourceHoneycombioRecipientRead(ctx context.Context, d *schema.ResourceD
 		d.Set("address", rcpt.Details.EmailAddress)
 	case honeycombio.RecipientTypeSlack:
 		d.Set("channel", rcpt.Details.SlackChannel)
+	case honeycombio.RecipientTypeMSTeams:
+		d.Set("name", rcpt.Details.WebhookName)
+		d.Set("url", rcpt.Details.WebhookURL)
 	case honeycombio.RecipientTypeWebhook:
 		d.Set("name", rcpt.Details.WebhookName)
 		d.Set("secret", rcpt.Details.WebhookSecret)
diff --git a/honeycombio/data_source_recipients.go b/honeycombio/data_source_recipients.go
index f69280e..4d2f4f4 100644
--- a/honeycombio/data_source_recipients.go
+++ b/honeycombio/data_source_recipients.go
@@ -9,6 +9,7 @@ import (
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
 	honeycombio "github.com/honeycombio/terraform-provider-honeycombio/client"
+	"github.com/honeycombio/terraform-provider-honeycombio/internal/helper"
 	"github.com/honeycombio/terraform-provider-honeycombio/internal/helper/hashcode"
 )
 
@@ -25,7 +26,7 @@ func dataSourceHoneycombioRecipients() *schema.Resource {
 				Type:         schema.TypeString,
 				Optional:     true,
 				Description:  "The type of recipients.",
-				ValidateFunc: validation.StringInSlice([]string{"email", "pagerduty", "slack", "webhook"}, false),
+				ValidateFunc: validation.StringInSlice(helper.AsStringSlice(honeycombio.RecipientTypes()), false),
 			},
 			"detail_filter": {
 				Type:         schema.TypeList,
@@ -90,9 +91,6 @@ func dataSourceHoneycombioRecipientsRead(ctx context.Context, d *schema.Resource
 	}
 	if v, ok := d.GetOk("detail_filter"); ok {
 		rcptFilter = expandRecipientFilter(v.([]interface{}))
-		if rcptFilter.Type != matchType {
-			return diag.Errorf("provided type doesn't match filter type")
-		}
 	}
 
 	var rcptIDs []string
diff --git a/honeycombio/provider.go b/honeycombio/provider.go
index afca32f..42f480e 100644
--- a/honeycombio/provider.go
+++ b/honeycombio/provider.go
@@ -58,6 +58,7 @@ func Provider(version string) *schema.Provider {
 			"honeycombio_query_annotation":    newQueryAnnotation(),
 			"honeycombio_email_recipient":     newEmailRecipient(),
 			"honeycombio_pagerduty_recipient": newPDRecipient(),
+			"honeycombio_msteams_recipient":   newMSTeamsRecipient(),
 			"honeycombio_slack_recipient":     newSlackRecipient(),
 			"honeycombio_webhook_recipient":   newWebhookRecipient(),
 			"honeycombio_slo":                 newSLO(),
diff --git a/honeycombio/resource_msteams_recipient.go b/honeycombio/resource_msteams_recipient.go
new file mode 100644
index 0000000..78853ec
--- /dev/null
+++ b/honeycombio/resource_msteams_recipient.go
@@ -0,0 +1,53 @@
+package honeycombio
+
+import (
+	"context"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
+	honeycombio "github.com/honeycombio/terraform-provider-honeycombio/client"
+)
+
+func newMSTeamsRecipient() *schema.Resource {
+	return &schema.Resource{
+		CreateContext: resourceMSTeamsRecipientCreate,
+		ReadContext:   resourceMSTeamsRecipientRead,
+		UpdateContext: resourceMSTeamsRecipientUpdate,
+		DeleteContext: resourceMSTeamsRecipientDelete,
+		Importer: &schema.ResourceImporter{
+			StateContext: schema.ImportStatePassthroughContext,
+		},
+		Description: "Honeycomb MSTeams Recipient allows you to define and manage an MSTeams recipient that can be used by Triggers or BurnAlerts notifications.",
+
+		Schema: map[string]*schema.Schema{
+			"name": {
+				Type:        schema.TypeString,
+				Required:    true,
+				Description: "The name of the MSTeams Integration to create",
+			},
+			"url": {
+				Type:         schema.TypeString,
+				Required:     true,
+				Description:  "The Incoming Webhook URL to send the notification to",
+				ValidateFunc: validation.IsURLWithHTTPorHTTPS,
+			},
+		},
+	}
+}
+
+func resourceMSTeamsRecipientCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	return createRecipient(ctx, d, meta, honeycombio.RecipientTypeMSTeams)
+}
+
+func resourceMSTeamsRecipientRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	return readRecipient(ctx, d, meta, honeycombio.RecipientTypeMSTeams)
+}
+
+func resourceMSTeamsRecipientUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	return updateRecipient(ctx, d, meta, honeycombio.RecipientTypeMSTeams)
+}
+
+func resourceMSTeamsRecipientDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	return deleteRecipient(ctx, d, meta)
+}
diff --git a/honeycombio/type_helpers.go b/honeycombio/type_helpers.go
index 5f78dff..f7f2052 100644
--- a/honeycombio/type_helpers.go
+++ b/honeycombio/type_helpers.go
@@ -270,6 +270,9 @@ func expandRecipient(t honeycombio.RecipientType, d *schema.ResourceData) (*hone
 		r.Details.PDIntegrationName = d.Get("integration_name").(string)
 	case honeycombio.RecipientTypeSlack:
 		r.Details.SlackChannel = d.Get("channel").(string)
+	case honeycombio.RecipientTypeMSTeams:
+		r.Details.WebhookName = d.Get("name").(string)
+		r.Details.WebhookURL = d.Get("url").(string)
 	case honeycombio.RecipientTypeWebhook:
 		r.Details.WebhookName = d.Get("name").(string)
 		r.Details.WebhookSecret = d.Get("secret").(string)
@@ -322,6 +325,9 @@ func readRecipient(ctx context.Context, d *schema.ResourceData, meta interface{}
 		d.Set("integration_name", r.Details.PDIntegrationName)
 	case honeycombio.RecipientTypeSlack:
 		d.Set("channel", r.Details.SlackChannel)
+	case honeycombio.RecipientTypeMSTeams:
+		d.Set("name", r.Details.WebhookName)
+		d.Set("url", r.Details.WebhookURL)
 	case honeycombio.RecipientTypeWebhook:
 		d.Set("name", r.Details.WebhookName)
 		d.Set("secret", r.Details.WebhookSecret)
@@ -401,10 +407,6 @@ func (f *recipientFilter) IsMatch(r honeycombio.Recipient) bool {
 	if f == nil {
 		return true
 	}
-	// types don't match, no point in going further
-	if r.Type != f.Type {
-		return false
-	}
 
 	if f.ValueRegex != nil {
 		switch r.Type {
@@ -414,7 +416,7 @@ func (f *recipientFilter) IsMatch(r honeycombio.Recipient) bool {
 			return f.ValueRegex.MatchString(r.Details.SlackChannel)
 		case honeycombio.RecipientTypePagerDuty:
 			return f.ValueRegex.MatchString(r.Details.PDIntegrationName)
-		case honeycombio.RecipientTypeWebhook:
+		case honeycombio.RecipientTypeWebhook, honeycombio.RecipientTypeMSTeams:
 			return f.ValueRegex.MatchString(r.Details.WebhookName) || f.ValueRegex.MatchString(r.Details.WebhookURL)
 		}
 	} else if f.Value != nil {
@@ -425,7 +427,7 @@ func (f *recipientFilter) IsMatch(r honeycombio.Recipient) bool {
 			return (r.Details.SlackChannel == *f.Value)
 		case honeycombio.RecipientTypePagerDuty:
 			return (r.Details.PDIntegrationName == *f.Value)
-		case honeycombio.RecipientTypeWebhook:
+		case honeycombio.RecipientTypeWebhook, honeycombio.RecipientTypeMSTeams:
 			return (r.Details.WebhookName == *f.Value) || (r.Details.WebhookURL == *f.Value)
 		}
 	}
diff --git a/internal/provider/burn_alert_resource.go b/internal/provider/burn_alert_resource.go
index 32a5ab8..7d1438f 100644
--- a/internal/provider/burn_alert_resource.go
+++ b/internal/provider/burn_alert_resource.go
@@ -79,7 +79,7 @@ func (*burnAlertResource) Schema(_ context.Context, _ resource.SchemaRequest, re
 			},
 		},
 		Blocks: map[string]schema.Block{
-			"recipient": notificationRecipientSchema(),
+			"recipient": notificationRecipientSchema(client.RecipientTypes()),
 		},
 	}
 }
diff --git a/internal/provider/notification_recipients.go b/internal/provider/notification_recipients.go
index 50e72ac..9036546 100644
--- a/internal/provider/notification_recipients.go
+++ b/internal/provider/notification_recipients.go
@@ -18,9 +18,9 @@ import (
 	"github.com/honeycombio/terraform-provider-honeycombio/internal/models"
 )
 
-func notificationRecipientSchema() schema.SetNestedBlock {
+func notificationRecipientSchema(allowedTypes []client.RecipientType) schema.SetNestedBlock {
 	return schema.SetNestedBlock{
-		Description:   "Zero or more recipients to notify when the Trigger fires.",
+		Description:   "Zero or more recipients to notify when the resource fires.",
 		PlanModifiers: []planmodifier.Set{modifiers.NotificationRecipients()},
 		NestedObject: schema.NestedBlockObject{
 			Validators: []validator.Object{
@@ -42,15 +42,15 @@ func notificationRecipientSchema() schema.SetNestedBlock {
 				"type": schema.StringAttribute{
 					Optional:    true,
 					Computed:    true,
-					Description: "The type of the trigger recipient.",
+					Description: "The type of the notification recipient.",
 					Validators: []validator.String{
-						stringvalidator.OneOf(helper.AsStringSlice(client.TriggerRecipientTypes())...),
+						stringvalidator.OneOf(helper.AsStringSlice(allowedTypes)...),
 					},
 				},
 				"target": schema.StringAttribute{
 					Optional:    true,
 					Computed:    true,
-					Description: "Target of the trigger recipient, this has another meaning depending on the type of recipient.",
+					Description: "Target of the notification, this has another meaning depending on the type of recipient.",
 					Validators: []validator.String{
 						stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("type")),
 					},
diff --git a/internal/provider/trigger_resource.go b/internal/provider/trigger_resource.go
index bee6ad3..340d7db 100644
--- a/internal/provider/trigger_resource.go
+++ b/internal/provider/trigger_resource.go
@@ -195,7 +195,7 @@ func (r *triggerResource) Schema(_ context.Context, _ resource.SchemaRequest, re
 					},
 				},
 			},
-			"recipient": notificationRecipientSchema(),
+			"recipient": notificationRecipientSchema(client.TriggerRecipientTypes()),
 		},
 	}
 }


Test patch:
diff --git a/client/recipient_test.go b/client/recipient_test.go
index e20029a..95b1c93 100644
--- a/client/recipient_test.go
+++ b/client/recipient_test.go
@@ -74,3 +74,42 @@ func TestRecipientsEmail(t *testing.T) {
 		assert.True(t, de.IsNotFound())
 	})
 }
+
+func TestRecipientsWebhooksandMSTeams(t *testing.T) {
+	ctx := context.Background()
+	c := newTestClient(t)
+
+	testRcpts := []Recipient{
+		{
+			Type: RecipientTypeWebhook,
+			Details: RecipientDetails{
+				WebhookName:   "test webhook",
+				WebhookURL:    "https://example.com",
+				WebhookSecret: "secret",
+			},
+		},
+		{
+			Type: RecipientTypeMSTeams,
+			Details: RecipientDetails{
+				WebhookName: "test channel",
+				WebhookURL:  "https://corp.office.com/webhook",
+			},
+		},
+	}
+
+	for _, tr := range testRcpts {
+		r, err := c.Recipients.Create(ctx, &tr)
+		require.NoError(t, err)
+		t.Cleanup(func() {
+			_ = c.Recipients.Delete(ctx, r.ID)
+		})
+
+		r, err = c.Recipients.Get(ctx, r.ID)
+		require.NoError(t, err)
+
+		assert.Equal(t, tr.Type, r.Type)
+		assert.Equal(t, tr.Details.WebhookName, r.Details.WebhookName)
+		assert.Equal(t, tr.Details.WebhookURL, r.Details.WebhookURL)
+		assert.Equal(t, tr.Details.WebhookSecret, r.Details.WebhookSecret)
+	}
+}
diff --git a/honeycombio/data_source_recipient_test.go b/honeycombio/data_source_recipient_test.go
index 730bc71..7b287ee 100644
--- a/honeycombio/data_source_recipient_test.go
+++ b/honeycombio/data_source_recipient_test.go
@@ -61,6 +61,13 @@ func TestAccDataSourceHoneycombioRecipient_basic(t *testing.T) {
 				WebhookURL:    "https://my.webhook.dev.corp.io",
 			},
 		},
+		{
+			Type: honeycombio.RecipientTypeMSTeams,
+			Details: honeycombio.RecipientDetails{
+				WebhookName: "My Teams Channel",
+				WebhookURL:  "https://outlook.office.com/webhook/12345",
+			},
+		},
 	}
 
 	for i, r := range testRecipients {
@@ -117,6 +124,13 @@ func TestAccDataSourceHoneycombioRecipient_basic(t *testing.T) {
 					resource.TestCheckResourceAttr("data.honeycombio_recipient.test", "url", "https://my.webhook.dev.corp.io"),
 				),
 			},
+			{
+				Config: testAccRecipientWithFilterValue("msteams", "name", "My Teams Channel"),
+				Check: resource.ComposeAggregateTestCheckFunc(
+					resource.TestCheckResourceAttr("data.honeycombio_recipient.test", "name", "My Teams Channel"),
+					resource.TestCheckResourceAttr("data.honeycombio_recipient.test", "url", "https://outlook.office.com/webhook/12345"),
+				),
+			},
 			{
 				Config: testAccRecipientWithFilterValue("slack", "channel", "#tmp-acctest"),
 				Check:  resource.TestCheckResourceAttr("data.honeycombio_recipient.test", "channel", "#tmp-acctest"),
diff --git a/honeycombio/resource_recipients_test.go b/honeycombio/resource_recipients_test.go
index d916b97..e4d147c 100644
--- a/honeycombio/resource_recipients_test.go
+++ b/honeycombio/resource_recipients_test.go
@@ -61,6 +61,19 @@ resource "honeycombio_webhook_recipient" "test" {
 					resource.TestCheckResourceAttr("honeycombio_webhook_recipient.test", "url", "https://my.url.corp.net"),
 				),
 			},
+			{
+				Config: `
+			resource "honeycombio_msteams_recipient" "test" {
+			  name = "my teams channel"
+			  url  = "https://my.url.office.com/webhooks/incoming/123456789/abcdefg"
+			}
+			`,
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckRecipientExists(t, "honeycombio_msteams_recipient.test"),
+					resource.TestCheckResourceAttr("honeycombio_msteams_recipient.test", "name", "my teams channel"),
+					resource.TestCheckResourceAttr("honeycombio_msteams_recipient.test", "url", "https://my.url.office.com/webhooks/incoming/123456789/abcdefg"),
+				),
+			},
 		},
 	})
 }