← Back to Insights

RaiseError: skipping one recipient without stopping 400,000 emails

Pierre Frin September 2026 7 min read
un client sans conseiller, deux issues envoi de 400 000 rendu en cours RaiseError(msg) job arrêté, 0 email parti RaiseError(msg, true) un destinataire écarté, l'envoi continue journal Marketing Cloud Engagement · AMPscript · garde-fous d'envoi

A retail bank sends an appointment booking email every quarter. The adviser name and diary link are read from a reference table, keyed on the customer branch code. In QA, every test branch has an adviser, and the email passes validation without comment.

The day before the send, a developer adds a safeguard. If the adviser cannot be found, the code calls RaiseError("Conseiller introuvable"). The intent is sound, nobody wants an email greeting a nameless adviser.

The next morning, the send of 400,000 emails stops after a few minutes, in error. Three branches opened the previous month had no adviser in the reference table yet. And nobody knows how many customers are affected, or which ones.

The second parameter decides everything

The function takes one mandatory parameter and four optional ones. The second is the one missing from the incident.

RaiseError(errorMessage, boolSkipCurrentOnly, apiErrorCode, apiErrorNumber, boolPreserveDataExt)
ParameterTypeRole
errorMessageString, mandatoryMessage attached to the error
boolSkipCurrentOnlyBoolean, false by defaulttrue skips the current recipient and lets the send carry on, false stops the whole job
apiErrorCodeStringFree-form error code, useful for API calls
apiErrorNumberNumberFree-form error number
boolPreserveDataExtBooleantrue keeps the data extension writes made before the error, false rolls them back

A call with a single parameter is an emergency stop, then, not a filter. The confusion is easy to make, because the code reads like a safety condition while it behaves like a fire alarm.

⚠️ The QA trap: a safeguard that never fires in QA has not been tested. Your test data sets hold clean customers, and that is exactly what stops anyone noticing the difference between the two modes.

Three answers to missing data

Before writing a single line, sort every piece of data in the email by what should happen when it is missing. The sorting is done once, and it holds for every email in the campaign.

SituationExampleAnswer
Nice-to-have dataFirst name missingFallback content with If Empty(...)
Data this recipient cannot do withoutNo adviser found for their branchSkip and log, RaiseError(msg, true, ...)
Anomaly affecting the whole sendCampaign switched off, reference table emptyStop the send, RaiseError(msg, false)
Population known in advanceCustomers with no branch attachedExclude upstream with a SQL query

Excluding a known population with a query

The last line counts as much as the others. Salesforce recommends keeping the function for error handling rather than segmentation. If you know before the send that a population has to be dropped, removing it with a query costs less than evaluating it recipient by recipient during rendering, and the targeted volume shown before the send becomes accurate again.

Ordering the AMPscript blocks in the email

The order of the blocks mirrors the order of the decisions. The first block in the email checks what affects everybody, campaign parameters and reference tables. The second drops incomplete recipients. Only the third adapts the copy.

%%[
  Var @actif
  Set @actif = Lookup("Parametres_Campagne", "Actif", "CodeCampagne", "RDV_T3")

  If Empty(@actif) Then
    RaiseError("Parametres_Campagne : ligne RDV_T3 absente", false)
  EndIf
]%%

That check runs for every recipient, but it is the first one that triggers the stop. When the condition rests on a date, call Now(true) rather than Now(): the function returns the job start time, the same value for everyone, and the result does not shift mid-send. Write the messages without accents, they are meant for support teams and technical logs.

Log the ones you drop

Skipping a recipient without knowing which one solves half the problem. The answer is one line written to a data extension just before the error is raised.

%%[
  Var @cle, @job, @agence, @conseiller

  Set @cle    = _subscriberkey
  Set @job    = jobid
  Set @agence = Trim(AttributeValue("CodeAgence"))

  If not Empty(@agence) Then
    Set @conseiller = Lookup("Conseillers", "NomConseiller", "CodeAgence", @agence)
  EndIf

  If Empty(@conseiller) Then

    UpsertDE("Journal_Abonnes_Ignores", 3,
             "SubscriberKey", @cle,
             "JobID", @job,
             "Motif", "CONSEILLER_ABSENT",
             "Detail", Concat("Agence : ", @agence))

    RaiseError("Conseiller introuvable", true, "CONSEILLER_ABSENT", 1001, true)
  EndIf
]%%

Three details that keep the log filled

Three details separate a full log from an empty one. The write comes before the call, because the code stops at the error and never reaches the next line. The fifth parameter is set to true, otherwise the write is rolled back with everything else. And UpsertDE is used rather than InsertDE: the AMPscript of a send is processed in batches at the last step, the same recipient can be rendered more than once, and the upsert avoids a failure on a row that already exists.

The second argument of UpsertDE declares how many search pairs follow, three here. If that number does not match the pairs supplied, the function throws an exception. Read it again on every change to the block, it is the most common mistake on this function.

Counting skipped recipients in SQL

The day after the send, one query gives the count per branch, ready to pass to the team that owns the adviser table.

SELECT
    j.Detail,
    COUNT(*) AS NbClients
FROM Journal_Abonnes_Ignores AS j
WHERE j.Motif = 'CONSEILLER_ABSENT'
  AND j.DateTrace >= DATEADD(DAY, -1, GETDATE())
GROUP BY j.Detail

Set a retention rule on that log the day you create it. It holds nothing but technical identifiers and a reason, yet it is not meant to grow for ever, and a few months is ample to work through the gaps found after a send.

💡 Send log or dedicated data extension: the send log is an account-level data extension populated automatically on every send. Salesforce advises keeping it to roughly ten custom fields and a short retention, in the region of ten days. For a focused log of skipped recipients, a dedicated table stays easier to query.

What the function does elsewhere

A skipped recipient does not vanish from the system, they simply change status in several places. The documentation spells out three effects that teams tend to discover too late.

Repeating the safeguard inside a journey

That last point deserves attention when a journey chains several messages on the same piece of data. Dropping a customer from the first email does not protect them from the second. Either you repeat the check in each send, or you set an exit condition in the journey itself.

On how visible the error message is, stay cautious. It serves technical logs and support above all, and it is hard to find in the interface. Write it without accents and keep the same code in the log and in the third parameter; the matching will happen through that code, not through the sentence.

If you are coming from Adobe Campaign

In Adobe Campaign, exclusions happen in the workflow or through typology rules applied when the delivery is analysed. The result is a readable exclusion report, where every dropped recipient appears with a reason. You open the analysis, you see the volumes, you decide whether to deliver.

Marketing Cloud offers no such view. The log built above plays the part of that exclusion report, and it falls to you to build it before the first send, not after the first incident. As for the global stop, it resembles a control rule that blocks a delivery, except that it fires during the send rather than before it. A stop at six in the morning with no notification will only be found when the team arrives, so plan your Automation Studio alerts accordingly.

The habit worth keeping

Before publishing an email that calls this function, open the code and count the parameters. One parameter means the first incomplete recipient stops the entire send. Then create a test data extension with one row per branch of the code, nominal case, missing nice-to-have data, missing required data, and check every branch in preview before scheduling anything.

Frequently asked questions

Why does RaiseError stop the entire send?

Because the second parameter, boolSkipCurrentOnly, is false by default. A call with a single parameter is therefore an emergency stop rather than a filter: the first incomplete recipient stops the whole job. Set to true, the function skips the current recipient and lets the send carry on.

How do you know which recipients were skipped?

You write a row to a data extension just before raising the error, because the code stops at the error and never reaches the next line. Use UpsertDE rather than InsertDE, and set the fifth parameter of RaiseError to true, otherwise the write is rolled back with everything else. The day after the send, a query on that log gives the count per branch.

Are emails skipped by RaiseError billed?

No, they are not counted towards billed consumption. They do appear in tracking and reporting figures, which explains the gap between the targeted volume and the sent volume.

Does a skipped recipient leave the journey?

No. In a journey the contact is removed from one send only: they carry on through the journey and will receive the later emails, which therefore need safeguards of their own. Either you repeat the check in each send, or you set an exit condition in the journey itself.

Should RaiseError be used to exclude a population known in advance?

No. Salesforce recommends keeping the function for error handling rather than segmentation. If you know before the send that a population has to be dropped, removing it with a query costs less than evaluating it recipient by recipient during rendering, and the targeted volume shown before the send becomes accurate again.

More in this series
Pierre Frin
Grokium founder · CRM consultant · Adobe Campaign Classic and Salesforce Marketing Cloud Email Specialist certified

Do your AMPscript safeguards hold up in production?

I can review your personalisation blocks, your exclusions and your send logs, then send you a written diagnosis with the fixes to apply.

Describe your context →