Add SMS START/re-subscribe handling to Twilio webhook
Customers who replied STOP by mistake can now reply START, YES, or UNSTOP to automatically re-enable their SMS opt-in — no staff action needed. Adds SmsInboundStart notification type, HandleStartAsync in WebhooksController, and updates AI knowledge base and help docs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,12 @@ public class WebhooksController : ControllerBase
|
||||
"STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT"
|
||||
};
|
||||
|
||||
// CTIA-standard opt-in keywords
|
||||
private static readonly HashSet<string> StartKeywords = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"START", "YES", "UNSTOP"
|
||||
};
|
||||
|
||||
// CTIA-standard help keywords
|
||||
private static readonly HashSet<string> HelpKeywords = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
@@ -77,6 +83,9 @@ public class WebhooksController : ControllerBase
|
||||
if (StopKeywords.Contains(body))
|
||||
return await HandleStopAsync(payload.From);
|
||||
|
||||
if (StartKeywords.Contains(body))
|
||||
return await HandleStartAsync(payload.From);
|
||||
|
||||
if (HelpKeywords.Contains(body))
|
||||
return await HandleHelpAsync(payload.From);
|
||||
|
||||
@@ -123,6 +132,44 @@ public class WebhooksController : ControllerBase
|
||||
$"Reply START to re-subscribe.");
|
||||
}
|
||||
|
||||
// ── START ─────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Processes a START keyword: re-enables SMS for the customer, clears SmsOptedOutAt,
|
||||
/// logs to NotificationLog, and returns a TwiML confirmation message.
|
||||
/// </summary>
|
||||
private async Task<IActionResult> HandleStartAsync(string from)
|
||||
{
|
||||
var (customer, digits10) = await FindCustomerByPhoneAsync(from);
|
||||
|
||||
if (customer == null)
|
||||
{
|
||||
_logger.LogWarning("Twilio START from {From} — no matching customer found", from);
|
||||
return TwimlMessage("You have been re-subscribed and will receive messages again.");
|
||||
}
|
||||
|
||||
var companyName = await GetCompanyNameAsync(customer.CompanyId);
|
||||
|
||||
if (!customer.NotifyBySms)
|
||||
{
|
||||
customer.NotifyBySms = true;
|
||||
customer.SmsOptedOutAt = null;
|
||||
customer.UpdatedAt = DateTime.UtcNow;
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInformation("Customer {CustomerId} re-subscribed to SMS via START reply", customer.Id);
|
||||
}
|
||||
|
||||
await WriteInboundLogAsync(
|
||||
NotificationType.SmsInboundStart,
|
||||
customer,
|
||||
from,
|
||||
"START");
|
||||
|
||||
return TwimlMessage(
|
||||
$"{companyName}: You have been re-subscribed and will receive messages again. " +
|
||||
$"Reply STOP to unsubscribe at any time.");
|
||||
}
|
||||
|
||||
// ── HELP ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user