From 053fafb7cdbce6da497a5b886ba58e8e30994c51 Mon Sep 17 00:00:00 2001 From: KM Koushik Date: Sun, 14 Sep 2025 06:34:11 +1000 Subject: [PATCH] docs: add Railway self-hosting guide (#228) --- README.md | 6 +- apps/docs/docs.json | 8 +- .../overview.mdx} | 44 +++---- apps/docs/self-hosting/railway.mdx | 114 ++++++++++++++++++ apps/web/src/server/aws/ses.ts | 16 +-- 5 files changed, 153 insertions(+), 35 deletions(-) rename apps/docs/{get-started/self-hosting.mdx => self-hosting/overview.mdx} (79%) create mode 100644 apps/docs/self-hosting/railway.mdx diff --git a/README.md b/README.md index 16870d3..4cce7b6 100644 --- a/README.md +++ b/README.md @@ -96,9 +96,11 @@ For detailed instructions on how to configure and run the Docker container, plea ## Self Hosting -Checkout the [Self hosting](https://docs.usesend.com/get-started/self-hosting) guide to learn how to self-host useSend. +Checkout the [self-hosting guide](https://docs.usesend.com/self-hosting/overview) to learn how to run useSend on your own infrastructure. -Also +## Self Hosting with Railway + +Railway provides the quickest way to spin up useSend. Read the [Railway self-hosting guide](https://docs.usesend.com/self-hosting/railway) or deploy directly: [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/QbMnwX?referralCode=oaAwvp) diff --git a/apps/docs/docs.json b/apps/docs/docs.json index 29d1712..61b0ff8 100644 --- a/apps/docs/docs.json +++ b/apps/docs/docs.json @@ -29,10 +29,16 @@ "get-started/nodejs", "get-started/python", "get-started/local", - "get-started/self-hosting", "get-started/smtp" ] }, + { + "group": "Self Hosting", + "pages": [ + "self-hosting/overview", + "self-hosting/railway" + ] + }, { "group": "Guides", "pages": [ diff --git a/apps/docs/get-started/self-hosting.mdx b/apps/docs/self-hosting/overview.mdx similarity index 79% rename from apps/docs/get-started/self-hosting.mdx rename to apps/docs/self-hosting/overview.mdx index a785b1a..df24644 100644 --- a/apps/docs/get-started/self-hosting.mdx +++ b/apps/docs/self-hosting/overview.mdx @@ -1,7 +1,6 @@ --- title: Self hosting useSend description: "An end-to-end guide on how to self-host useSend. An open-source sending infrastructure for developers." -icon: server --- ## Prerequisites @@ -53,7 +52,7 @@ GITHUB_SECRET="" useSend uses Postgres as a database and Redis as a queue. You need to create a new database and add the following environment variables. -If you're using docker-compose or our railway template, it's all automatically done for you. +If you're using docker-compose, it's all automatically done for you. ```env DATABASE_URL="postgres://:@:/" @@ -80,7 +79,7 @@ Add the following environment variables. ## Step 2: Setting up the app -You can use any platforms that supports docker. You can also use the railway template. In this example I'll be using railway. If you have any questions drop in the [discord channel](https://discord.gg/gbsvjb9MqV) and i'll try to help you out +You can use any platform that supports Docker to host useSend. If you have any questions drop in the [discord channel](https://discord.gg/gbsvjb9MqV) and we'll try to help you out. ### Docker @@ -88,16 +87,6 @@ Follow this guide to setup your docker instance: [Set up docker](/get-started/se [![Docker image](https://img.shields.io/badge/dockerhub-images-important.svg?logo=Docker)](https://hub.docker.com/r/usesend/usesend) -### Railway - -This option is very easy. Click on the below button and click "Deploy now". Add a custom domain etc. - -Updating image is easy, click on the 3 dots and redeploy. This will pull the latest image. - -[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/QbMnwX?referralCode=oaAwvp) - -Your useSend instance is now live now. - ## Step 3: Setting up a region In order to send emails, you need to select a region in AWS. Use a region where your users are located / where useSend is hosted. If you're confused just use `us-east-1`. @@ -123,10 +112,12 @@ Once you log in to useSend, it will prompt you add SES configuration. The SMTP proxy server is an optional component that allows applications to send emails through useSend using standard SMTP protocol instead of the REST API. This is useful for legacy applications, email clients, or any software that needs to send emails via SMTP. -The complete source code for the SMTP proxy server is available at: [usesend/usesend/tree/main/apps/smtp-server](https://github.com/usesend/usesend/tree/main/apps/smtp-server) + The complete source code for the SMTP proxy server is available at: + [usesend/usesend/tree/main/apps/smtp-server](https://github.com/usesend/usesend/tree/main/apps/smtp-server) ### When to use the SMTP proxy: + - **Legacy applications** that only support SMTP - **Email clients** like Thunderbird, Outlook, Apple Mail - **Applications** that can't easily integrate with REST APIs @@ -146,23 +137,23 @@ services: environment: SMTP_AUTH_USERNAME: "usesend" # Username for SMTP authentication USESEND_BASE_URL: "https://your-usesend-instance.com" # Your useSend instance URL - + # Optional: SSL certificate paths for secure connections # USESEND_API_KEY_PATH: "/certs/server.key" # USESEND_API_CERT_PATH: "/certs/server.crt" - + # Optional: Mount SSL certificates # volumes: # - ./certs/server.key:/certs/server.key:ro # - ./certs/server.crt:/certs/server.crt:ro - + ports: - - "25:25" # Standard SMTP - - "587:587" # SMTP with STARTTLS + - "25:25" # Standard SMTP + - "587:587" # SMTP with STARTTLS - "2587:2587" # Alternative SMTP port - - "465:465" # SMTP over SSL/TLS + - "465:465" # SMTP over SSL/TLS - "2465:2465" # Alternative SMTPS port - + restart: unless-stopped ``` @@ -183,11 +174,14 @@ To send emails through the proxy, configure your application with these SMTP set - **Encryption**: STARTTLS (ports 25, 587, 2587) or SSL/TLS (ports 465, 2465) -The SMTP proxy forwards all emails to your useSend instance, so make sure your main useSend application is running and accessible. + The SMTP proxy forwards all emails to your useSend instance, so make sure your + main useSend application is running and accessible. -Ensure your firewall allows traffic on the SMTP ports you're using. For production deployments, consider using non-standard ports (2587, 2465) to avoid conflicts. + Ensure your firewall allows traffic on the SMTP ports you're using. For + production deployments, consider using non-standard ports (2587, 2465) to + avoid conflicts. ## Next steps @@ -201,5 +195,7 @@ You're all set up now. If you have any questions, please join [#self-host](https://discord.gg/gbsvjb9MqV) on discord. -A community member shared a short write-up on hosting useSend with [Coolify](https://mattstein.com/thoughts/coolify-unsend/). Give it a read if you need another reference. + A community member shared a short write-up on hosting useSend with + [Coolify](https://mattstein.com/thoughts/coolify-unsend/). Give it a read if + you need another reference. diff --git a/apps/docs/self-hosting/railway.mdx b/apps/docs/self-hosting/railway.mdx new file mode 100644 index 0000000..b0b6022 --- /dev/null +++ b/apps/docs/self-hosting/railway.mdx @@ -0,0 +1,114 @@ +--- +title: Self hosting with Railway +description: Deploy useSend on Railway with one click. +--- + +useSend is a [Railway Partner](https://railway.app/partners) and Railway offers the quickest way to get a useSend instance running. + +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/QbMnwX?referralCode=oaAwvp) + +## Prerequisites + +- A [GitHub](https://github.com) account +- An [AWS](https://aws.amazon.com) account + +If you have any questions join [#self-host](https://discord.gg/gbsvjb9MqV) on discord. + +## Step 1: Deploy on Railway + +1. Click the button above and choose **Deploy now**. +2. Wait for the deployment to complete and open the service. +3. Note the assigned Railway domain (you can add a custom domain later). +4. You can redeploy from the dashboard anytime to pull the latest image. + +## Step 2: Environment variables + +useSend depends on AWS SES to send emails and SNS to receive email status. The Railway template provisions Postgres (database) and Redis (queue) automatically—no action needed for those. Add the following variables under your service’s Variables tab. + + + + tl;dr: In the AWS Console, create a new IAM user with programmatic access. Attach `AmazonSNSFullAccess` and `AmazonSESFullAccess`. Then create an access key. + +Add the following environment variables in Railway. + +```env +AWS_ACCESS_KEY= +AWS_SECRET_KEY= +``` + + + Follow this for detailed steps: [Create AWS + credentials](/get-started/create-aws-credentials) + + + + + useSend uses GitHub authentication for login. + +Use this link to [create a GitHub app](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps) + +Callback URL : `https:///api/auth/callback/github` + +![github app](/images/github-callback.png) + +Add the following environment variables in Railway. + +```env +GITHUB_ID="" +GITHUB_SECRET="" +``` + + + + + Set your public app URL for NextAuth. + +Add the following variables in Railway: + +```env +NEXTAUTH_URL="https://" +``` + + + + + + After adding variables, click **Redeploy** so the service picks up the new + configuration. + + +## Step 3: Optional — Custom domain + +- Add a custom domain in Railway and configure DNS. +- Update `NEXTAUTH_URL` to your new domain (e.g., `https://mail.yourdomain.com`). +- Redeploy to apply the change. + +## Step 4: Set up SES region in useSend + +In order to send emails, you need to select a region in AWS. Use a region where your users are located / where useSend is hosted. If you're unsure, use `us-east-1`. + +You can check available regions [here](https://docs.aws.amazon.com/general/latest/gr/ses.html) + +Once you log in to useSend, it will prompt you add SES configuration. + +- Add the region +- Add the callback url, which is basically the app url. Note this should be accessible from the internet. This is how you get the delivery status of the emails. +- You don't need to update the send rate, it's automatically based on your account. + +![add ses settings](/images/ses-settings/add-ses-settings.png) + +## Step 5: Get out of SES sandbox + +Don't forget to get the SES account out of sandbox mode. + +![sandbox mode](/images/ses-settings/sandbox.png) + +## Next steps + +You're all set up. + +- Set up a domain. +- Create an API key. +- Start sending emails. + +If you have any questions, please join [#self-host](https://discord.gg/gbsvjb9MqV) on discord. diff --git a/apps/web/src/server/aws/ses.ts b/apps/web/src/server/aws/ses.ts index 2012e4a..0bb413a 100644 --- a/apps/web/src/server/aws/ses.ts +++ b/apps/web/src/server/aws/ses.ts @@ -86,7 +86,7 @@ export async function addDomain( domain: string, region: string, sesTenantId?: string, - dkimSelector: string = "usesend", + dkimSelector: string = "usesend" ) { const sesClient = getSesClient(region); @@ -115,13 +115,13 @@ export async function addDomain( }); const tenantResourceAssociationResponse = await sesClient.send( - tenantResourceAssociationCommand, + tenantResourceAssociationCommand ); if (tenantResourceAssociationResponse.$metadata.httpStatusCode !== 200) { logger.error( { tenantResourceAssociationResponse }, - "Failed to associate domain with tenant", + "Failed to associate domain with tenant" ); throw new Error("Failed to associate domain with tenant"); } @@ -133,7 +133,7 @@ export async function addDomain( ) { logger.error( { response, emailIdentityResponse }, - "Failed to create domain identity", + "Failed to create domain identity" ); throw new Error("Failed to create domain identity"); } @@ -144,7 +144,7 @@ export async function addDomain( export async function deleteDomain( domain: string, region: string, - sesTenantId?: string, + sesTenantId?: string ) { const sesClient = getSesClient(region); @@ -156,13 +156,13 @@ export async function deleteDomain( }); const tenantResourceAssociationResponse = await sesClient.send( - tenantResourceAssociationCommand, + tenantResourceAssociationCommand ); if (tenantResourceAssociationResponse.$metadata.httpStatusCode !== 200) { logger.error( { tenantResourceAssociationResponse }, - "Failed to delete tenant resource association", + "Failed to delete tenant resource association" ); throw new Error("Failed to delete tenant resource association"); } @@ -292,7 +292,7 @@ export async function addWebhookConfiguration( configName: string, topicArn: string, eventTypes: EventType[], - region: string, + region: string ) { const sesClient = getSesClient(region);