MailKite
Get your API key
All guides

Programmatic email: events, sequences, and scheduled sends

Reviewed

Programmatic email is email triggered or assembled by software: an account event, a scheduled digest, or a sequence of conditional reminders. For a backend developer, the result should be a workflow that survives retries and stops when its purpose is complete. Choose a single send for an immediate message, a scheduled send for one known future message, and a sequence when later events determine the next step.

“Programmatic” describes execution, not message purpose. A generated newsletter is still a newsletter; a scheduled promotion is still promotional. MailKite, which we build, supports these orchestration primitives, but its terms prohibit unsolicited bulk or commercial mail, sending without consent, and sending after opt-out. The same eligibility rules apply whether a person clicks Send or a worker calls an API.

Pick the primitive from the stopping rule

Start with the simplest question: what could make this message unnecessary? If nothing changes before an immediate send, use a send job. If a known future message can be canceled by ID, a scheduled send may suffice. If completion, account state, or a reply changes later steps, use conditional orchestration.

RequirementStarting primitiveApplication invariant
Receipt after a completed transactionImmediate send jobOne intended receipt per transaction revision
Reminder at a known future instantScheduled sendSave the schedule ID and define cancellation
Onboarding that ends after setupSequence or durable workflowCompletion prevents subsequent reminders
Monthly edition for subscribersBroadcast/campaignCurrent subscription governs eligibility
Personalized digest for many usersFan-out queue and batchesIndependent recipient outcomes

The table is design guidance. For the subscriber lifecycle, read programmatic newsletters. For many independently rendered messages, read bulk email.

Use an outbox between business state and email

Suppose an application marks an account created, then crashes before scheduling its welcome message. Sending inside the database transaction introduces a different problem: the provider may accept mail before the transaction rolls back. Use a transactional outbox, a table of pending events written alongside the business change, then let a worker publish committed events.

Each outbox record needs a stable event identity, recipient identity, business-object ID, payload version, and processing state. The worker can repeat a request after a temporary failure without inventing a new event. Keep the event payload small and specific. Don’t move a customer’s full profile into an email event merely because JSON allows it.

This pattern guarantees that your application retains intent. It doesn’t automatically guarantee exactly-once mail delivery. Event recording, sequence enrollment, message submission, and delivery are separate boundaries, each with its own retry contract.

Deduplicate facts at the event boundary

MailKite’s sequence documentation describes sendEvent with a dedupeKey: repeating the key returns the original event rather than recording another event and triggering duplicate enrollment. Use the original business event’s identity, not a timestamp generated on each retry.

Choose the key’s scope deliberately. A payment attempt and an invoice are different objects. If each failed attempt is a distinct event, include the attempt identity. If the business rule is one recovery workflow per invoice, enforce that enrollment rule independently. A transport accepting one event twice is a different problem from your application emitting two distinct events for the same business situation.

MailKite also documents direct enrollment with startSequence, taking a sequence name or ID. Use direct enrollment when an operator or script knows the intended automation. Use an event when the application knows what happened and configured triggers should decide what reacts. An event may reach multiple listeners; don’t assume it selects exactly one sequence.

Build the exit before the reminder

For onboarding, define an explicit completion event, then make every future reminder conditional on incomplete setup. For a payment reminder, correlate the exit with the invoice, not merely the customer’s email address. One customer can have two open invoices. A recipient-level stop can be broader than the business object you intended to cancel.

MailKite documents wait_for_event, conditional branches, and direct stopSequence cancellation. A direct start can include a cancelKey, letting your application cancel by its own business identity. Don’t assume that an event-triggered enrollment automatically inherits a cancellation key or that a named completion event matches every business object correctly. Verify the exact exit/filter contract you configure.

Remember the late-stage race: completion may occur while a send is being submitted. Recheck state as close to submission as your architecture permits. A cancellation stops pending work; it doesn’t recall accepted mail. Record the relevant times so support can explain an already-submitted reminder without guessing.

Scheduling is persistent state

MailKite’s sending reference accepts future scheduledAt values and returns a scheduled-send identifier. Individual scheduled sends can be canceled through their documented endpoint. Personalized scheduled batches create a separate cancelable identifier for each recipient.

Save those IDs with the originating job. If a scheduler request times out, the outcome is unknown, not automatically absent. Resolve it before creating another schedule. Use absolute instants for persisted work and display the intended local timezone separately. A relative time string is convenient at submission, but your own record should explain when the message was meant to run.

Observe the whole chain

Trace business event, outbox record, provider event, enrollment, step, and message ID. Report the state at each boundary: recorded, enrolled, waiting, submitted, rejected, canceled, or completed. A successful event API response says the fact was recorded; it doesn’t prove a later email reached a mailbox.

Consume bounces, complaints, and replies as new facts. Authenticate their webhooks and make duplicate processing harmless. Keep marketing preferences separate from essential account communication, while still respecting provider suppressions and the actual policy for each message. Don’t use an open pixel as the sole proof that a customer completed a product action.

Implement the workflow in JavaScript. Rehearse a repeated event, completion before the reminder, a restart during a wait, and an ambiguous submission. Inspect the resulting ledger rather than assuming a lack of exceptions means success. For a transport choice that fits this workload, consult the bulk email API comparison; for outreach-specific decisions, use the cold email architecture guide.