Pipedream and Groundmail

Send email from a Pipedream workflow with a Node.js code step that calls the Groundmail API, or with the Send any HTTP Request action. The code step is the better fit: it can retry and handle errors, and the HTTP action cannot.


Prerequisites

  • A Groundmail account with a verified sending domain.
  • A Groundmail API key. Create one under API Keys in the dashboard. Choose Send only, and limit it to your sending domain with the allowed domains option. See API keys.
  • A Pipedream workflow with a trigger.

1. Store the key

In Pipedream, go to Settings, then Environment Variables, and click New Variable. Use the key GROUNDMAIL_API_KEY and paste your Groundmail key as the value. New variables are secret by default. For a variable that belongs to one project, open the project and use Variables instead.

Do not log the key in a step: logged values appear in the step's logs.


2. Add a code step

Add a Node.js code step after your trigger and paste this. It uses the axios wrapper from @pipedream/platform, which Pipedream provides in code steps:

import { axios } from "@pipedream/platform"

export default defineComponent({
  async run({ steps, $ }) {
    return await axios($, {
      url: "https://api.groundmail.dev/emails",
      method: "post",
      headers: {
        Authorization: `Bearer ${process.env.GROUNDMAIL_API_KEY}`,
        "Idempotency-Key": `welcome-${steps.trigger.event.user_id}`,
      },
      data: {
        from: "notifications@yourdomain.com",
        to: steps.trigger.event.email,
        subject: "Welcome",
        html: "<p>Thanks for signing up.</p>",
        text: "Thanks for signing up.",
      },
    })
  },
})

Change steps.trigger.event.email and steps.trigger.event.user_id to the fields your trigger provides. The Idempotency-Key header makes a retried run harmless: for 24 hours Groundmail returns the original response instead of sending twice. Use a value that is unique per event. See Idempotency.

The step returns the response body: { "id": "msg_…", "status": "sent" }. If the request fails, Pipedream's axios wrapper surfaces the response body, which is a Groundmail error with a code and a hint (see Errors). Wrap the call in try...catch if you want to retry or branch on code.

Without code

The Send any HTTP Request action does the same without code. Set the method to POST, the URL to https://api.groundmail.dev/emails, add an Authorization header with Bearer gm_…, and send the JSON body above as the request body. Pipedream notes this action halts the workflow when the request fails and cannot retry on its own.


3. Test it

Use a test key (gm_test_…, created by ticking Test key on the API Keys page) while you build. Groundmail runs the whole pipeline but delivers nothing, so any recipient works, and the response is:

{ "id": "msg_…", "status": "sent", "test": true }

You can also send with a live key to delivered@simulator.groundmail.dev, which never reaches a real inbox. Use bounce@simulator.groundmail.dev to see a hard bounce. The full list is in Testing. The email shows up under Emails in the Groundmail dashboard. When you are done, swap the variable to a live key.


4. React to delivery events (optional)

Pipedream can receive Groundmail webhooks. Add an HTTP / Webhook trigger to a second workflow, copy its URL, and register it:

curl -X POST https://api.groundmail.dev/webhooks \
  -H "Authorization: Bearer $GROUNDMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://YOUR-ENDPOINT.m.pipedream.net",
    "events": ["email.delivered", "email.bounced", "email.complained"]
  }'

Troubleshooting

401 unauthorized. The header must be Authorization: Bearer gm_…. If process.env.GROUNDMAIL_API_KEY is undefined, the variable name does not match (names are case-sensitive).

403 forbidden. The domain in from is not verified on your account, or the account is still on the sandbox sender, which only sends to your own account email. Verify the domain under Domains. A 403 domain_not_allowed means the key is limited to other domains, and insufficient_scope means the key cannot send.

400 validation_error. A required field is missing or empty, or to is not a valid address. The message and details say which field.

422 validation_error with suppressed. Every recipient is on your suppression list or was held back as unable to receive mail. Do not retry the same address; see Suppressions.

429. You hit a sending limit. See Sending limits.