> ## Documentation Index
> Fetch the complete documentation index at: https://ahasend.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> AhaSend stores all message data in the EU. See /facts.
> The API base URL is https://api.ahasend.com. Send the API key in the Authorization: Bearer header. See /api-reference/authentication.
> Use send.ahasend.com for SMTP in the EU, or send-us.ahasend.com as a US connection point forwarding to the EU. Ports 25, 587 and 2525 use STARTTLS. Port 465 is not supported. See /smtp.
> For send-only integrations, create an API Key v2 under Credentials → Add and scope it to messages:send:{your-domain}. See /send-api/credentials and /api-reference/scopes.

# Send Email from Google Cloud Functions (Node.js)

> Send email from Google Cloud Run functions in Node.js with AhaSend: use the Functions Framework, protect the endpoint and run local sandbox tests.

AhaSend lets you send transactional email from Google Cloud Run functions in Node.js with the official SDK; this guide runs the same HTTP function locally with the Functions Framework.

Cloud Run functions is the current name for the service previously called Cloud Functions.

## Prerequisites

Use a [verified sending domain](/docs/domains), your account ID, and a domain-scoped [send-only API v2 key](/docs/send-api/credentials). Use [sandbox mode](/docs/send-api/sandbox) for every test. Management tasks need a separate [full API key](/docs/api-reference/authentication).

Use Node.js 22 and the [Node.js Functions Framework](https://github.com/GoogleCloudPlatform/functions-framework-nodejs). A Google Cloud account is only needed when you deploy.

## Install the Dependencies

```bash theme={null}
npm init -y
npm install --save-exact @ahasend/sdk@0.2.1 @google-cloud/functions-framework@3
npm pkg set main=index.cjs
```

## Try a Sandbox Send

Run this short SDK check locally before adding the longer server integration below. Use a [verified sender](/docs/domains), a [send-only API key](/docs/send-api/credentials), and your account ID. It sends to a reserved test address with `sandbox: true`, so no email is delivered.

In the server project directory, add `.env` to `.gitignore`, then create `.env` with `AHASEND_API_KEY`, `AHASEND_ACCOUNT_ID`, and `AHASEND_FROM` (an address on your verified domain). Keep these values out of browser code and AI prompts. Use Node.js 22 or newer. Save this as `quick-send.mjs`:

```js quick-send.mjs theme={null}
import { AhaSendClient } from "@ahasend/sdk";
try {
  const client = AhaSendClient.fromEnv();
  const result = await client.messages.send({
    from: { email: process.env.AHASEND_FROM },
    recipients: [{ email: "recipient@example.com" }],
    subject: "My first sandbox email",
    text_content: "Hello from AhaSend",
    sandbox: true,
  });
  const statuses = result.data.map((entry) => entry.status);
  console.log({ statuses });
  if (!statuses.length || statuses.some((s) => !["queued", "scheduled"].includes(s)))
    throw new Error("A recipient was rejected");
} catch {
  console.error("Sandbox send failed; check your credentials, sender and account.");
  process.exitCode = 1;
}
```

Run `node --env-file=.env quick-send.mjs` from that directory. The output lists each recipient status; `queued` or `scheduled` means accepted for sandbox processing. The process exits with a failure if the request or any recipient fails. Continue below for the full integration.

## Add the Shared Sending Code

Save this as `send-email.cjs` beside the function file. Configure `AHASEND_API_KEY`, `AHASEND_ACCOUNT_ID`, `AHASEND_FROM`, `AHASEND_TO` and a long random `SEND_TOKEN` on the server. Set `AHASEND_TO=recipient@example.com` and `AHASEND_DELIVERY_MODE=sandbox` for local testing. Keep a sandbox credential until you are ready for real delivery; changing the mode to `live` alone cannot override a sandbox credential.

```js send-email.cjs theme={null}
const { createHash, timingSafeEqual } = require("node:crypto");
const { AhaSendClient } = require("@ahasend/sdk");
const digest = (value) => createHash("sha256").update(value).digest();
let client;
exports.sendEmail = async (body, authorization = "") => {
  const token = process.env.SEND_TOKEN;
  if (!token) return [500, { error: "Missing server configuration" }];
  if (!timingSafeEqual(digest(authorization), digest(`Bearer ${token}`)))
    return [401, { error: "Unauthorized" }];
  if (!body || typeof body.event_id !== "string" ||
      !/^[A-Za-z0-9_-]{8,64}$/.test(body.event_id))
    return [400, { error: "event_id must be 8-64 letters, digits, _ or -" }];
  const mode = process.env.AHASEND_DELIVERY_MODE || "sandbox";
  if (!["sandbox", "live"].includes(mode) ||
      !["AHASEND_API_KEY", "AHASEND_ACCOUNT_ID", "AHASEND_FROM", "AHASEND_TO"]
        .every((name) => process.env[name]))
    return [500, { error: "Missing or invalid server configuration" }];
  try {
    client ||= AhaSendClient.fromEnv();
    const result = await client.messages.send({
      from: { email: process.env.AHASEND_FROM },
      recipients: [{ email: process.env.AHASEND_TO }],
      subject: "Welcome", text_content: "Your account is ready.",
      sandbox: mode === "sandbox",
    }, { idempotencyKey: `${mode}-welcome-${body.event_id}` });
    const statuses = result.data.map((item) => item.status);
    if (!statuses.length || statuses.some((s) => !["queued", "scheduled"].includes(s)))
      return [422, { error: "A recipient was rejected" }];
    return [202, { statuses }];
  } catch {
    return [502, { error: "Email request failed" }];
  }
};
```

The caller supplies only the business event ID. For a real signup, read the recipient from your database after checking the caller's access. Save the exact message with that event ID and reuse both when retrying. Read the [idempotency limits](/docs/api-reference/idempotency) before retrying an uncertain send. Add request limits at the hosting layer and log an internal event ID instead of credentials or message bodies.

## Create the HTTP Function

Save this as `index.cjs`:

```js index.cjs theme={null}
const functions = require("@google-cloud/functions-framework");
const { sendEmail } = require("./send-email.cjs");
functions.http("sendEmail", async (req, res) => {
  if (req.method !== "POST") return res.status(405).end();
  if ((req.rawBody?.length || 0) > 16384) return res.status(413).end();
  if (!req.is("application/json")) return res.status(415).end();
  const [status, result] = await sendEmail(req.body, req.get("authorization") || "");
  res.status(status).json(result);
});
```

## Run with the Functions Framework

If port 8080 is busy, set `PORT` to an unused local port in both shells. The commands below default to 8080.

Export the shared code's environment variables before starting the local server:

```bash theme={null}
npx functions-framework --target=sendEmail --port="${PORT:-8080}"
```

In another shell with `SEND_TOKEN` set, run:

```bash theme={null}
curl --fail-with-body "http://localhost:${PORT:-8080}"   -H "Authorization: Bearer $SEND_TOKEN" -H 'Content-Type: application/json'   -d '{"event_id":"local-google-node-001"}'
```

Expect HTTP 202 with acceptance statuses. The handler refuses non-POST requests, missing tokens and invalid event IDs. A fresh test needs a new event ID; an exact retry keeps the old one.

## Prepare the Hosted Function

Choose the Node.js 22 runtime and entry point `sendEmail`. Follow [Google's function deployment steps](https://cloud.google.com/run/docs/deploy-functions). Put secrets in [Secret Manager](https://cloud.google.com/run/docs/configuring/services/secrets), allow only trusted invokers, and cap requests before they create mail jobs. When Cloud Run IAM and the app token are both in use, put the Google identity token in `X-Serverless-Authorization` and keep the app token in `Authorization`, as described in [Google's service authentication guide](https://cloud.google.com/run/docs/authenticating/service-to-service).

Use [webhooks](/docs/integrations/webhooks) to track final delivery. Also see [Google functions in Python](/docs/guides/google-cloud-functions-python), [Go](/docs/guides/google-cloud-functions) and [Node.js SMTP](/docs/smtp/nodejs).

## Related Guides

* Before sending: [verify a domain](/docs/domains) and [create a send-only key](/docs/send-api/credentials).
* Other ways to send: [REST API](/docs/send-api/send-email), [SMTP](/docs/smtp), [CLI quickstart](/docs/cli/quickstart), [Node.js SDK](/docs/guides/nodejs-sdk) and [Go SDK](/docs/guides/go-sdk).
* Request rules: [API authentication](/docs/api-reference/authentication), [scopes](/docs/api-reference/scopes), [idempotency](/docs/api-reference/idempotency), [errors](/docs/api-reference/errors) and [rate limits](/docs/api-reference/rate-limits).
* Testing and events: [sandbox mode](/docs/send-api/sandbox), [CLI webhook testing](/docs/cli/webhook-testing), [event payloads](/docs/api-reference/webhooks), [signature verification](/docs/api-reference/webhooks/security) and [delivery retries](/docs/api-reference/webhooks/retry-policy).
* Data and limits: [retention](/docs/retention), [tracking](/docs/tracking/open-tracking) and [plans and feature availability](/docs/facts#plans-and-features).
