Email imports: configuration and diagnostics
Create or save a job email-import configuration
/a/ImportJobsEmail_save
Create or save a job email-import configuration
When to use this endpoint
Create an email-import routing address or replace the extraction settings for an existing job configuration. Obtain an existing ID from the Jobs by Email list rather than treating this as an upsert by name.
Authentication and permissions
Authenticate with signed API-key fields or a logged-in CRM session. The handler calls Core_requireLogin(); it has no separate page, administrator, or role-permission check.
The account must have Import By Email enabled (employer|features bit 32768). There is no separate page, administrator, or role-permission check in these handlers.
The shared mutation helper selects the global database explicitly and locks the authenticated tenant owner and selected configuration rows. Every target must belong to that tenant and match this route’s configuration type: 0 job, 1 customer, or 2 task. Missing, foreign-owned, and wrong-type targets return the same that import does not exist error (the capture setter retains its dedicated not-found message).
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | Integer | Yes | Use 0 to create a new filter; customer/task calls default to zero when omitted. An existing positive integer updates only an owned row of the same family. Negative, fractional, blank, and malformed values are rejected. Job saves still require the id key. |
table_start | String | Yes | Literal table delimiter, not a regular expression. May be blank. The four values are JSON-encoded as start, end, item_start, and item_end in regexps_table_delimiters. |
table_end | String | Yes | Literal table delimiter, not a regular expression. May be blank. The four values are JSON-encoded as start, end, item_start, and item_end in regexps_table_delimiters. |
table_item_start | String | Yes | Literal table delimiter, not a regular expression. May be blank. The four values are JSON-encoded as start, end, item_start, and item_end in regexps_table_delimiters. |
table_item_end | String | Yes | Literal table delimiter, not a regular expression. May be blank. The four values are JSON-encoded as start, end, item_start, and item_end in regexps_table_delimiters. |
form_id | Integer | Yes | Default job Form ID, converted with intval(). Presence is required, but zero, existence, active state, and Form type are not validated here. |
regexps | JSON text | Yes | JSON text containing rows [pattern,replacement,destination,stripHtml]. Patterns require PCRE delimiters; SUBJECT: selects the subject. The worker skips blank, invalid, and nonmatching patterns, performs one replacement, and ignores a result identical to the actual input. A truthy fourth item strips HTML. The save does not validate rule semantics. |
tables | JSON text | Yes | Stored verbatim as regexps_table. Worker rows [pattern, replacement, tableFormID, columnName] extract columns after literal delimiter matching of non-spreadsheet attachments. No rule or table-Form validation is performed here. |
spreadsheets | JSON text | Yes | Spreadsheet rules are JSON text containing rows [sheetNumber, columnLetters, rowNumber, destination]. Sheet and row numbers are one-based. The worker reads XLS/XLSX attachments, uses uppercase column letters through its legacy index helper, and can overwrite an earlier body-rule value with a spreadsheet value. Presence is required even when unused; send []. |
form-by-regexp | String | Yes | Delimited PCRE expression applied to the body to extract a Form name with replacement \1. A matching Form name can override form_id in the job worker. May be blank. |
sample_email | String | Yes | Stored sample body for the rule editor. Blank is accepted. Saving it does not process an email. |
sample_email_subject | String | No | Trimmed sample subject, default "". Omitting it on update clears the previous sample subject. The handler does not use it as an outbound email subject. |
Example request
$result = FM_api('ImportJobsEmail_save', [
'id' => '0',
'table_start' => '',
'table_end' => '',
'table_item_start' => '',
'table_item_end' => '',
'form_id' => '27',
'regexps' => json_encode([['/.*Name: ([^;]+).*/', '$1', 'Client Name', false]]),
'tables' => '[]',
'spreadsheets' => '[]',
'form-by-regexp' => '',
'sample_email' => 'Name: Example Services; Email: office@example.com',
'sample_email_subject' => 'Website enquiry',
]);
Example response
{"ok":1,"id":1042}
Success contains integer ok and the newly inserted or updated integer id. Insert, owner lookup, update, and global feature-marker failures are reported explicitly rather than returning null or a failed insert ID under success.
Missing, foreign-owned, and wrong-type existing IDs are errors. The global configuration and feature marker commit together. A later tenant-history failure adds saved:1 and a no-replay warning.
Errors and unusual responses
| Response | Condition |
|---|---|
{"error":"not logged in"} | No authenticated tenant context is available. Invalid signed credentials can be rejected by the dispatcher before the handler runs. |
{"error":"missing parameter: KEY"} | A required parameter is absent or null. KEY is the exact first missing key in handler order: id, table_start, table_end, table_item_start, table_item_end, form_id, regexps, tables, spreadsheets, form-by-regexp, sample_email. These presence checks precede authentication. |
{"error":"Import By Email feature not enabled"} | The account feature bit is disabled. |
{"error":"that import does not exist"} | No row of this family owned by the authenticated tenant was found. Bulk edits preflight every selected row before writing. |
{"error":"email import write failed"} | A checked global write or insert-ID retrieval failed; the global transaction is rolled back. |
{"error":"email import read failed"} | The checked lookup failed; no configuration write is committed. |
{"error":"email import transaction failed"} | Beginning or committing the global transaction failed. Inspect the configuration before retrying an uncertain commit. |
Legacy application errors may be returned with HTTP 200. Check the decoded response for error, not only the HTTP status.
Behaviour and edge cases
- This call changes configuration only. It does not fetch a mailbox, parse a message, or synchronously create a customer, job, or task. The background email processor consumes the stored configuration later; this API provides no processing-time guarantee.
- The save replaces every listed required setting and the sample subject; it is not a partial patch. Creation explicitly assigns job type
0; updates require an existing job-type row. - Rules and stored JSON are accepted as strings without parsing, regex validation, destination allowlists, or Form validation. Encode nested rule arrays before passing them to the signing helper. Invalid mappings can therefore save successfully but fail to produce a usable import later.
- At processing time, body/subject rules and attachment rules feed the job worker. Customer matching obeys
customer-name-allow-duplicate; an existing activeJob Refprevents the ordinary new-job branch. These checks happen in the worker, not in this save.
Side effects
- The primary configuration write uses checked global persistence, advances Unix-seconds
last_edited, and requestsimport_filterscache invalidation. A changed write and any global feature-marker update commit together; a failure rolls back the global transaction. - Change history is written to the authenticated tenant after the global commit, once per changed configuration. These are separate database contexts, not an atomic cross-database audit. If history fails, the committed result includes
saved:1and a warning to avoid replaying the write. For a SQL-string deletion response, the warning result is an object withok:1and the original string underresult. - No mailbox is fetched and no business object is imported synchronously by this configuration mutation.
- Creates a cryptographically random 32-character routing token only on insertion. The recipient address is the token at
import.fieldmotion.com. - On creation, sets global
employers.features_usedbit4in the same checked global transaction as the new filter.
See it in Fieldmotion
CRM1
In Imports → Jobs by Email, create or edit an entry and save the rule editor. Filter for ImportJobsEmail_save. The editor sends the full required delimiter/Form/mapping payload.
CRM2
In Jobs by Email, save the edit widget and filter for ImportJobsEmail_save. It uses the same legacy configuration payload rather than the mapped field names.
Related endpoints
- ImportJobsEmail_getDetails: re-read the actual stored row after saving.
- ImportJobsEmail_setName: set a display label separately.
- ImportJobsEmail_updateData: mapped configuration writes.