Host your own email and enhance your privacy
Cabalmail uses an AWS End User Messaging toll-free number for transactional SMS (signup phone verification, password reset, sign-in MFA). The phone number is provisioned by Terraform when TF_VAR_USE_EUM_SMS=true, but it sits in Pending status until you submit a toll-free verification (TFV) registration. AWS does not surface this requirement anywhere in the console flow - the number simply waits.
This guide walks you through the one-time operator steps to submit the TFV registration via the register-tfv GitHub Actions workflow. After submission, carrier review takes 5-15 business days; until it completes, SMS does not deliver.
Before you trigger the workflow:
TF_VAR_USE_EUM_SMS=true is set as a GitHub Environment variable on the target environment (stage and/or prod) and that infra.yml has applied successfully. The number should appear in the AWS End User Messaging console under Configurations -> Phone numbers with status Pending.https://www.<control_domain>/privacy.html and https://www.<control_domain>/terms.html respectively. The bucket and CloudFront distribution are provisioned by the front_door Terraform module and the content ships via the front_door area of .github/workflows/app.yml (see front-door.md).Opt-in screenshot. AWS requires a screenshot of the signup screen showing the SMS consent language. Capture a screenshot of the Cabalmail admin signup form (the panel with the username + phone number fields and the paragraph reading “By creating an account you agree to the Terms and Privacy Policy, and to receive transactional SMS…”). Save as a PNG, ideally under 1 MB.
You have two delivery options:
front-door/opt-in-screenshot.png. The workflow picks it up automatically.opt_in_image_url workflow input. The workflow fetches it at run time. This avoids committing a binary that may need to be regenerated whenever the signup screen changes.IAM permissions on the cicd role. The workflow assumes the same cicd deploy role via GitHub OIDC that infra.yml uses (see AWS setup step 7). That role does not have sms-voice:* permissions by default; attach the inline policy below to the role before triggering the workflow for the first time on each AWS account. The policy is intentionally action-scoped rather than wildcarded so future API additions are reviewable.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CabalmailTFVSubmission",
"Effect": "Allow",
"Action": [
"sms-voice:DescribePhoneNumbers",
"sms-voice:DescribeRegistrations",
"sms-voice:DescribeRegistrationFieldDefinitions",
"sms-voice:DescribeRegistrationVersions",
"sms-voice:DescribeRegistrationFieldValues",
"sms-voice:DescribeRegistrationAttachments",
"sms-voice:ListRegistrationAssociations",
"sms-voice:CreateRegistration",
"sms-voice:CreateRegistrationVersion",
"sms-voice:CreateRegistrationAttachment",
"sms-voice:PutRegistrationFieldValue",
"sms-voice:CreateRegistrationAssociation",
"sms-voice:SubmitRegistrationVersion"
],
"Resource": "*"
}
]
}
AWS Console -> IAM -> Users -> terraform -> Add permissions -> Create inline policy -> JSON tab -> paste -> name it CabalmailTFVSubmission. Same procedure on each AWS account you intend to submit a TFV registration from (stage, prod).
For each environment you want to enable TFV in (typically stage first, then prod), set the TFV_* variables and secrets under Settings -> Environments -> [environment name]. The full list of names, examples, and descriptions is in docs/github.md. The AWS_REGION repository secret and the per-environment AWS_DEPLOY_ROLE_ARN variable are reused from infra.yml and do not need to be duplicated.
Note: This workflow is just a convenience. The rules for completing the registration are highly opaque, with branching requirements that change based on prior answers. We don’t guarantee that this workflow will succeed for every case. You can always update or replace the registration manually via the AWS console. See [the AWS documentation])(https://docs.aws.amazon.com/sms-voice/latest/userguide/registrations.html) for details.
stage or prod).opt_in_image_url empty (uses the file at front-door/opt-in-screenshot.png if committed) or paste a public HTTPS URL to the screenshot.The job will:
Final output includes the registration ID and a describe-registrations command you can run to poll status.
aws pinpoint-sms-voice-v2 describe-registrations \
--registration-ids <id from workflow output> \
--region <your AWS region>
Status progression: DRAFT (before submit) -> REVIEWING (carrier review) -> COMPLETE (approved, number can deliver) or REQUIRES_UPDATES (carrier rejected; reason in RegistrationVersionStatusHistory).
If the registration comes back REQUIRES_UPDATES:
describe-registration-versions.TFV_USE_CASE_DETAILS, TFV_OPT_IN_DESCRIPTION, or the opt-in screenshot).Once status is COMPLETE:
sms_configuration path).https://admin.<control_domain> using a phone number you control.TFV_COMPANY_WEBSITE/privacy.html must explicitly cover SMS use, message frequency, STOP/HELP keywords, and data retention. The default front-door/privacy.html includes all of these.ONE_TIME_PASSCODES or ACCOUNT_NOTIFICATIONS are the closest fits; PROMOTIONS_AND_MARKETING would be rejected.TFV_MONTHLY_VOLUME=10 unless you actually expect more.