Build a Secure Signup API in .NET that Filters Emails and Verifies with Twilio SendGrid
Time to read:
Build a Secure Signup API in .Net
Sometimes at work, we’d see what looked like a surge of new users on random mornings and get very excited, only to discover later that most were fake or spam signups using disposable or suspicious email addresses. This not only polluted our metrics but also opened the door to abuse. In this tutorial, you’ll learn how to build a secure signup API using .NET that blocks specific email addresses and domains, adds domain-level access control, and sends verification emails through Twilio SendGrid.
Prerequisites
To follow along with this tutorial, you’ll need:
- A free Twilio account
- A verified Twilio SendGrid API key
- .NET 6 SDK or later
- Basic knowledge of building REST APIs in ASP.NET Core
- Swagger or a similar API testing tool
- A text editor like VS Code
Setting up your .NET Web API project
To get started, open up your IDE of choice, and create a new ASP.NET Core Web API using the .NET CLI. In the terminal, type:
Then, run the app to confirm everything is working.
By default, your output should look like the screenshot below.


Setting Up Swagger UI
Swagger gives you a browser-based interface for testing your API endpoints, and is really helpful during development and when sharing API documentation with team members or clients.
Your project doesn’t support Swagger out of the box. You'll need to install the Swashbuckle.AspNetCore
NuGet package first.
In your terminal, run:
This adds Swagger and OpenAPI tooling that enables AddSwaggerGen()
and UseSwagger()
in your code.


Swagger is really useful when debugging API responses or demoing endpoint behavior without needing to use an external tool like Postman.
What’s in your Program.cs
When you create a new Web API project in .NET 6 or later using the CLI, your Program.csfile acts as the central hub. Instead of separate controller files, your routes and logic can live directly inside Program.cs. This is known as the minimal API pattern, which is okay for basic programs.
By default, the project comes with a sample endpoint called /weatherforecast
. You'll need to remove this so you can replace it with your own custom logic for this project, starting with a /signup
endpoint.
Clean out the template
- Open Program.cs.
- Delete or comment out everything related to
/weatherforecast
. That includes thesummaries
array,MapGet("/weatherforecast")
, and theWeatherForecast
record.
Your Program.cs should now be mostly empty except for the startup logic.
Adding Swagger Services
Now you are going to add the Swagger services so your API is visible in Swagger.
In Program.cs, look for this line:
Below that line, add the services below. These lines of code will generate the OpenAPI specification and UI.
Here’s what each line does:
AddControllers()
tells.NET
to look for controller files like SignupController.cs and register the routes defined there.AddEndpointsApiExplorer()
enables minimal APIs or conventional routing to be discoverable.AddSwaggerGen()
generates the Swagger/OpenAPI documentation based on your registered endpoints.
Without AddControllers()
, your custom routes like /signup will not appear in Swagger, which might cause you to see issues like " No operations defined in spec".
Now, look for this line in Program.cs.
Below that line, add the middleware configuration, which tells your app to serve the OpenAPI JSON and render the Swagger interface at /swagger
.
This configures your API to:
- Serve Swagger UI at
/swagger
- Redirect all
HTTP
traffic toHTTPS
- Load routes defined in controller classes like
SignupController
Final Program.cs should look like this:
Then run your app again, using:
Visit http://localhost:5000/swagger
(or whatever port your application is running on) and you’ll see a full UI where you can test API endpoints like POST /signup
in real time.
You should now see the Swagger UI. If it says “No operations defined in spec,” don’t worry. This is expected at this stage, since you haven’t defined any custom endpoints yet. You’ll see your routes appear in Swagger once you create them in the following sections.
Creating the Signup Endpoint
Create the Models folder
The first step is to create a Models
folder in the root of your project. Then, inside it, create a file named SignupRequest.cs (projectname/Models/SignupRequest.cs). This is where you define all classes used to represent structured data in your API. In this case, you’ll create a class named SignupRequest
that holds the email address submitted during signup.
Create the controller
Next, create another folder in the root of your project and name it Controllers. In the Controllers folder, add a file named SignupController.cs. This controller defines your API logic and contains the /signup
endpoint.
[ApiController]
allows automatic model validation and better error messages.[Route("[controller]")]
sets the endpoint to match the controller name, i.e., /signup.IActionResult Signup(...)
is the method that handles POST requests with user email data.
It also contains a function that validates the email for emptiness and proper format using a regular expression.
Now you can check if the email validation works as it should. Run the app and visit http://localhost:5000/swagger
to try the POST /signup
endpoint with:
Your response should look like this:
Blocking Disposable Email Domains
You've likely been here. Find a new product you wanted to test, or quickly download, and you toss in a throwaway email like user@mailinator.com. Of course, you think to yourself, no harm done. But imagine running a platform and waking up to thousands of those fake addresses distorting your growth metrics, giving you false signs of hope and alerts, and even opening doors to misuse. What started as someone skipping the friction becomes your team's data nightmare. To prevent this, you can try to be one step ahead and block common and suspicious looking disposable email domains using a local file.
Create a blocklist file
In the root of your project, add a file called blocked_domains.txt ( projectname/ blocked_domains.txt). In this file you can add a list of domains that should be flagged immediately if they try to sign up. The beauty of doing this in this separate file is the ease of adding to or removing from the list without requiring code changes.
Create a domain checker class
Create a folder called Utils in the root of your project (project/Utils). This folder will house utility classes that support your application's logic but don't belong to the controller or model layers. Inside it, add a create a file called BlockedDomainChecker.cs. Like the name suggests, this class will handle the logic for checking whether an email's domain appears in your blocklist. You will make use of a Hashset by loading the list into it. This will ensure fast lookups, and make this solution efficient even as the list grows.
You’ll extract the domain from each submitted email, convert it to lowercase, and check if it exists in your blocked domains list. Paste the following code into your BlockedDomainChecker.cs file
Next, register the BlockedDomainChecker
in your Program.cs file under the Swagger and controller services. This will make sure the class is available to be injected into your controller.
Open your Program.cs and add the following line:
Use it in your controller
First you need to call the file, so add this to the top of your Controllers/SignupController.cs :
In your SignupController
, define the checker as a private field and instantiate it:
Then, inside your Signup
method, right after validating the email format, check if the domain is blocked:
Now you can go back to Swagger to test your endpoint. Run your app and test with:
Your response should look like this:


Restricting signups to specific allowed domains
While blocking bad domains is helpful, sometimes you want to go a step further. For example, you might want to:
- Only allow people from your company (like @mycompany.com) to register
- Prevent anyone from signing up with a personal Gmail or Yahoo address
To achieve this successfully, you can handle this by setting up a configuration-based allowlist.
Add allowed domains to your config
Head over to your appsettings.json
file and add the following after "AllowedHosts:"
:
This array represents the only domains that should be allowed. You can update it without changing your code.
Create the domain allowlist checker
Next, you need to create a new file under the Utils folder called Utils/AllowedDomainChecker.cs. In this file, you will communicate with the array of allowed domains and will have a service to check if the domain extracted from the inputted email address is one of the permitted domains, if not you prevent them from accessing.
Next, you need to register the checker in Program.cs under the swagger service you added earlier. This way the class is available to be injected into your controller.
Add the following line to the top of your Program.cs, under other using directives:
Then, under the AddSwaggerGen();
line you added earlier, add this:
Use it in your controller
You need to inject the AllowedDomainChecker
through the constructor in your SignupController.cs:
Add allowlist validation inside the Signup
method. Do this after the email format and blocklist check.
Now, even if someone uses a valid and non-disposable email (like user@gmail.com), this step ensures they’re still not allowed unless their domain is explicitly approved by you or your team. It's very useful for internal tools or closed betas.
Send a verification email with Twilio SendGrid
Once the email passes all validations, you want to send a confirmation message to the user’s inbox. We'll do this using Twilio SendGrid. First, head over to your terminal and run:
- The
Sendgrid
package lets you send emails through the Twilio Sendgrid API dotenv.net
package loads environment variables from an .env file
Configure Twilio SendGrid
Before you can send emails, you'll need a SendGrid API key. Here's how to generate one:
- Log into your SendGrid Dashboard.
- In the left-hand menu, click Settings, then API Keys.
- Click the Create API Key button.
- Give the key a name you’ll recognize (e.g., SecureSignupApi).
- For simplicity in this tutorial, select Full Access (you can scope it down later for production use).
- Click Create & View.
SendGrid will now show you the API key once. Copy it immediately and store it somewhere secure. If you close the page without copying the key, you'll need to generate a new one. In this project, you’ll store the key in a .env file.
Next, in the root of your project, create your .env file. Remember to add this file to your .gitignore to prevent it from being pushed when committing to Git. In this file define the variables below:
Create the email sending service
You’ll need to create a reusable service class that handles sending emails using the SendGrid API. Create a new folder in the root of your project called Service (projectname/service) and within it, a file called EmailService.cs.
This service will read credentials from environment variables and send a standard verification message to any valid email. Here is what the service will do:
- The service loads SendGrid credentials from environment variables (set via your .env file).
- It uses SendGrid’s helper to format the message.
- Success or failure is logged in your terminal.
Register EmailService and load your .env file
To be able to use the EmailService
in your controller, you need to register it with the dependency injection container and load your .env file at startup. Head to your Program.cs file, and at the top, alongside other imports, add this:
Then, in your setup login in this same file, before the builder was created, add this to ensure that values like SENDGRID_API_KEY
are available from your .env file.
And after all other service declarations, add this to make the email sender injectable in your controller.
Inject and use EmailService in your controller
Now that the service is registered, you can add it to your controller and use it after the email is validated. First, update your controller’s constructor:
Then, call it at the end of the Signup method:
Now to test, run your app and head back to swagger. Test with:
If the email is valid - meaning, it's not a blocked domain, and the domain is on your allowed list - the API sends a real email and logs a green [INFO] message in the terminal. Something like this:




Troubleshooting Tips
Here are a few common issues you might run into and tips to guide you:
- If the
/swagger
page doesn't load, make sureUseSwagger()
andUseSwaggerUI()
are included in Program.cs. - If no email is received, check that your SendGrid API key is correct and your sender email is verified in your SendGrid dashboard.
- If you are using Twilio's Sendgrid trial account, you might be restricted to sending 100 emails daily.
- If environment variables aren't loading, ensure .env exists and
DotEnv.Load()
is called before service registration.
Conclusion
Congratulations on building a secure and production-ready signup API in .NET! You’ve learned how to validate emails, block suspicious domains, enforce domain-level access control, and send real-time verification emails using Twilio SendGrid. This foundation is perfect for internal tools, waitlisted platforms, or any application where user authenticity matters.
If you’d like to explore this project further or fork a working version, check out the GitHub repository.
Remember, email sending is tightly monitored. Avoid spamming users or sending content without consent. As a next step, explore the Twilio SendGrid documentation to learn about advanced features like sender reputation, domain authentication, and delivery optimization.
Thanks for reading!
Oghenevwede is a Senior Product Engineer with 8 years of obsessing about how things work on her phone and on the web. Her new obsession is health and wellness.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.