Here's one way:
Have a table of Speeches and a table of Contacts. Have a 3rd table, a 'dashboard' for the other two, with 3 fields: gSpeechID, gContactID, and SpeechPersonalised.
Write the speeches in the Speeches Table with placeholders such as [name] where you want the contact's name to be inserted.
Have a portal show a list of the speeches from the Speeches Table, and another portal list the contacts from the Contacts Table.
Let the user click a speech and a contact from the lists.
The SpeechPersonalised field is a calculation of:
Substitute ( Speeches::SpeechText ; "[name]"; Contacts::ContactName )
You can expand the idea by including placeholders for products or services also, or use [name] and [names] to correctly substitute 'Jim' and ' Jim's ' if necessary.
If it makes it easier to handle as the use expands you could have the user click a 'Show speech' button and a script can run through the range of substitutions you want to present in the (now text) SpeechPersonalised field.
Thanks, Your solution triggered a thought pattern for me. I needed to have the Speech show up on the main input screen in my main table. So using you idea for the substitute calculation I just set up a lookup field in the main table that pulls the speech from the Speeches table and then set up a Personalized Speeches field with the Substitution calculation to fill in the contacts first name. And no need for another table. WORKS GREAT!