Temporal.io Workflow Activities: Reliable multi-step onboarding email execution with AutoSend

How do you implement durable email delivery activities inside Temporal TypeScript workflows using AutoSend with automatic activity timeouts and heartbeats?

Temporal.io TypeScript Workflow Activity + AutoSend Integration

In Temporal, defining email delivery as a separate Activity guarantees durable execution, automatic exponential backoff retries on transient network/5xx errors, and explicit classification of non-retryable client errors (such as 400 Bad Request or invalid recipient emails).

1. Define the Email Activity (activities/email.ts)

// activities/email.ts
import { ApplicationFailure } from '@temporalio/activity';

export interface WelcomeEmailInput {
  email: string;
  name: string;
}

export async function sendWelcomeEmailActivity(input: WelcomeEmailInput): Promise<{ messageId: string }> {
  const response = await fetch('https://api.autosend.com/v1/mails/send', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.AUTOSEND_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      from: 'Founder <founder@yourdomain.com>',
      to: [input.email],
      subject: `Welcome to the platform, ${input.name}!`,
      html: `<p>Hi ${input.name}, thanks for joining!</p>`,
    }),
  });

  if (!response.ok) {
    const errorBody = await response.text();
    // Non-retryable on client/validation error (e.g. 400 Bad Request, unverified domain)
    if (response.status === 400 || response.status === 422) {
      throw ApplicationFailure.nonRetryable(
        `AutoSend validation failure (${response.status}): ${errorBody}`,
        'INVALID_EMAIL_PAYLOAD'
      );
    }
    // Retryable on rate limits (429) or transient 5xx server errors
    throw new Error(`AutoSend temporary failure (${response.status}): ${errorBody}`);
  }

  const result = await response.json();
  return { messageId: result.id };
}

2. Execute within an Onboarding Workflow (workflows/onboarding.ts)

In your workflow definition, proxy the activity with a designated startToCloseTimeout and retry policy:

// workflows/onboarding.ts
import { proxyActivities, sleep } from '@temporalio/workflow';
import type * as activities from '../activities/email';

const { sendWelcomeEmailActivity } = proxyActivities<typeof activities>({
  startToCloseTimeout: '1 minute',
  retry: {
    initialInterval: '2s',
    backoffCoefficient: 2,
    maximumInterval: '30s',
    maximumAttempts: 5,
    nonRetryableErrorTypes: ['INVALID_EMAIL_PAYLOAD'],
  },
});

export async function userOnboardingWorkflow(user: { email: string; name: string }): Promise<void> {
  // Step 1: Send immediate welcome email
  await sendWelcomeEmailActivity(user);

  // Step 2: Durable timer (3-day follow-up)
  await sleep('3 days');

  // Step 3: Send check-in email
  // (Additional workflow steps continue seamlessly even across worker restarts)
}

Relevant Documentation