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:
2026-05-02 20:09:49 -04:00
parent 8148908a66
commit 3ff6a96bc8
4 changed files with 54 additions and 4 deletions
@@ -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>