oss-mailer
v1.1.2
Published
Client SDK for the oss-mailer self-hosted email routing service
Maintainers
Readme
oss-mailer
oss-mailer is the client SDK for the oss-mailer self-hosted email service.
Important: this package is not a hosted email service and does not send email by itself. You must deploy an
oss-mailerinstance first, then point this SDK at it.
First action: install this package from npm
If you are a coding agent integrating oss-mailer into an application, start in the user's application repository and install the package from npm using that application's package manager:
npm install oss-mailerUse the equivalent command for the project if it uses another package manager: pnpm add oss-mailer, yarn add oss-mailer, or bun add oss-mailer.
After installation, read this README from node_modules/oss-mailer/README.md, inspect the package exports, and continue with the integration instructions below. Do not clone the GitHub repository just to use the client SDK. The GitHub repository is only needed if the agent is provisioning a new self-hosted mailer service.
Instructions for coding agents
When a user asks you to add email sending to an application, follow this README as the implementation runbook. Work in the user's application repository; do not modify this package's source code or ask the user to manually copy SDK code.
- Install
oss-mailerfrom npm using the application's package manager, as shown above. If it is already installed, inspect its installed version rather than reinstalling blindly. - Inspect the application. Identify its framework, server-only code locations, environment-file convention, deployment platform, and whether an email service is already configured.
- If
MAILER_URLandMAILER_API_KEYalready exist, create a small server-only mailer module and integrate it at the requested email call site. - If no
MAILER_URLexists, explain that the SDK needs a deployed service. If the user wants this agent to provision it, follow Self-host the service; otherwise ask only for the existing service URL and API key. - Ask the user only for values that cannot be safely discovered or generated: account logins, verified SES sender and region, AWS credentials,
DATABASE_URL, QStash credentials, deployment access, and a recipient address for testing. GenerateMAILER_API_KEYwith a secure local command when one does not already exist. - Keep all credentials server-side. Never create
NEXT_PUBLIC_versions of mailer, AWS, database, or QStash variables; never print secret values; never commit.env.localor any secret file. - Use the existing application conventions for environment variables and server modules. Do not add a second mail client, duplicate environment variables, or a browser-side fetch when a server-side integration already exists.
- Use the category and
propscontracts below. Prefermagic_linkfor the first verification because it sends synchronously; useupdateorpromotionalwhen queued delivery is intended. - Run the application's relevant typecheck, lint, and tests. Then run the authenticated health check and one test send. Do not claim setup is complete until both succeed, or clearly report the exact external step that remains.
The expected result in the user's application is:
oss-mailerinstalled in the correct dependency section- server-side
MAILER_URLandMAILER_API_KEYvariables - one reusable server-only client module
- the requested email flow integrated with the correct category
- a successful test send, or a precise explanation of what the user must still do
What you need
Before you start, create or have access to:
- An AWS account with Amazon SES
- A PostgreSQL database, such as Neon
- An Upstash QStash account for queued
promotionalandupdateemail - A deployment for the service, such as Vercel
- Node.js 18 or newer
Prepare the account values
You do not need to understand the application code to prepare these values. Give the values to your agent through the project's secure environment, a password manager integration, or the prompts it provides. Do not put them in a chat message if your agent's transcript is retained.
| Account | One-time action | Value the agent needs |
| --- | --- | --- |
| AWS SES | Create and verify an email identity or domain in the same region you will use for sending. If the account is in sandbox mode, verify the test recipient too. | MAILER_FROM, SES_REGION |
| AWS IAM | Create an access key for an identity that can call ses:SendEmail and ses:SendRawEmail. | SES_ACCESS_KEY_ID, SES_SECRET_ACCESS_KEY |
| Neon | Create a PostgreSQL project and copy its connection string. | DATABASE_URL |
| Upstash QStash | Create a QStash instance and copy its API URL and token. | QSTASH_URL, QSTASH_TOKEN |
| Vercel | Sign in with vercel login, or connect the repository in the dashboard. | Deployment access |
The QStash signing keys can be retrieved after you have the token. An agent with permission to use that token can run:
curl --request GET \
--url "https://qstash.upstash.io/v2/keys" \
--header "Authorization: Bearer $QSTASH_TOKEN"Copy the returned current value to QSTASH_CURRENT_SIGNING_KEY and the returned next value to QSTASH_NEXT_SIGNING_KEY. Keep all of these values secret.
Self-host the service
Clone the service repository and install its dependencies:
git clone https://github.com/DarrenJCoxon/oss-mailer.git
cd oss-mailer
npm installCopy the example environment file:
cp .env.example .env.localGenerate a private API key for your service:
openssl rand -hex 32Set the values in .env.local (or in your deployment provider's environment-variable settings):
| Variable | What to set |
| --- | --- |
| MAILER_API_KEY | A long, random secret. Your application will use the same value when calling the service. |
| MAILER_FROM | The sender address, for example oss-mailer <[email protected]>. The address or domain must be verified in SES. |
| SES_ACCESS_KEY_ID | An AWS access key with permission to send through SES. |
| SES_SECRET_ACCESS_KEY | The matching AWS secret access key. Keep it private. |
| SES_REGION | The AWS region where your SES identity is verified, for example eu-west-2. |
| MAGIC_LINK_PROVIDER | ses |
| PROMOTIONAL_PROVIDER | ses |
| UPDATE_PROVIDER | ses |
| DATABASE_URL | Your PostgreSQL connection string. |
| QSTASH_URL | The QStash API URL from your QStash setup. |
| QSTASH_TOKEN | Your QStash token. |
| QSTASH_CURRENT_SIGNING_KEY | Your QStash current signing key. |
| QSTASH_NEXT_SIGNING_KEY | Your QStash next signing key. |
| DELIVER_URL | The public URL of the service's queue endpoint: https://your-mailer.example.com/api/queue/deliver. |
The service currently supports Amazon SES as its provider, so all three provider variables should be set to ses.
Verify the SES sender
In SES, verify the email address or domain used by MAILER_FROM. If your AWS account is still in the SES sandbox, the recipient address must also be verified before a test send will succeed.
Create the database schema
Run the migration against the database in DATABASE_URL:
npm run db:migrateThe migration is safe to run again; Drizzle records which migrations have already been applied. Run it from the cloned service directory with the same DATABASE_URL that the deployed service will use.
Deploy
Deploy the repository as a Next.js application. Vercel is the simplest option: import the GitHub repository, add the environment variables above, and deploy it.
Because DELIVER_URL contains the deployed URL, the usual order is:
- Deploy once with a temporary
DELIVER_URLvalue. - Copy the deployment's public URL.
- Set
DELIVER_URLto<public-url>/api/queue/deliver. - Redeploy so the new value is active.
Vercel CLI path
An AI agent can perform the deployment without using the dashboard once you have authenticated the Vercel CLI:
npm install --global vercel
vercel login
vercel link --yes
vercel deploy --prodIf the agent is setting deployment variables through the CLI, use the interactive vercel env add <NAME> production prompts or your team's approved secret-management workflow. Do not put secret values directly into shell commands, Git commits, or the README. Environment-variable changes apply to new deployments, so redeploy after adding or changing them.
For a new Vercel project, the agent should add each service variable to the Production environment before deploying. The following is the complete list; the vercel env add command prompts for the value without putting it in the command itself:
vercel env add MAILER_API_KEY production
vercel env add MAILER_FROM production
vercel env add SES_ACCESS_KEY_ID production
vercel env add SES_SECRET_ACCESS_KEY production
vercel env add SES_REGION production
vercel env add MAGIC_LINK_PROVIDER production
vercel env add PROMOTIONAL_PROVIDER production
vercel env add UPDATE_PROVIDER production
vercel env add DATABASE_URL production
vercel env add QSTASH_URL production
vercel env add QSTASH_TOKEN production
vercel env add QSTASH_CURRENT_SIGNING_KEY production
vercel env add QSTASH_NEXT_SIGNING_KEY production
vercel env add DELIVER_URL productionUse ses for the three provider prompts. For the first deployment, use a temporary value for DELIVER_URL; after Vercel prints the public URL, replace it with the real /api/queue/deliver URL and deploy again.
The first production deployment prints the public URL. The agent should then set DELIVER_URL to <public-url>/api/queue/deliver in the production environment and run vercel deploy --prod again. If you use a custom domain, use that stable domain in DELIVER_URL rather than a temporary Vercel deployment URL.
Dashboard path
If you prefer the Vercel dashboard, import DarrenJCoxon/oss-mailer as a Next.js project, add the variables from the table above to the Production environment, and deploy. Then update DELIVER_URL with the deployed URL and redeploy. The agent can still run the migration and verification commands locally while you complete these dashboard steps.
After deployment, save these two values for your application:
MAILER_URL: the service's public base URL, such ashttps://your-mailer.vercel.appMAILER_API_KEY: the same secret configured on the service
You can inspect the service configuration at /settings. Keep the API key and AWS/QStash credentials server-side; never expose them as NEXT_PUBLIC_ variables or commit them to Git.
Verify the deployment before integrating the SDK
Run these checks from a trusted terminal. Replace the placeholders locally; do not paste real secrets into documentation or chat transcripts:
export MAILER_URL="https://your-mailer.vercel.app"
export MAILER_API_KEY="your-service-api-key"
curl -fsS \
-H "Authorization: Bearer $MAILER_API_KEY" \
"$MAILER_URL/health"
curl -fsS -X POST "$MAILER_URL/api/send" \
-H "Authorization: Bearer $MAILER_API_KEY" \
-H "content-type: application/json" \
-d '{
"category": "magic_link",
"to": "[email protected]",
"subject": "oss-mailer test",
"props": { "url": "https://example.com/verify" }
}'The health request should return the configuration page, and the send request should return HTTP 200 with a message ID. Use a recipient verified in SES while the AWS account is in sandbox mode. This direct request is also a useful fallback when an agent needs to distinguish a mailer-service problem from an application-integration problem.
Configure the installed SDK
After installing the package, set the service URL and API key in your application's server-side environment:
MAILER_URL=https://your-mailer.vercel.app
MAILER_API_KEY=your-service-api-keyThese variables belong to your application, not to the oss-mailer package. Do not put them in browser code.
Send your first email
Create the client once in server-side application code:
import { createMailerClient } from 'oss-mailer'
const mailer = createMailerClient({
url: process.env.MAILER_URL!,
apiKey: process.env.MAILER_API_KEY!,
})
const result = await mailer.sendMail({
category: 'magic_link',
to: '[email protected]',
subject: 'Sign in to your account',
props: {
url: 'https://app.example.com/auth/verify?token=abc123',
},
})
console.log(result)magic_link sends immediately. promotional and update messages are queued through QStash and return a queue job ID.
Email categories
Magic links
The built-in template expects a url:
await mailer.sendMail({
category: 'magic_link',
to: '[email protected]',
subject: 'Sign in to your account',
props: { url: 'https://app.example.com/auth/verify?token=abc123' },
})Product updates
The built-in update template expects subject, body, and unsubscribeUrl in props:
await mailer.sendMail({
category: 'update',
to: '[email protected]',
subject: 'Your weekly update',
props: {
subject: 'Your weekly update',
body: 'Here is what changed this week.',
unsubscribeUrl: 'https://app.example.com/email/preferences',
},
})Promotional email
The built-in promotional template uses the same props as update:
await mailer.sendMail({
category: 'promotional',
to: '[email protected]',
subject: 'What is new',
props: {
subject: 'What is new',
body: 'Read about our latest improvements.',
unsubscribeUrl: 'https://app.example.com/email/preferences',
},
})For any category, you can provide pre-rendered HTML instead:
props: { html: '<p>Your email content</p>' }NextAuth v5
Use the included provider in your auth.ts:
import NextAuth from 'next-auth'
import { MailerEmailProvider } from 'oss-mailer/nextauth'
export const { handlers, auth } = NextAuth({
providers: [
MailerEmailProvider({
mailerUrl: process.env.MAILER_URL!,
apiKey: process.env.MAILER_API_KEY!,
from: '[email protected]',
appName: 'My App',
}),
],
})The actual sender is the MAILER_FROM value configured on the mailer service.
Responses and errors
An immediate send returns { success: true, messageId, provider, sentAt }. A queued send returns { queued: true, jobId }.
import { createMailerClient, isMailerError } from 'oss-mailer'
try {
await mailer.sendMail({
category: 'magic_link',
to: '[email protected]',
subject: 'Sign in',
props: { url: 'https://example.com/verify' },
})
} catch (error) {
if (isMailerError(error)) {
console.error(error.code, error.detail)
}
}Troubleshooting
401 Unauthorized:MAILER_API_KEYin your application does not exactly match the key on the deployed service.- SES rejects the message as “not verified”: verify
MAILER_FROMand, in SES sandbox mode, the recipient address. - Queued messages fail: check
QSTASH_TOKEN, both QStash signing keys, and thatDELIVER_URLis public and ends with/api/queue/deliver. - Configuration is missing: open the service's
/settingspage, add the missing deployment variables, and redeploy.
API reference
The client sends authenticated POST requests to <MAILER_URL>/api/send with this JSON shape:
{
"category": "magic_link",
"to": "[email protected]",
"subject": "Sign in",
"props": { "url": "https://example.com/verify" }
}Authentication uses Authorization: Bearer <MAILER_API_KEY>.
